/*
* Copyright (C) 2004, 2005, 2006 Joe Walnes.
* Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 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 14. May 2004 by Joe Walnes
*/
package com.thoughtworks.xstream.converters.reflection;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.thoughtworks.xstream.core.Caching;
import com.thoughtworks.xstream.core.JVM;
import com.thoughtworks.xstream.core.util.OrderRetainingMap;
/**
* A field dictionary instance caches information about classes fields.
*
* @author Joe Walnes
* @author Jörg Schaible
* @author Guilherme Silveira
*/
public class FieldDictionary implements Caching {
private transient Map keyedByFieldNameCache;
private transient Map keyedByFieldKeyCache;
private final FieldKeySorter sorter;
public FieldDictionary() {
this(new ImmutableFieldKeySorter());
}
public FieldDictionary(FieldKeySorter sorter) {
this.sorter = sorter;
init();
}
private void init() {
keyedByFieldNameCache = new HashMap();
keyedByFieldKeyCache = new HashMap();
keyedByFieldNameCache.put(Object.class, Collections.EMPTY_MAP);
keyedByFieldKeyCache.put(Object.class, Collections.EMPTY_MAP);
}
/**
* Returns an iterator for all fields for some class
*
* @param cls the class you are interested on
* @return an iterator for its fields
* @deprecated As of 1.3, use {@link #fieldsFor(Class)} instead
*/
public Iterator serializableFieldsFor(Class cls) {
return fieldsFor(cls);
}
/**
* Returns an iterator for all fields for some class
*
* @param cls the class you are interested on
* @return an iterator for its fields
*/
public Iterator fieldsFor(final Class cls) {
return buildMap(cls, true).values().iterator();
}
/**
* Returns an specific field of some class. If definedIn is null, it searches for the field
* named 'name' inside the class cls. If definedIn is different than null, tries to find the
* specified field name in the specified class cls which should be defined in class
* definedIn (either equals cls or a one of it's superclasses)
*
* @param cls the class where the field is to be searched
* @param name the field name
* @param definedIn the superclass (or the class itself) of cls where the field was defined
* @return the field itself
* @throws ObjectAccessException if no field can be found
*/
public Field field(Class cls, String name, Class definedIn) {
Field field = fieldOrNull(cls, name, definedIn);
if (field == null) {
throw new ObjectAccessException("No such field " + cls.getName() + "." + name);
} else {
return field;
}
}
/**
* Returns an specific field of some class. If definedIn is null, it searches for the field
* named 'name' inside the class cls. If definedIn is different than null, tries to find the
* specified field name in the specified class cls which should be defined in class
* definedIn (either equals cls or a one of it's superclasses)
*
* @param cls the class where the field is to be searched
* @param name the field name
* @param definedIn the superclass (or the class itself) of cls where the field was defined
* @return the field itself or <code>null</code>
* @since 1.4
*/
public Field fieldOrNull(Class cls, String name, Class definedIn) {
Map fields = buildMap(cls, definedIn != null);
Field field = (Field)fields.get(definedIn != null
? (Object)new FieldKey(name, definedIn, 0)
: (Object)name);
return field;
}
private Map buildMap(final Class type, boolean tupleKeyed) {
final Map result;
Class cls = type;
synchronized (this) {
if (!keyedByFieldNameCache.containsKey(type)) {
final List superClasses = new ArrayList();
while (!Object.class.equals(cls)) {
superClasses.add(0, cls);
cls = cls.getSuperclass();
}
Map lastKeyedByFieldName = Collections.EMPTY_MAP;
Map lastKeyedByFieldKey = Collections.EMPTY_MAP;
for (final Iterator iter = superClasses.iterator(); iter.hasNext();) {
cls = (Class)iter.next();
if (!keyedByFieldNameCache.containsKey(cls)) {
final Map keyedByFieldName = new HashMap(lastKeyedByFieldName);
final Map keyedByFieldKey = new OrderRetainingMap(lastKeyedByFieldKey);
Field[] fields = cls.getDeclaredFields();
if (JVM.reverseFieldDefinition()) {
for (int i = fields.length >> 1; i-- > 0;) {
final int idx = fields.length - i - 1;
final Field field = fields[i];
fields[i] = fields[idx];
fields[idx] = field;
}
}
for (int i = 0; i < fields.length; i++ ) {
Field field = fields[i];
if (!field.isAccessible()) {
field.setAccessible(true);
}
FieldKey fieldKey = new FieldKey(
field.getName(), field.getDeclaringClass(), i);
Field existent = (Field)keyedByFieldName.get(field.getName());
if (existent == null
// do overwrite statics
|| ((existent.getModifiers() & Modifier.STATIC) != 0)
// overwrite non-statics with non-statics only
|| (existent != null && ((field.getModifiers() & Modifier.STATIC) == 0))) {
keyedByFieldName.put(field.getName(), field);
}
keyedByFieldKey.put(fieldKey, field);
}
final Map sortedFieldKeys = sorter.sort(type, keyedByFieldKey);
keyedByFieldNameCache.put(cls, keyedByFieldName);
keyedByFieldKeyCache.put(cls, sortedFieldKeys);
lastKeyedByFieldName = keyedByFieldName;
lastKeyedByFieldKey = sortedFieldKeys;
} else {
lastKeyedByFieldName = (Map)keyedByFieldNameCache.get(cls);
lastKeyedByFieldKey = (Map)keyedByFieldKeyCache.get(cls);
}
}
result = tupleKeyed ? lastKeyedByFieldKey : lastKeyedByFieldName;
} else {
result = (Map)(tupleKeyed
? keyedByFieldKeyCache.get(type)
: keyedByFieldNameCache.get(type));
}
}
return result;
}
public synchronized void flushCache() {
Set objectTypeSet = Collections.singleton(Object.class);
keyedByFieldNameCache.keySet().retainAll(objectTypeSet);
keyedByFieldKeyCache.keySet().retainAll(objectTypeSet);
if (sorter instanceof Caching) {
((Caching)sorter).flushCache();
}
}
protected Object readResolve() {
init();
return this;
}
}