diff --git a/platform/util/src/com/intellij/util/xmlb/AbstractCollectionBinding.java b/platform/util/src/com/intellij/util/xmlb/AbstractCollectionBinding.java index 678089b7cc5f..dd20dc36b0ca 100644 --- a/platform/util/src/com/intellij/util/xmlb/AbstractCollectionBinding.java +++ b/platform/util/src/com/intellij/util/xmlb/AbstractCollectionBinding.java @@ -28,7 +28,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; -abstract class AbstractCollectionBinding extends Binding { +abstract class AbstractCollectionBinding extends Binding implements MultiNodeBinding { private Map myElementBindings; private final Class myElementType; @@ -43,6 +43,11 @@ abstract class AbstractCollectionBinding extends Binding { myAnnotation = accessor == null ? null : accessor.getAnnotation(AbstractCollection.class); } + @Override + public boolean isMulti() { + return true; + } + @Override public void init() { if (myAnnotation != null) { diff --git a/platform/util/src/com/intellij/util/xmlb/AccessorBindingWrapper.java b/platform/util/src/com/intellij/util/xmlb/AccessorBindingWrapper.java index 8fd45fcf8164..8b872a47a94d 100644 --- a/platform/util/src/com/intellij/util/xmlb/AccessorBindingWrapper.java +++ b/platform/util/src/com/intellij/util/xmlb/AccessorBindingWrapper.java @@ -21,7 +21,7 @@ import org.jetbrains.annotations.Nullable; import java.util.List; -class AccessorBindingWrapper extends Binding { +class AccessorBindingWrapper extends Binding implements MultiNodeBinding { private final Binding myBinding; public AccessorBindingWrapper(@NotNull Accessor accessor, @NotNull Binding binding) { @@ -64,7 +64,7 @@ class AccessorBindingWrapper extends Binding { ((BeanBinding)myBinding).deserializeInto(currentValue, (Element)nodes.get(0), null); } else { - Object deserializedValue = myBinding.deserializeList(currentValue, nodes); + Object deserializedValue = Binding.deserializeList(myBinding, currentValue, nodes); if (currentValue != deserializedValue) { myAccessor.write(context, deserializedValue); } @@ -72,6 +72,11 @@ class AccessorBindingWrapper extends Binding { return context; } + @Override + public boolean isMulti() { + return myBinding instanceof MultiNodeBinding && ((MultiNodeBinding)myBinding).isMulti(); + } + @Override public boolean isBoundTo(Object node) { return myBinding.isBoundTo(node); diff --git a/platform/util/src/com/intellij/util/xmlb/AttributeBinding.java b/platform/util/src/com/intellij/util/xmlb/AttributeBinding.java index d556052bf585..5b04adb5b3eb 100644 --- a/platform/util/src/com/intellij/util/xmlb/AttributeBinding.java +++ b/platform/util/src/com/intellij/util/xmlb/AttributeBinding.java @@ -21,8 +21,6 @@ import org.jdom.Text; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.List; - public class AttributeBinding extends BasePrimitiveBinding { public AttributeBinding(@NotNull Accessor accessor, @NotNull Attribute attribute) { super(accessor, attribute.value(), attribute.converter()); @@ -52,13 +50,6 @@ public class AttributeBinding extends BasePrimitiveBinding { return new org.jdom.Attribute(myName, stringValue); } - @Nullable - @Override - public Object deserializeList(Object context, @NotNull List nodes) { - assert nodes.size() == 1; - return deserialize(context, nodes.get(0)); - } - @Override @Nullable public Object deserialize(Object context, @NotNull Object node) { diff --git a/platform/util/src/com/intellij/util/xmlb/BeanBinding.java b/platform/util/src/com/intellij/util/xmlb/BeanBinding.java index abcefd9f03df..c97b99bda912 100644 --- a/platform/util/src/com/intellij/util/xmlb/BeanBinding.java +++ b/platform/util/src/com/intellij/util/xmlb/BeanBinding.java @@ -127,26 +127,6 @@ class BeanBinding extends Binding { return instance; } - @Nullable - @Override - public Object deserializeList(Object context, @NotNull List nodes) { - Element element = null; - for (Object aNode : nodes) { - if (!XmlSerializerImpl.isIgnoredNode(aNode)) { - element = (Element)aNode; - break; - } - } - - if (element == null) { - return context; - } - - Object instance = ReflectionUtil.newInstance(myBeanClass); - deserializeInto(instance, element, null); - return instance; - } - @NotNull public Binding[] computeOrderedBindings(@NotNull LinkedHashSet accessorNameTracker) { final TObjectDoubleHashMap weights = new TObjectDoubleHashMap(accessorNameTracker.size()); @@ -182,7 +162,7 @@ class BeanBinding extends Binding { } public void deserializeInto(@NotNull Object result, @NotNull Element element, @Nullable Set accessorNameTracker) { - MultiMap data = MultiMap.createLinked(); + MultiMap data = null; nextNode: for (Object child : ContainerUtil.concat(element.getContent(), element.getAttributes())) { if (XmlSerializerImpl.isIgnoredNode(child)) { @@ -191,7 +171,18 @@ class BeanBinding extends Binding { for (Binding binding : myBindings) { if (binding.isBoundTo(child)) { - data.putValue(binding, child); + if (binding instanceof MultiNodeBinding && ((MultiNodeBinding)binding).isMulti()) { + if (data == null) { + data = MultiMap.createLinked(); + } + data.putValue(binding, child); + } + else { + if (accessorNameTracker != null) { + accessorNameTracker.add(binding.getAccessor().getName()); + } + binding.deserialize(result, child); + } continue nextNode; } } @@ -202,11 +193,13 @@ class BeanBinding extends Binding { Logger.getInstance("#" + myBeanClass.getName()).debug(message); } - for (Binding binding : data.keySet()) { - if (accessorNameTracker != null) { - accessorNameTracker.add(binding.getAccessor().getName()); + if (data != null) { + for (Binding binding : data.keySet()) { + if (accessorNameTracker != null) { + accessorNameTracker.add(binding.getAccessor().getName()); + } + ((MultiNodeBinding)binding).deserializeList(result, (List)data.get(binding)); } - binding.deserializeList(result, (List)data.get(binding)); } } diff --git a/platform/util/src/com/intellij/util/xmlb/Binding.java b/platform/util/src/com/intellij/util/xmlb/Binding.java index 8eb80bfcf6d4..bac5dcf702b8 100644 --- a/platform/util/src/com/intellij/util/xmlb/Binding.java +++ b/platform/util/src/com/intellij/util/xmlb/Binding.java @@ -38,13 +38,22 @@ abstract class Binding { @Nullable public abstract Object deserialize(Object context, @NotNull Object node); - @Nullable - public abstract Object deserializeList(Object context, @NotNull List nodes); - public abstract boolean isBoundTo(Object node); public abstract Class getBoundNodeType(); public void init() { } + + @SuppressWarnings("CastToIncompatibleInterface") + @Nullable + public static Object deserializeList(@NotNull Binding binding, Object context, @NotNull List nodes) { + if (binding instanceof MultiNodeBinding) { + return ((MultiNodeBinding)binding).deserializeList(context, nodes); + } + else { + assert nodes.size() == 1; + return binding.deserialize(context, nodes.get(0)); + } + } } diff --git a/platform/util/src/com/intellij/util/xmlb/DateBinding.java b/platform/util/src/com/intellij/util/xmlb/DateBinding.java index da3292932868..bc431c1d2f9e 100644 --- a/platform/util/src/com/intellij/util/xmlb/DateBinding.java +++ b/platform/util/src/com/intellij/util/xmlb/DateBinding.java @@ -44,4 +44,9 @@ public class DateBinding extends PrimitiveValueBinding { return new Date(0); } } + + @Override + public boolean isMulti() { + return false; + } } diff --git a/platform/util/src/com/intellij/util/xmlb/JDOMElementBinding.java b/platform/util/src/com/intellij/util/xmlb/JDOMElementBinding.java index 8e03f0ec47ff..ef33959f1e01 100644 --- a/platform/util/src/com/intellij/util/xmlb/JDOMElementBinding.java +++ b/platform/util/src/com/intellij/util/xmlb/JDOMElementBinding.java @@ -16,7 +16,6 @@ package com.intellij.util.xmlb; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.util.SmartList; import com.intellij.util.xmlb.annotations.Tag; import org.jdom.Element; import org.jetbrains.annotations.NotNull; @@ -25,7 +24,7 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; -class JDOMElementBinding extends Binding { +class JDOMElementBinding extends Binding implements MultiNodeBinding { private final String myTagName; public JDOMElementBinding(@NotNull Accessor accessor) { @@ -68,13 +67,8 @@ class JDOMElementBinding extends Binding { @Override public Object deserializeList(Object context, @NotNull List nodes) { if (myAccessor.getValueClass().isArray()) { - List result = new SmartList(); - for (Object aNode : nodes) { - if (!XmlSerializerImpl.isIgnoredNode(aNode)) { - result.add((Element)aNode); - } - } - myAccessor.write(context, result.toArray(new Element[nodes.size()])); + //noinspection SuspiciousToArrayCall + myAccessor.write(context, nodes.toArray(new Element[nodes.size()])); } else { Element element = null; @@ -90,6 +84,11 @@ class JDOMElementBinding extends Binding { return context; } + @Override + public boolean isMulti() { + return true; + } + @Override @Nullable public Object deserialize(Object context, @NotNull Object node) { diff --git a/platform/util/src/com/intellij/util/xmlb/MapBinding.java b/platform/util/src/com/intellij/util/xmlb/MapBinding.java index a39b59e70278..2818b4814734 100644 --- a/platform/util/src/com/intellij/util/xmlb/MapBinding.java +++ b/platform/util/src/com/intellij/util/xmlb/MapBinding.java @@ -32,11 +32,11 @@ import java.util.*; import static com.intellij.util.xmlb.Constants.*; -class MapBinding extends Binding { +class MapBinding extends Binding implements MultiNodeBinding { private static final Logger LOG = Logger.getInstance(MapBinding.class); private static final Comparator KEY_COMPARATOR = new Comparator() { - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "NullableProblems"}) @Override public int compare(Object o1, Object o2) { if (o1 instanceof Comparable && o2 instanceof Comparable) { @@ -64,6 +64,11 @@ class MapBinding extends Binding { myMapAnnotation = accessor.getAnnotation(MapAnnotation.class); } + @Override + public boolean isMulti() { + return true; + } + @Nullable @Override public Object serialize(Object o, @Nullable Object context, SerializationFilter filter) { @@ -186,7 +191,7 @@ class MapBinding extends Binding { } } else { - k = myKeyBinding.deserializeList(context, entry.getChild(getKeyAttributeName()).getContent()); + k = Binding.deserializeList(myKeyBinding, context, XmlSerializerImpl.getFilteredContent(entry.getChild(getKeyAttributeName()))); } } @@ -204,7 +209,7 @@ class MapBinding extends Binding { } } else { - v = myValueBinding.deserializeList(context, entry.getChild(getValueAttributeName()).getContent()); + v = Binding.deserializeList(myValueBinding, context, XmlSerializerImpl.getFilteredContent(entry.getChild(getValueAttributeName()))); } } diff --git a/platform/util/src/com/intellij/util/xmlb/MultiNodeBinding.java b/platform/util/src/com/intellij/util/xmlb/MultiNodeBinding.java new file mode 100644 index 000000000000..1a6ec4e705f5 --- /dev/null +++ b/platform/util/src/com/intellij/util/xmlb/MultiNodeBinding.java @@ -0,0 +1,28 @@ +/* + * 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. + */ +package com.intellij.util.xmlb; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +interface MultiNodeBinding { + @Nullable + Object deserializeList(Object context, @NotNull List nodes); + + boolean isMulti(); +} \ No newline at end of file diff --git a/platform/util/src/com/intellij/util/xmlb/OptionTagBinding.java b/platform/util/src/com/intellij/util/xmlb/OptionTagBinding.java index 9393b6e62c0b..b11cd341a3da 100644 --- a/platform/util/src/com/intellij/util/xmlb/OptionTagBinding.java +++ b/platform/util/src/com/intellij/util/xmlb/OptionTagBinding.java @@ -13,13 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.intellij.util.xmlb; -import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.JDOMUtil; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.util.SmartList; import com.intellij.util.xmlb.annotations.OptionTag; import org.jdom.Attribute; import org.jdom.Content; @@ -32,8 +29,6 @@ import org.jetbrains.annotations.Nullable; import java.util.List; class OptionTagBinding extends BasePrimitiveBinding { - private static final Logger LOG = Logger.getInstance(OptionTagBinding.class); - private final String myTagName; private final String myNameAttribute; private final String myValueAttribute; @@ -91,16 +86,6 @@ class OptionTagBinding extends BasePrimitiveBinding { return targetElement; } - @Nullable - @Override - public Object deserializeList(Object context, @NotNull List nodes) { - if (nodes.size() > 1) { - LOG.info("Duplicate options for " + context + " will be ignored"); - } - assert !nodes.isEmpty() : "Empty nodes passed to: " + this; - return deserialize(context, ((Element)nodes.get(0))); - } - @Override public Object deserialize(Object context, @NotNull Object node) { return deserialize(context, (Element)node); @@ -108,7 +93,17 @@ class OptionTagBinding extends BasePrimitiveBinding { private Object deserialize(Object context, Element element) { Attribute valueAttribute = element.getAttribute(myValueAttribute); - if (valueAttribute != null) { + if (valueAttribute == null) { + List children = XmlSerializerImpl.getFilteredContent(element); + if (children.isEmpty()) { + myAccessor.write(context, null); + } + else { + assert myBinding != null; + myAccessor.write(context, Binding.deserializeList(myBinding, myAccessor.read(context), children)); + } + } + else { Object value; if (myConverter != null) { value = myConverter.fromString(valueAttribute.getValue()); @@ -119,23 +114,6 @@ class OptionTagBinding extends BasePrimitiveBinding { } myAccessor.write(context, value); } - else { - List children = new SmartList(); - for (Content child : element.getContent()) { - if (!XmlSerializerImpl.isIgnoredNode(child)) { - children.add(child); - } - } - - if (!children.isEmpty()) { - assert myBinding != null; - Object value = myBinding.deserializeList(myAccessor.read(context), children); - myAccessor.write(context, value); - } - else { - myAccessor.write(context, null); - } - } return context; } diff --git a/platform/util/src/com/intellij/util/xmlb/PrimitiveValueBinding.java b/platform/util/src/com/intellij/util/xmlb/PrimitiveValueBinding.java index 1ac6e11124ba..cc103bd2c55e 100644 --- a/platform/util/src/com/intellij/util/xmlb/PrimitiveValueBinding.java +++ b/platform/util/src/com/intellij/util/xmlb/PrimitiveValueBinding.java @@ -23,7 +23,7 @@ import org.jetbrains.annotations.Nullable; import java.util.List; -class PrimitiveValueBinding extends Binding { +class PrimitiveValueBinding extends Binding implements MultiNodeBinding { private final Class myType; public PrimitiveValueBinding(@NotNull Class myType, @Nullable Accessor accessor) { @@ -55,6 +55,11 @@ class PrimitiveValueBinding extends Binding { } } + @Override + public boolean isMulti() { + return true; + } + @Override @Nullable public Object deserialize(Object o, @NotNull Object node) { diff --git a/platform/util/src/com/intellij/util/xmlb/SingleBinding.java b/platform/util/src/com/intellij/util/xmlb/SingleBinding.java deleted file mode 100644 index 7820d1ddaa08..000000000000 --- a/platform/util/src/com/intellij/util/xmlb/SingleBinding.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.intellij.util.xmlb; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; - -abstract class SingleBinding extends Binding { - protected SingleBinding(Accessor accessor) { - super(accessor); - } - - @Nullable - @Override - public Object deserializeList(Object context, @NotNull List nodes) { - assert nodes.size() == 1; - return deserialize(context, nodes.get(0)); - } -} \ No newline at end of file diff --git a/platform/util/src/com/intellij/util/xmlb/TagBinding.java b/platform/util/src/com/intellij/util/xmlb/TagBinding.java index 89e408edfa7f..6722d54f4a72 100644 --- a/platform/util/src/com/intellij/util/xmlb/TagBinding.java +++ b/platform/util/src/com/intellij/util/xmlb/TagBinding.java @@ -28,7 +28,7 @@ import org.jetbrains.annotations.Nullable; import java.util.Collections; import java.util.List; -class TagBinding extends BasePrimitiveBinding { +class TagBinding extends BasePrimitiveBinding implements MultiNodeBinding { private final String myTextIfEmpty; public TagBinding(@NotNull Accessor accessor, @NotNull Tag tagAnnotation) { @@ -64,17 +64,22 @@ class TagBinding extends BasePrimitiveBinding { Element element = (Element)node; assert element.getName().equals(name); //noinspection unchecked - children.addAll(((List)(isBeanBinding ? element.getChildren() : element.getContent()))); + children.addAll(((List)(isBeanBinding ? element.getChildren() : XmlSerializerImpl.getFilteredContent(element)))); } return deserialize(context, children, isBeanBinding); } + @Override + public boolean isMulti() { + return myBinding instanceof MultiNodeBinding && ((MultiNodeBinding)myBinding).isMulti(); + } + @Override @Nullable public Object deserialize(Object context, @NotNull Object node) { boolean isBeanBinding = myBinding instanceof BeanBinding; Element element = (Element)node; - return deserialize(context, isBeanBinding ? element.getChildren() : element.getContent(), isBeanBinding); + return deserialize(context, isBeanBinding ? element.getChildren() : XmlSerializerImpl.getFilteredContent(element), isBeanBinding); } private Object deserialize(Object o, List children, boolean isBeanBinding) { @@ -87,7 +92,7 @@ class TagBinding extends BasePrimitiveBinding { children = Collections.singletonList(new Text(myTextIfEmpty)); } - Object v = myBinding.deserializeList(myAccessor.read(o), children); + Object v = Binding.deserializeList(myBinding, myAccessor.read(o), children); myAccessor.write(o, XmlSerializerImpl.convert(v, myAccessor.getValueClass())); } return o; diff --git a/platform/util/src/com/intellij/util/xmlb/TagBindingWrapper.java b/platform/util/src/com/intellij/util/xmlb/TagBindingWrapper.java index 5d1dc0463416..76d811f4ee98 100644 --- a/platform/util/src/com/intellij/util/xmlb/TagBindingWrapper.java +++ b/platform/util/src/com/intellij/util/xmlb/TagBindingWrapper.java @@ -22,15 +22,15 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; //todo: merge with option tag binding -class TagBindingWrapper extends SingleBinding { - private final Binding binding; +class TagBindingWrapper extends Binding { + private final Binding myBinding; private final String myTagName; private final String myAttributeName; public TagBindingWrapper(@NotNull Binding binding, final String tagName, final String attributeName) { super(binding.myAccessor); - this.binding = binding; + myBinding = binding; //noinspection unchecked assert binding.getBoundNodeType().isAssignableFrom(Text.class); @@ -42,7 +42,7 @@ class TagBindingWrapper extends SingleBinding { @Override public Object serialize(Object o, @Nullable Object context, SerializationFilter filter) { Element e = new Element(myTagName); - Content content = (Content)binding.serialize(o, e, filter); + Content content = (Content)myBinding.serialize(o, e, filter); if (content != null) { if (!myAttributeName.isEmpty()) { e.setAttribute(myAttributeName, content.getValue()); @@ -61,10 +61,10 @@ class TagBindingWrapper extends SingleBinding { public Object deserialize(Object context, @NotNull Object node) { Element element = (Element)node; if (myAttributeName.isEmpty()) { - return binding.deserializeList(context, element.getContent()); + return Binding.deserializeList(myBinding, context, XmlSerializerImpl.getFilteredContent(element)); } else { - return binding.deserialize(context, element.getAttribute(myAttributeName)); + return myBinding.deserialize(context, element.getAttribute(myAttributeName)); } } diff --git a/platform/util/src/com/intellij/util/xmlb/TextBinding.java b/platform/util/src/com/intellij/util/xmlb/TextBinding.java index 276b2721b787..069f3c8b6e5d 100644 --- a/platform/util/src/com/intellij/util/xmlb/TextBinding.java +++ b/platform/util/src/com/intellij/util/xmlb/TextBinding.java @@ -20,7 +20,7 @@ import org.jdom.Text; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class TextBinding extends SingleBinding { +public class TextBinding extends Binding { private volatile Binding myBinding; public TextBinding(@NotNull Accessor accessor) { diff --git a/platform/util/src/com/intellij/util/xmlb/XmlSerializerImpl.java b/platform/util/src/com/intellij/util/xmlb/XmlSerializerImpl.java index d8206d3e6c8b..d693b722b56b 100644 --- a/platform/util/src/com/intellij/util/xmlb/XmlSerializerImpl.java +++ b/platform/util/src/com/intellij/util/xmlb/XmlSerializerImpl.java @@ -17,10 +17,8 @@ package com.intellij.util.xmlb; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; -import org.jdom.Attribute; -import org.jdom.Comment; -import org.jdom.Element; -import org.jdom.Text; +import org.jdom.*; +import org.jdom.filter.Filter; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -29,17 +27,36 @@ import java.lang.ref.SoftReference; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; -import java.util.Collection; -import java.util.Date; -import java.util.Map; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * @author mike */ class XmlSerializerImpl { + private static final Filter CONTENT_FILTER = new Filter() { + @Override + public boolean matches(Object obj) { + return !isIgnoredNode(obj); + } + }; + private static SoftReference, Binding>> ourBindings; + @NotNull + static List getFilteredContent(@NotNull Element element) { + List content = element.getContent(); + if (content.isEmpty()) { + return content; + } + else if (content.size() == 1) { + return isIgnoredNode(content.get(0)) ? Collections.emptyList() : content; + } + else { + return element.getContent(CONTENT_FILTER); + } + } + @NotNull static Element serialize(@NotNull Object object, @NotNull SerializationFilter filter) throws XmlSerializationException { try {