From 47e6e070ab01fcdd50063b6022f339de6806b3bd Mon Sep 17 00:00:00 2001 From: Dmitry Batkovich Date: Wed, 11 Feb 2015 15:21:37 +0300 Subject: [PATCH] XmlPropertiesFiles inserts property in alpha-order if it is possible --- .../properties/xml/XmlPropertiesFileImpl.java | 145 ++++++++++++++---- plugins/properties/testData/xml/bar.xml | 7 + .../properties/xml/XmlPropertiesTest.java | 19 +++ 3 files changed, 145 insertions(+), 26 deletions(-) create mode 100644 plugins/properties/testData/xml/bar.xml diff --git a/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/xml/XmlPropertiesFileImpl.java b/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/xml/XmlPropertiesFileImpl.java index a3a94c8f7f18..3590681522f8 100644 --- a/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/xml/XmlPropertiesFileImpl.java +++ b/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/xml/XmlPropertiesFileImpl.java @@ -33,7 +33,10 @@ import com.intellij.psi.util.CachedValuesManager; import com.intellij.psi.xml.XmlFile; import com.intellij.psi.xml.XmlTag; import com.intellij.reference.SoftLazyValue; +import com.intellij.util.Function; import com.intellij.util.IncorrectOperationException; +import com.intellij.util.SmartList; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; import gnu.trove.THashMap; import org.jetbrains.annotations.NonNls; @@ -47,29 +50,54 @@ import java.util.*; * Date: 7/26/11 */ public class XmlPropertiesFileImpl extends XmlPropertiesFile { - private static final Key> KEY = Key.create("xml properties file"); private final XmlFile myFile; - private final SoftLazyValue> myPropertiesMap = new SoftLazyValue>() { + private final SoftLazyValue myInfo = new SoftLazyValue() { @NotNull @Override - protected MultiMap compute() { - XmlTag rootTag = myFile.getRootTag(); - if (rootTag == null) { - return MultiMap.emptyInstance(); - } - - XmlTag[] entries = rootTag.findSubTags("entry"); - MultiMap map = new MultiMap(); - - for (XmlTag entry : entries) { - XmlProperty property = new XmlProperty(entry, XmlPropertiesFileImpl.this); - map.putValue(property.getKey(), property); - } - return map; + protected Info compute() { + return new Info(); } }; + private class Info { + private final MultiMap myPropertiesMap = MultiMap.create(); + private List myPropertiesOrder; + private boolean mySorted; + + public Info() { + XmlTag rootTag = myFile.getRootTag(); + final List propertiesOrder = new ArrayList(); + if (rootTag != null) { + XmlTag[] entries = rootTag.findSubTags("entry"); + for (XmlTag entry : entries) { + XmlProperty property = new XmlProperty(entry, XmlPropertiesFileImpl.this); + propertiesOrder.add(property); + myPropertiesMap.putValue(property.getKey(), property); + } + } + mySorted = PropertiesImplUtil.isAlphaSorted(propertiesOrder); + myPropertiesOrder = mySorted ? propertiesOrder : null; + } + + public void setSorted(boolean sorted) { + mySorted = sorted; + myPropertiesOrder = null; + } + + public MultiMap getPropertiesMap() { + return myPropertiesMap; + } + + public List getPropertiesOrder() { + return myPropertiesOrder; + } + + public boolean isSorted() { + return mySorted; + } + } + private XmlPropertiesFileImpl(XmlFile file) { myFile = file; } @@ -83,19 +111,19 @@ public class XmlPropertiesFileImpl extends XmlPropertiesFile { @NotNull @Override public List getProperties() { - return new ArrayList(myPropertiesMap.getValue().values()); + return new ArrayList(myInfo.getValue().getPropertiesMap().values()); } @Override public IProperty findPropertyByKey(@NotNull @NonNls String key) { - Collection properties = myPropertiesMap.getValue().get(key); + Collection properties = myInfo.getValue().getPropertiesMap().get(key); return properties.isEmpty() ? null : properties.iterator().next(); } @NotNull @Override public List findPropertiesByKey(@NotNull @NonNls String key) { - return new ArrayList(myPropertiesMap.getValue().get(key)); + return new ArrayList(myInfo.getValue().getPropertiesMap().get(key)); } @NotNull @@ -122,22 +150,62 @@ public class XmlPropertiesFileImpl extends XmlPropertiesFile { @Override public IProperty addPropertyAfter(String key, String value, Property anchor) { - return null; + return addPropertyAfterAndCheckAlphaSorting(key, value, anchor, true, true); + } + + @NotNull + public IProperty addPropertyAfterAndCheckAlphaSorting(String key, String value, @Nullable IProperty anchor, boolean addToEnd, boolean checkAlphaSorting) { + final XmlTag anchorTag = anchor == null ? null : (XmlTag)anchor.getPsiElement(); + final XmlTag rootTag = myFile.getRootTag(); + final XmlTag entry = createPropertyTag(key, value); + final XmlTag addedEntry = (XmlTag) (anchorTag == null ? myFile.getRootTag().addSubTag(entry, !addToEnd) : rootTag.addAfter(entry, anchorTag)); + final XmlProperty property = new XmlProperty(addedEntry, this); + myInfo.getValue().getPropertiesMap().putValue(key, property); + if (checkAlphaSorting) { + checkAlphaSorting(property); + } + return property; } @NotNull @Override public IProperty addProperty(String key, String value) { + final XmlTag entry = createPropertyTag(key, value); + if (myInfo.getValue().isSorted()) { + final XmlProperty dummyProperty = new XmlProperty(entry, this); + final int insertIndex = Collections.binarySearch(myInfo.getValue().getPropertiesOrder(), dummyProperty, new Comparator() { + @Override + public int compare(IProperty p1, IProperty p2) { + final String k1 = p1.getKey(); + final String k2 = p2.getKey(); + return k1.compareTo(k2); + } + }); + final IProperty insertPosition; + final IProperty inserted; + if (insertIndex == -1) { + inserted = addPropertyAfterAndCheckAlphaSorting(key, value, null, false, false); + myInfo.getValue().getPropertiesOrder().add(0, inserted); + } + else { + final int position = insertIndex < 0 ? -insertIndex - 2 : insertIndex; + insertPosition = myInfo.getValue().getPropertiesOrder().get(position); + inserted = addPropertyAfterAndCheckAlphaSorting(key, value, insertPosition, false, false); + myInfo.getValue().getPropertiesOrder().add(position + 1, inserted); + } + return inserted; + } else { + return addPropertyAfterAndCheckAlphaSorting(key, value, null, true, false); + } + } + + private XmlTag createPropertyTag(final String key, final String value) { XmlTag rootTag = myFile.getRootTag(); XmlTag entry = rootTag.createChildTag("entry", "", value, false); entry.setAttribute("key", key); - rootTag.addSubTag(entry, false); - final XmlProperty property = new XmlProperty(entry, this); - myPropertiesMap.getValue().putValue(key, property); - return property; + return entry; } - public static PropertiesFile getPropertiesFile(final PsiFile file) { CachedValuesManager manager = CachedValuesManager.getManager(file.getProject()); if (file instanceof XmlFile) { @@ -194,7 +262,7 @@ public class XmlPropertiesFileImpl extends XmlPropertiesFile { @Override public boolean isAlphaSorted() { - return PropertiesImplUtil.isAlphaSorted(getProperties()); + return myInfo.getValue().isSorted(); } @Override @@ -213,4 +281,29 @@ public class XmlPropertiesFileImpl extends XmlPropertiesFile { public int hashCode() { return myFile.hashCode(); } + + private void checkAlphaSorting(final IProperty property) { + if (myInfo.getValue().isSorted()) { + final String key = property.getKey(); + final XmlTag prev = getSibling((XmlTag)property.getPsiElement(), true); + final String prevKey = prev == null ? null : new XmlProperty(prev, this).getKey(); + if (prevKey != null && key != null && prevKey.compareTo(key) > 0) { + myInfo.getValue().setSorted(false); + } else { + final XmlTag next = getSibling((XmlTag)property.getPsiElement(), false); + final String nextKey = next == null ? null : new XmlProperty(next, this).getKey(); + if (nextKey != null && key != null && nextKey.compareTo(key) < 0) { + myInfo.getValue().setSorted(false); + } + } + } + } + + private static XmlTag getSibling(final XmlTag entry, final boolean prev) { + XmlTag sibling = (XmlTag)(prev ? entry.getPrevSibling() : entry.getNextSibling()); + while (sibling != null && !"entry".equals(sibling.getName())) { + sibling = (XmlTag)(prev ? sibling.getPrevSibling() : sibling.getNextSibling()); + } + return sibling; + } } diff --git a/plugins/properties/testData/xml/bar.xml b/plugins/properties/testData/xml/bar.xml new file mode 100644 index 000000000000..b85abb53cc8e --- /dev/null +++ b/plugins/properties/testData/xml/bar.xml @@ -0,0 +1,7 @@ + + + + Hi + bar + baz + \ No newline at end of file diff --git a/plugins/properties/testSrc/com/intellij/lang/properties/xml/XmlPropertiesTest.java b/plugins/properties/testSrc/com/intellij/lang/properties/xml/XmlPropertiesTest.java index d16eaf352f38..fd4b1212107f 100644 --- a/plugins/properties/testSrc/com/intellij/lang/properties/xml/XmlPropertiesTest.java +++ b/plugins/properties/testSrc/com/intellij/lang/properties/xml/XmlPropertiesTest.java @@ -9,6 +9,8 @@ import com.intellij.openapi.command.WriteCommandAction; import com.intellij.psi.PsiFile; import com.intellij.testFramework.PlatformTestCase; import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; import java.util.List; @@ -81,6 +83,23 @@ public class XmlPropertiesTest extends LightPlatformCodeInsightFixtureTestCase { assertEquals("vvv", property2.getValue()); } + public void testAddPropertyInAlphaOrder() { + final PsiFile psiFile = myFixture.configureByFile("bar.xml"); + final PropertiesFile propertiesFile = PropertiesImplUtil.getPropertiesFile(psiFile); + assertNotNull(propertiesFile); + + WriteCommandAction.runWriteCommandAction(getProject(), new Runnable() { + public void run() { + propertiesFile.addProperty("d", "vvv"); + propertiesFile.addProperty("a", "vvv"); + propertiesFile.addProperty("l", "vvv"); + propertiesFile.addProperty("v", "vvv"); + } + }); + assertTrue(propertiesFile.isAlphaSorted()); + assertTrue(PropertiesImplUtil.getPropertiesFile(psiFile).isAlphaSorted()); + } + @Override protected String getTestDataPath() { return PluginPathManager.getPluginHomePath("properties") + "/testData/xml/";