blob: 354af5f93d7b22f09e3de7457ee2a5a6bb2aeaf6 [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.
*/
package com.intellij.util.xmlb;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.xmlb.annotations.MapAnnotation;
import org.jdom.Attribute;
import org.jdom.Content;
import org.jdom.Element;
import org.jdom.Text;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Map;
import java.util.Set;
import static com.intellij.util.xmlb.Constants.*;
class MapBinding implements Binding {
private static final Logger LOG = Logger.getInstance(MapBinding.class);
private static final Comparator<Object> KEY_COMPARATOR = new Comparator<Object>() {
@SuppressWarnings("unchecked")
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Comparable && o2 instanceof Comparable) {
Comparable c1 = (Comparable)o1;
Comparable c2 = (Comparable)o2;
return c1.compareTo(c2);
}
return 0;
}
};
private final Binding myKeyBinding;
private final Binding myValueBinding;
private final MapAnnotation myMapAnnotation;
public MapBinding(ParameterizedType type, Accessor accessor) {
Type[] arguments = type.getActualTypeArguments();
Type keyType = arguments[0];
Type valueType = arguments[1];
myKeyBinding = XmlSerializerImpl.getBinding(keyType);
myValueBinding = XmlSerializerImpl.getBinding(valueType);
myMapAnnotation = XmlSerializerImpl.findAnnotation(accessor.getAnnotations(), MapAnnotation.class);
}
@Override
public Object serialize(Object o, Object context, SerializationFilter filter) {
Map map = (Map)o;
Element m;
if (myMapAnnotation == null || myMapAnnotation.surroundWithTag()) {
m = new Element(MAP);
}
else {
m = (Element)context;
}
final Set keySet = map.keySet();
final Object[] keys = ArrayUtil.toObjectArray(keySet);
if (myMapAnnotation == null || myMapAnnotation.sortBeforeSave()) {
Arrays.sort(keys, KEY_COMPARATOR);
}
for (Object k : keys) {
Object v = map.get(k);
Element entry = new Element(getEntryAttributeName());
m.addContent(entry);
Object kNode = myKeyBinding.serialize(k, entry, filter);
if (kNode instanceof Text) {
Text text = (Text)kNode;
entry.setAttribute(getKeyAttributeName(), text.getText());
}
else {
if (myMapAnnotation != null && !myMapAnnotation.surroundKeyWithTag()) {
entry.addContent((Content)kNode);
}
else {
Element key = new Element(getKeyAttributeName());
entry.addContent(key);
key.addContent((Content)kNode);
}
}
Object vNode = myValueBinding.serialize(v, entry, filter);
if (vNode instanceof Text) {
Text text = (Text)vNode;
entry.setAttribute(getValueAttributeName(), text.getText());
}
else {
if (myMapAnnotation != null && !myMapAnnotation.surroundValueWithTag()) {
entry.addContent((Element)vNode);
}
else {
Element value = new Element(getValueAttributeName());
entry.addContent(value);
value.addContent((Content)vNode);
}
}
}
return m;
}
private String getEntryAttributeName() {
return myMapAnnotation == null ? ENTRY : myMapAnnotation.entryTagName();
}
private String getKeyAttributeName() {
return myMapAnnotation == null ? KEY : myMapAnnotation.keyAttributeName();
}
private String getValueAttributeName() {
return myMapAnnotation == null ? VALUE : myMapAnnotation.valueAttributeName();
}
@Override
public Object deserialize(Object o, @NotNull Object... nodes) {
Map map = (Map)o;
map.clear();
final Object[] childNodes;
if (myMapAnnotation == null || myMapAnnotation.surroundWithTag()) {
assert nodes.length == 1;
Element m = (Element)nodes[0];
childNodes = JDOMUtil.getContent(m);
}
else {
childNodes = nodes;
}
for (Object childNode : childNodes) {
if (XmlSerializerImpl.isIgnoredNode(childNode)) continue;
Element entry = (Element)childNode;
Object k = null;
Object v = null;
if (!entry.getName().equals(getEntryAttributeName())) {
LOG.warn("unexpected entry for serialized Map will be skipped: " + entry);
continue;
}
Attribute keyAttr = entry.getAttribute(getKeyAttributeName());
if (keyAttr != null) {
k = myKeyBinding.deserialize(o, keyAttr);
}
else {
if (myMapAnnotation != null && !myMapAnnotation.surroundKeyWithTag()) {
final Object[] children = JDOMUtil.getContent(entry);
for (Object child : children) {
if (myKeyBinding.isBoundTo(child)) {
k = myKeyBinding.deserialize(o, child);
break;
}
}
assert k != null : "no key found";
}
else {
final Object keyNode = entry.getChildren(getKeyAttributeName()).get(0);
k = myKeyBinding.deserialize(o, JDOMUtil.getContent((Element)keyNode));
}
}
Attribute valueAttr = entry.getAttribute(getValueAttributeName());
if (valueAttr != null) {
v = myValueBinding.deserialize(o, valueAttr);
}
else {
if (myMapAnnotation != null && !myMapAnnotation.surroundValueWithTag()) {
final Object[] children = JDOMUtil.getContent(entry);
for (Object child : children) {
if (myValueBinding.isBoundTo(child)) {
v = myValueBinding.deserialize(o, child);
break;
}
}
assert v != null : "no value found";
}
else {
final Object valueNode = entry.getChildren(getValueAttributeName()).get(0);
v = myValueBinding.deserialize(o, XmlSerializerImpl.getNotIgnoredContent((Element)valueNode));
}
}
//noinspection unchecked
map.put(k, v);
}
return map;
}
@Override
public boolean isBoundTo(Object node) {
if (!(node instanceof Element)) return false;
if (myMapAnnotation != null && !myMapAnnotation.surroundWithTag()) {
return myMapAnnotation.entryTagName().equals(((Element)node).getName());
}
return ((Element)node).getName().equals(MAP);
}
@Override
public Class getBoundNodeType() {
return Element.class;
}
@Override
public void init() {
}
}