blob: 398b9ae1f8e432fb5e4de74541cf8fbc791b5958 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @author cdr
*/
package com.intellij.lang.properties.structureView;
import com.intellij.lang.properties.*;
import com.intellij.lang.properties.psi.PropertiesFile;
import com.intellij.openapi.components.*;
import com.intellij.openapi.project.Project;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.SoftFactoryMap;
import com.intellij.util.xmlb.annotations.MapAnnotation;
import com.intellij.util.xmlb.annotations.Property;
import com.intellij.util.xmlb.annotations.Transient;
import gnu.trove.TIntLongHashMap;
import gnu.trove.TIntProcedure;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
@State(
name="PropertiesSeparatorManager",
storages= {
@Storage(
file = StoragePathMacros.PROJECT_FILE
)}
)
public class PropertiesSeparatorManager implements PersistentStateComponent<PropertiesSeparatorManager.PropertiesSeparatorManagerState> {
private final Project myProject;
public static PropertiesSeparatorManager getInstance(final Project project) {
return ServiceManager.getService(project, PropertiesSeparatorManager.class);
}
private PropertiesSeparatorManagerState myUserDefinedSeparators = new PropertiesSeparatorManagerState();
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
private final SoftFactoryMap<ResourceBundleImpl, String> myGuessedSeparators = new SoftFactoryMap<ResourceBundleImpl, String>() {
@Nullable
@Override
protected String create(ResourceBundleImpl resourceBundle) {
return guessSeparator(resourceBundle);
}
};
public PropertiesSeparatorManager(final Project project) {
myProject = project;
}
@NotNull
public String getSeparator(final ResourceBundle resourceBundle) {
if (!(resourceBundle instanceof ResourceBundleImpl)) {
return ".";
}
final ResourceBundleImpl resourceBundleImpl = (ResourceBundleImpl)resourceBundle;
String separator = myUserDefinedSeparators.getSeparators().get(resourceBundleImpl.getUrl());
return separator == null ? myGuessedSeparators.get(resourceBundleImpl) : separator;
}
//returns most probable separator in properties files
private static String guessSeparator(final ResourceBundleImpl resourceBundle) {
final TIntLongHashMap charCounts = new TIntLongHashMap();
for (PropertiesFile propertiesFile : resourceBundle.getPropertiesFiles()) {
if (propertiesFile == null) continue;
List<IProperty> properties = propertiesFile.getProperties();
for (IProperty property : properties) {
String key = property.getUnescapedKey();
if (key == null) continue;
for (int i =0; i<key.length(); i++) {
char c = key.charAt(i);
if (!Character.isLetterOrDigit(c)) {
charCounts.put(c, charCounts.get(c) + 1);
}
}
}
}
final char[] mostProbableChar = new char[]{'.'};
charCounts.forEachKey(new TIntProcedure() {
long count = -1;
public boolean execute(int ch) {
long charCount = charCounts.get(ch);
if (charCount > count) {
count = charCount;
mostProbableChar[0] = (char)ch;
}
return true;
}
});
if (mostProbableChar[0] == 0) {
mostProbableChar[0] = '.';
}
return Character.toString(mostProbableChar[0]);
}
public void setSeparator(ResourceBundle resourceBundle, String separator) {
if (resourceBundle instanceof ResourceBundleImpl) {
myUserDefinedSeparators.getSeparators().put(((ResourceBundleImpl)resourceBundle).getUrl(), separator);
}
}
public void loadState(final PropertiesSeparatorManagerState state) {
myUserDefinedSeparators = state.decode(myProject);
}
@Nullable
@Override
public PropertiesSeparatorManagerState getState() {
return myUserDefinedSeparators.isEmpty() ? null : myUserDefinedSeparators.encode();
}
public static class PropertiesSeparatorManagerState {
@Property(surroundWithTag = false)
@MapAnnotation(surroundWithTag = false,
surroundKeyWithTag = false,
surroundValueWithTag = false,
keyAttributeName = "url",
valueAttributeName = "separator",
entryTagName = "file")
public Map<String, String> mySeparators = new HashMap<String, String>();
public Map<String, String> getSeparators() {
return mySeparators;
}
public boolean isEmpty() {
return mySeparators.isEmpty();
}
public PropertiesSeparatorManagerState encode() {
PropertiesSeparatorManagerState encodedState = new PropertiesSeparatorManagerState();
for (final Map.Entry<String, String> entry : mySeparators.entrySet()) {
String separator = entry.getValue();
StringBuilder encoded = new StringBuilder(separator.length());
for (int i=0;i<separator.length();i++) {
char c = separator.charAt(i);
encoded.append("\\u");
encoded.append(String.format("%04x", (int) c));
}
encodedState.getSeparators().put(entry.getKey(), encoded.toString());
}
return encodedState;
}
public PropertiesSeparatorManagerState decode(final Project project) {
PropertiesSeparatorManagerState decoded = new PropertiesSeparatorManagerState();
for (final Map.Entry<String, String> entry : mySeparators.entrySet()) {
String separator = entry.getValue();
separator = decodeSeparator(separator);
if (separator == null) {
continue;
}
final String url = entry.getKey();
ResourceBundle resourceBundle = PropertiesImplUtil.createByUrl(url, project);
if (resourceBundle != null) {
decoded.getSeparators().put(url, separator);
}
}
return decoded;
}
}
@Nullable
private static String decodeSeparator(String separator) {
if (separator.length() % 6 != 0) {
return null;
}
StringBuilder result = new StringBuilder();
int pos = 0;
while (pos < separator.length()) {
String encodedCharacter = separator.substring(pos, pos+6);
if (!encodedCharacter.startsWith("\\u")) {
return null;
}
char code = (char) Integer.parseInt(encodedCharacter.substring(2), 16);
result.append(code);
pos += 6;
}
return result.toString();
}
}