ImplicitCollectionMapper.java
/*
* Copyright (C) 2005 Joe Walnes.
* Copyright (C) 2006, 2007, 2009, 2011, 2012, 2013, 2014, 2015, 2016 XStream Committers.
* All rights reserved.
*
* The software in this package is published under the terms of the BSD
* style license a copy of which has been included with this distribution in
* the LICENSE.txt file.
*
* Created on 16. February 2005 by Joe Walnes
*/
package com.thoughtworks.xstream.mapper;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import com.thoughtworks.xstream.InitializationException;
import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
import com.thoughtworks.xstream.core.util.Primitives;
public class ImplicitCollectionMapper extends MapperWrapper {
private final ReflectionProvider reflectionProvider;
public ImplicitCollectionMapper(final Mapper wrapped, final ReflectionProvider reflectionProvider) {
super(wrapped);
this.reflectionProvider = reflectionProvider;
}
private final Map<Class<?>, ImplicitCollectionMapperForClass> classNameToMapper = new HashMap<>();
private ImplicitCollectionMapperForClass getMapper(final Class<?> declaredFor, final String fieldName) {
Class<?> definedIn = declaredFor;
final Field field = fieldName != null ? reflectionProvider.getFieldOrNull(definedIn, fieldName) : null;
final Class<?> inheritanceStop = field != null ? field.getDeclaringClass() : null;
while (definedIn != null) {
final ImplicitCollectionMapperForClass mapper = classNameToMapper.get(definedIn);
if (mapper != null) {
return mapper;
}
if (definedIn == inheritanceStop) {
break;
}
definedIn = definedIn.getSuperclass();
}
return null;
}
private ImplicitCollectionMapperForClass getOrCreateMapper(final Class<?> definedIn) {
ImplicitCollectionMapperForClass mapper = classNameToMapper.get(definedIn);
if (mapper == null) {
mapper = new ImplicitCollectionMapperForClass(definedIn);
classNameToMapper.put(definedIn, mapper);
}
return mapper;
}
@Override
public String getFieldNameForItemTypeAndName(final Class<?> definedIn, final Class<?> itemType,
final String itemFieldName) {
final ImplicitCollectionMapperForClass mapper = getMapper(definedIn, null);
if (mapper != null) {
return mapper.getFieldNameForItemTypeAndName(itemType, itemFieldName);
} else {
return null;
}
}
@Override
public Class<?> getItemTypeForItemFieldName(final Class<?> definedIn, final String itemFieldName) {
final ImplicitCollectionMapperForClass mapper = getMapper(definedIn, null);
if (mapper != null) {
return mapper.getItemTypeForItemFieldName(itemFieldName);
} else {
return null;
}
}
@Override
public ImplicitCollectionMapping getImplicitCollectionDefForFieldName(final Class<?> itemType,
final String fieldName) {
final ImplicitCollectionMapperForClass mapper = getMapper(itemType, fieldName);
if (mapper != null) {
return mapper.getImplicitCollectionDefForFieldName(fieldName);
} else {
return null;
}
}
public void add(final Class<?> definedIn, final String fieldName, final Class<?> itemType) {
add(definedIn, fieldName, null, itemType);
}
public void add(final Class<?> definedIn, final String fieldName, final String itemFieldName,
final Class<?> itemType) {
add(definedIn, fieldName, itemFieldName, itemType, null);
}
public void add(final Class<?> definedIn, final String fieldName, final String itemFieldName, Class<?> itemType,
final String keyFieldName) {
Field field = null;
if (definedIn != null) {
Class<?> declaredIn = definedIn;
while (declaredIn != Object.class) {
try {
field = declaredIn.getDeclaredField(fieldName);
if (!Modifier.isStatic(field.getModifiers())) {
break;
}
field = null;
} catch (final SecurityException e) {
throw new InitializationException("Access denied for field with implicit collection", e);
} catch (final NoSuchFieldException e) {
declaredIn = declaredIn.getSuperclass();
}
}
}
if (field == null) {
throw new InitializationException("No field \"" + fieldName + "\" for implicit collection");
} else if (Map.class.isAssignableFrom(field.getType())) {
if (itemFieldName == null && keyFieldName == null) {
itemType = Map.Entry.class;
}
} else if (!Collection.class.isAssignableFrom(field.getType())) {
final Class<?> fieldType = field.getType();
if (!fieldType.isArray()) {
throw new InitializationException("Field \"" + fieldName + "\" declares no collection or array");
} else {
Class<?> componentType = fieldType.getComponentType();
componentType = componentType.isPrimitive() ? Primitives.box(componentType) : componentType;
if (itemType == null) {
itemType = componentType;
} else {
itemType = itemType.isPrimitive() ? Primitives.box(itemType) : itemType;
if (!componentType.isAssignableFrom(itemType)) {
throw new InitializationException("Field \""
+ fieldName
+ "\" declares an array, but the array type is not compatible with "
+ itemType.getName());
}
}
}
}
final ImplicitCollectionMapperForClass mapper = getOrCreateMapper(definedIn);
mapper.add(new ImplicitCollectionMappingImpl(fieldName, itemType, itemFieldName, keyFieldName));
}
private class ImplicitCollectionMapperForClass {
private final Class<?> definedIn;
private final Map<NamedItemType, ImplicitCollectionMappingImpl> namedItemTypeToDef = new HashMap<>();
private final Map<String, ImplicitCollectionMappingImpl> itemFieldNameToDef = new HashMap<>();
private final Map<String, ImplicitCollectionMappingImpl> fieldNameToDef = new HashMap<>();
ImplicitCollectionMapperForClass(final Class<?> definedIn) {
this.definedIn = definedIn;
}
public String getFieldNameForItemTypeAndName(final Class<?> itemType, final String itemFieldName) {
ImplicitCollectionMappingImpl unnamed = null;
for (final NamedItemType itemTypeForFieldName : namedItemTypeToDef.keySet()) {
final ImplicitCollectionMappingImpl def = namedItemTypeToDef.get(itemTypeForFieldName);
if (itemType == Mapper.Null.class) {
unnamed = def;
break;
} else if (itemTypeForFieldName.itemType.isAssignableFrom(itemType)) {
if (def.getItemFieldName() != null) {
if (def.getItemFieldName().equals(itemFieldName)) {
return def.getFieldName();
}
} else {
if (unnamed == null
|| unnamed.getItemType() == null
|| def.getItemType() != null && unnamed.getItemType().isAssignableFrom(def.getItemType())) {
unnamed = def;
}
}
}
}
if (unnamed != null) {
return unnamed.getFieldName();
} else {
final ImplicitCollectionMapperForClass mapper = getMapper(definedIn.getSuperclass(), null);
return mapper != null ? mapper.getFieldNameForItemTypeAndName(itemType, itemFieldName) : null;
}
}
public Class<?> getItemTypeForItemFieldName(final String itemFieldName) {
final ImplicitCollectionMappingImpl def = getImplicitCollectionDefByItemFieldName(itemFieldName);
if (def != null) {
return def.getItemType();
} else {
final ImplicitCollectionMapperForClass mapper = getMapper(definedIn.getSuperclass(), null);
return mapper != null ? mapper.getItemTypeForItemFieldName(itemFieldName) : null;
}
}
private ImplicitCollectionMappingImpl getImplicitCollectionDefByItemFieldName(final String itemFieldName) {
if (itemFieldName == null) {
return null;
} else {
final ImplicitCollectionMappingImpl mapping = itemFieldNameToDef.get(itemFieldName);
if (mapping != null) {
return mapping;
} else {
final ImplicitCollectionMapperForClass mapper = getMapper(definedIn.getSuperclass(), null);
return mapper != null ? mapper.getImplicitCollectionDefByItemFieldName(itemFieldName) : null;
}
}
}
public ImplicitCollectionMapping getImplicitCollectionDefForFieldName(final String fieldName) {
final ImplicitCollectionMapping mapping = fieldNameToDef.get(fieldName);
if (mapping != null) {
return mapping;
} else {
final ImplicitCollectionMapperForClass mapper = getMapper(definedIn.getSuperclass(), null);
return mapper != null ? mapper.getImplicitCollectionDefForFieldName(fieldName) : null;
}
}
public void add(final ImplicitCollectionMappingImpl def) {
fieldNameToDef.put(def.getFieldName(), def);
namedItemTypeToDef.put(def.createNamedItemType(), def);
if (def.getItemFieldName() != null) {
itemFieldNameToDef.put(def.getItemFieldName(), def);
}
}
}
private static class ImplicitCollectionMappingImpl implements ImplicitCollectionMapping {
private final String fieldName;
private final String itemFieldName;
private final Class<?> itemType;
private final String keyFieldName;
ImplicitCollectionMappingImpl(
final String fieldName, final Class<?> itemType, final String itemFieldName,
final String keyFieldName) {
this.fieldName = fieldName;
this.itemFieldName = itemFieldName;
this.itemType = itemType;
this.keyFieldName = keyFieldName;
}
public NamedItemType createNamedItemType() {
return new NamedItemType(itemType, itemFieldName);
}
@Override
public String getFieldName() {
return fieldName;
}
@Override
public String getItemFieldName() {
return itemFieldName;
}
@Override
public Class<?> getItemType() {
return itemType;
}
@Override
public String getKeyFieldName() {
return keyFieldName;
}
}
private static class NamedItemType {
Class<?> itemType;
String itemFieldName;
NamedItemType(final Class<?> itemType, final String itemFieldName) {
this.itemType = itemType == null ? Object.class : itemType;
this.itemFieldName = itemFieldName;
}
@Override
public boolean equals(final Object obj) {
if (obj instanceof NamedItemType) {
final NamedItemType b = (NamedItemType)obj;
return itemType.equals(b.itemType) && isEquals(itemFieldName, b.itemFieldName);
} else {
return false;
}
}
private static boolean isEquals(final Object a, final Object b) {
if (a == null) {
return b == null;
} else {
return a.equals(b);
}
}
@Override
public int hashCode() {
int hash = itemType.hashCode() << 7;
if (itemFieldName != null) {
hash += itemFieldName.hashCode();
}
return hash;
}
}
}