1 /*
2  * Copyright (C) 2004, 2005, 2006 Joe Walnes.
3  * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2018 XStream Committers.
4  * All rights reserved.
5  * The software in this package is published under the terms of the BSD
6  * style license a copy of which has been included with this distribution in
7  * the LICENSE.txt file.
8  * Created on 14. May 2004 by Joe Walnes
9  */

10 package com.thoughtworks.xstream.converters.reflection;
11
12 import java.lang.reflect.Field;
13 import java.lang.reflect.Modifier;
14 import java.util.Collections;
15 import java.util.HashMap;
16 import java.util.Iterator;
17 import java.util.LinkedList;
18 import java.util.Map;
19
20 import com.thoughtworks.xstream.core.Caching;
21 import com.thoughtworks.xstream.core.JVM;
22 import com.thoughtworks.xstream.core.util.OrderRetainingMap;
23
24
25 /**
26  * A field dictionary instance caches information about classes fields.
27  *
28  * @author Joe Walnes
29  * @author Jörg Schaible
30  * @author Guilherme Silveira
31  */

32 public class FieldDictionary implements Caching {
33
34     private static final DictionaryEntry OBJECT_DICTIONARY_ENTRY = new DictionaryEntry(Collections.EMPTY_MAP,
35         Collections.EMPTY_MAP);
36
37     private transient Map dictionaryEntries;
38     private transient FieldUtil fieldUtil;
39     private final FieldKeySorter sorter;
40
41     public FieldDictionary() {
42         this(new ImmutableFieldKeySorter());
43     }
44
45     public FieldDictionary(final FieldKeySorter sorter) {
46         this.sorter = sorter;
47         init();
48     }
49
50     private void init() {
51         dictionaryEntries = new HashMap();
52         if (JVM.is15())
53             try {
54                 fieldUtil = (FieldUtil)JVM.loadClassForName("com.thoughtworks.xstream.converters.reflection.FieldUtil15"true).newInstance();
55             } catch (Exception e) {
56                 ;
57             }
58         if (fieldUtil == null)
59             fieldUtil = new FieldUtil14();
60     }
61
62     /**
63      * Returns an iterator for all fields for some class
64      *
65      * @param cls the class you are interested on
66      * @return an iterator for its fields
67      * @deprecated As of 1.3, use {@link #fieldsFor(Class)} instead
68      */

69     public Iterator serializableFieldsFor(final Class cls) {
70         return fieldsFor(cls);
71     }
72
73     /**
74      * Returns an iterator for all fields for some class
75      *
76      * @param cls the class you are interested on
77      * @return an iterator for its fields
78      */

79     public Iterator fieldsFor(final Class cls) {
80         return buildMap(cls, true).values().iterator();
81     }
82
83     /**
84      * Returns an specific field of some class. If definedIn is null, it searches for the field named 'name' inside the
85      * class cls. If definedIn is different than null, tries to find the specified field name in the specified class cls
86      * which should be defined in class definedIn (either equals cls or a one of it's superclasses)
87      *
88      * @param cls the class where the field is to be searched
89      * @param name the field name
90      * @param definedIn the superclass (or the class itself) of cls where the field was defined
91      * @return the field itself
92      * @throws ObjectAccessException if no field can be found
93      */

94     public Field field(final Class cls, final String name, final Class definedIn) {
95         final Field field = fieldOrNull(cls, name, definedIn);
96         if (field == null) {
97             throw new MissingFieldException(cls.getName(), name);
98         } else {
99             return field;
100         }
101     }
102
103     /**
104      * Returns an specific field of some class. If definedIn is null, it searches for the field named 'name' inside the
105      * class cls. If definedIn is different than null, tries to find the specified field name in the specified class cls
106      * which should be defined in class definedIn (either equals cls or a one of it's superclasses)
107      *
108      * @param cls the class where the field is to be searched
109      * @param name the field name
110      * @param definedIn the superclass (or the class itself) of cls where the field was defined
111      * @return the field itself or <code>null</code>
112      * @since 1.4
113      */

114     public Field fieldOrNull(final Class cls, final String name, final Class definedIn) {
115         final Map fields = buildMap(cls, definedIn != null);
116         final Field field = (Field)fields.get(definedIn != null
117             ? (Object)new FieldKey(name, definedIn, -1)
118             : (Object)name);
119         return field;
120     }
121
122     private Map buildMap(final Class type, final boolean tupleKeyed) {
123
124         Class cls = type;
125
126         DictionaryEntry lastDictionaryEntry = null;
127         final LinkedList superClasses = new LinkedList();
128         while (lastDictionaryEntry == null) {
129             if (Object.class.equals(cls) || cls == null) {
130                 lastDictionaryEntry = OBJECT_DICTIONARY_ENTRY;
131             } else {
132                 lastDictionaryEntry = getDictionaryEntry(cls);
133             }
134             if (lastDictionaryEntry == null) {
135                 superClasses.addFirst(cls);
136                 cls = cls.getSuperclass();
137             }
138         }
139
140         for (final Iterator iter = superClasses.iterator(); iter.hasNext();) {
141             cls = (Class)iter.next();
142             DictionaryEntry newDictionaryEntry = buildDictionaryEntryForClass(cls, lastDictionaryEntry);
143             synchronized (this) {
144                 final DictionaryEntry concurrentEntry = getDictionaryEntry(cls);
145                 if (concurrentEntry == null) {
146                     dictionaryEntries.put(cls, newDictionaryEntry);
147                 } else {
148                     newDictionaryEntry = concurrentEntry;
149                 }
150             }
151             lastDictionaryEntry = newDictionaryEntry;
152         }
153
154         return tupleKeyed ? lastDictionaryEntry.getKeyedByFieldKey() : lastDictionaryEntry.getKeyedByFieldName();
155
156     }
157
158     private DictionaryEntry buildDictionaryEntryForClass(final Class cls, final DictionaryEntry lastDictionaryEntry) {
159         final Map keyedByFieldName = new HashMap(lastDictionaryEntry.getKeyedByFieldName());
160         final Map keyedByFieldKey = new OrderRetainingMap(lastDictionaryEntry.getKeyedByFieldKey());
161         final Field[] fields = cls.getDeclaredFields();
162         if (JVM.reverseFieldDefinition()) {
163             for (int i = fields.length >> 1; i-- > 0;) {
164                 final int idx = fields.length - i - 1;
165                 final Field field = fields[i];
166                 fields[i] = fields[idx];
167                 fields[idx] = field;
168             }
169         }
170         for (int i = 0; i < fields.length; i++) {
171             final Field field = fields[i];
172             if (fieldUtil.isSynthetic(field) && field.getName().startsWith("$jacoco")) {
173                 continue;
174             }
175             if (!field.isAccessible()) {
176                 field.setAccessible(true);
177             }
178             final FieldKey fieldKey = new FieldKey(field.getName(), field.getDeclaringClass(), i);
179             final Field existent = (Field)keyedByFieldName.get(field.getName());
180             if (existent == null
181                     // do overwrite statics
182                     || (existent.getModifiers() & Modifier.STATIC) != 0
183                     // overwrite non-statics with non-statics only
184                     || (existent != null && (field.getModifiers() & Modifier.STATIC) == 0)) {
185                 keyedByFieldName.put(field.getName(), field);
186             }
187             keyedByFieldKey.put(fieldKey, field);
188         }
189         final Map sortedFieldKeys = sorter.sort(cls, keyedByFieldKey);
190         return new DictionaryEntry(keyedByFieldName, sortedFieldKeys);
191     }
192
193     private synchronized DictionaryEntry getDictionaryEntry(final Class cls) {
194         return (DictionaryEntry)dictionaryEntries.get(cls);
195     }
196
197     public synchronized void flushCache() {
198         dictionaryEntries.clear();
199         if (sorter instanceof Caching) {
200             ((Caching)sorter).flushCache();
201         }
202     }
203
204     protected Object readResolve() {
205         init();
206         return this;
207     }
208
209     interface FieldUtil {
210         boolean isSynthetic(Field field);
211     }
212
213     private static final class DictionaryEntry {
214
215         private final Map keyedByFieldName;
216         private final Map keyedByFieldKey;
217
218         public DictionaryEntry(final Map keyedByFieldName, final Map keyedByFieldKey) {
219             super();
220             this.keyedByFieldName = keyedByFieldName;
221             this.keyedByFieldKey = keyedByFieldKey;
222         }
223
224         public Map getKeyedByFieldName() {
225             return keyedByFieldName;
226         }
227
228         public Map getKeyedByFieldKey() {
229             return keyedByFieldKey;
230         }
231     }
232 }
233