diff --git a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java index e525c567970a..e7cdf92aebd5 100644 --- a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java +++ b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java @@ -2472,7 +2472,7 @@ public class StringUtil extends StringUtilRt { * Escape property name or key in property file. Unicode characters are escaped as well. * * @param input an input to escape - * @param isKey if true, the rules for key escaping are applied. The leading space is escaped in that case. + * @param isKey if true, the rules for key escaping are applied. All spaces are escaped in that case. * @return an escaped string */ @NotNull @@ -2483,7 +2483,7 @@ public class StringUtil extends StringUtilRt { final char ch = input.charAt(i); switch (ch) { case ' ': - if (isKey && i == 0) { + if (isKey) { // only the leading space has to be escaped escaped.append('\\'); } diff --git a/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/psi/impl/PropertiesFileImpl.java b/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/psi/impl/PropertiesFileImpl.java index e52f027dcaa7..d5a4cceccdbb 100644 --- a/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/psi/impl/PropertiesFileImpl.java +++ b/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/psi/impl/PropertiesFileImpl.java @@ -19,41 +19,39 @@ import com.intellij.extapi.psi.PsiFileBase; import com.intellij.lang.ASTFactory; import com.intellij.lang.ASTNode; import com.intellij.lang.properties.*; -import com.intellij.lang.properties.ResourceBundle; import com.intellij.lang.properties.parsing.PropertiesElementTypes; import com.intellij.lang.properties.psi.PropertiesElementFactory; import com.intellij.lang.properties.psi.PropertiesFile; +import com.intellij.lang.properties.psi.PropertiesList; import com.intellij.lang.properties.psi.Property; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.FileViewProvider; import com.intellij.psi.PsiElement; import com.intellij.psi.TokenType; import com.intellij.psi.impl.source.tree.ChangeUtil; import com.intellij.psi.impl.source.tree.TreeElement; -import com.intellij.psi.stubs.PsiFileStub; -import com.intellij.psi.stubs.StubElement; -import com.intellij.psi.stubs.StubTree; import com.intellij.psi.tree.TokenSet; +import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.IncorrectOperationException; import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.containers.MostlySingularMultiMap; import gnu.trove.THashMap; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; import java.util.stream.Collectors; +import java.util.stream.Stream; public class PropertiesFileImpl extends PsiFileBase implements PropertiesFile { private static final Logger LOG = Logger.getInstance(PropertiesFileImpl.class); private static final TokenSet PROPERTIES_LIST_SET = TokenSet.create(PropertiesElementTypes.PROPERTIES_LIST); - private volatile MostlySingularMultiMap myPropertiesMap; //guarded by lock - private volatile List myProperties; //guarded by lock - private volatile boolean myAlphaSorted; - private final Object lock = new Object(); public PropertiesFileImpl(FileViewProvider viewProvider) { super(viewProvider, PropertiesLanguage.INSTANCE); @@ -73,65 +71,25 @@ public class PropertiesFileImpl extends PsiFileBase implements PropertiesFile { @Override @NotNull public List getProperties() { - ensurePropertiesLoaded(); - return myProperties; + final PropertiesList propertiesList = PsiTreeUtil.getStubChildOfType(this, PropertiesList.class); + if (propertiesList == null) return Collections.emptyList(); + return Collections.unmodifiableList(PsiTreeUtil.getStubChildrenOfTypeAsList(propertiesList, Property.class)); } private ASTNode getPropertiesList() { return ArrayUtil.getFirstElement(getNode().getChildren(PROPERTIES_LIST_SET)); } - private void ensurePropertiesLoaded() { - if (myPropertiesMap != null) return; - final StubTree stubTree = getStubTree(); - List properties; - MostlySingularMultiMap propertiesMap = new MostlySingularMultiMap<>(); - if (stubTree != null) { - final PsiFileStub root = stubTree.getRoot(); - final StubElement propertiesList = root.findChildStubByType(PropertiesElementTypes.PROPERTIES_LIST); - if (propertiesList != null) { - properties = Arrays.stream(propertiesList.getChildrenByType(PropertiesElementTypes.PROPERTY, Property[]::new)) - .map(IProperty.class::cast) - .peek(p -> propertiesMap.add(p.getKey(), p)) - .collect(Collectors.toList()); - } else { - properties = Collections.emptyList(); - } - } else { - final ASTNode[] props = getPropertiesList().getChildren(PropertiesElementTypes.PROPERTIES); - properties = new ArrayList<>(props.length); - for (final ASTNode prop : props) { - final Property property = (Property)prop.getPsi(); - String key = property.getUnescapedKey(); - propertiesMap.add(key, property); - properties.add(property); - } - } - final boolean isAlphaSorted = PropertiesImplUtil.isAlphaSorted(properties); - synchronized (lock) { - if (myPropertiesMap != null) return; - myProperties = properties; - myPropertiesMap = propertiesMap; - myAlphaSorted = isAlphaSorted; - } - } - + @Nullable @Override public IProperty findPropertyByKey(@NotNull String key) { - ensurePropertiesLoaded(); - synchronized (lock) { - Iterator iterator = myPropertiesMap.get(key).iterator(); - return iterator.hasNext() ? iterator.next() : null; - } + return propertiesByKey(key).findFirst().orElse(null); } @Override @NotNull public List findPropertiesByKey(@NotNull String key) { - ensurePropertiesLoaded(); - synchronized (lock) { - return ContainerUtil.collect(myPropertiesMap.get(key).iterator()); - } + return propertiesByKey(key).collect(Collectors.toList()); } @Override @@ -161,25 +119,6 @@ public class PropertiesFileImpl extends PsiFileBase implements PropertiesFile { return addPropertyAfter(property, position); } - private IProperty findInsertionPosition(@NotNull IProperty property) { - synchronized (lock) { - ensurePropertiesLoaded(); - if (myProperties.isEmpty()) { - return null; - } - if (myAlphaSorted) { - final int insertIndex = Collections.binarySearch(myProperties, property, (p1, p2) -> { - final String k1 = p1.getKey(); - final String k2 = p2.getKey(); - LOG.assertTrue(k1 != null && k2 != null); - return String.CASE_INSENSITIVE_ORDER.compare(k1, k2); - }); - return insertIndex == -1 ? null :myProperties.get(insertIndex < 0 ? - insertIndex - 2 : insertIndex); - } - return myProperties.get(myProperties.size() - 1); - } - } - @Override @NotNull public PsiElement addPropertyAfter(@NotNull final IProperty property, @Nullable final IProperty anchor) throws IncorrectOperationException { @@ -235,19 +174,26 @@ public class PropertiesFileImpl extends PsiFileBase implements PropertiesFile { @Override public boolean isAlphaSorted() { - synchronized (lock) { - ensurePropertiesLoaded(); - return myAlphaSorted; - } + return PropertiesImplUtil.isAlphaSorted(getProperties()); } - @Override - public void clearCaches() { - super.clearCaches(); - - synchronized (lock) { - myPropertiesMap = null; - myProperties = null; + private IProperty findInsertionPosition(@NotNull IProperty property) { + List properties = getProperties(); + if (properties.isEmpty()) return null; + if (PropertiesImplUtil.isAlphaSorted(properties)) { + final int insertIndex = Collections.binarySearch(getProperties(), property, (p1, p2) -> { + final String k1 = p1.getKey(); + final String k2 = p2.getKey(); + LOG.assertTrue(k1 != null && k2 != null); + return String.CASE_INSENSITIVE_ORDER.compare(k1, k2); + }); + return insertIndex == -1 ? null : getProperties().get(insertIndex < 0 ? -insertIndex - 2 : insertIndex); } + return ContainerUtil.getLastItem(properties); + } + + private Stream propertiesByKey(@NotNull String key) { + final String escapedKey = StringUtil.escapeProperty(key, true); + return getProperties().stream().filter(p -> escapedKey.equals(p.getKey())); } }