• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.caliper.memory;
18 
19 import com.google.common.base.Function;
20 import com.google.common.base.Predicate;
21 import com.google.common.collect.Lists;
22 
23 import java.lang.reflect.AccessibleObject;
24 import java.lang.reflect.Array;
25 import java.lang.reflect.Field;
26 import java.lang.reflect.Modifier;
27 import java.util.ArrayDeque;
28 import java.util.Collections;
29 import java.util.Deque;
30 import java.util.EnumSet;
31 import java.util.IdentityHashMap;
32 import java.util.List;
33 import java.util.Set;
34 import java.util.concurrent.ConcurrentHashMap;
35 
36 import javax.annotation.Nonnull;
37 
38 /**
39  * A depth-first object graph explorer. The traversal starts at a root (an
40  * {@code Object}) and explores any other reachable object (recursively) or
41  * primitive value, excluding static fields from the traversal. The traversal
42  * is controlled by a user-supplied {@link ObjectVisitor}, which decides for
43  * each explored path whether to continue exploration of that path, and it can
44  * also return a value at the end of the traversal.
45  */
46 public final class ObjectExplorer {
ObjectExplorer()47   private ObjectExplorer() { }
48 
49   /**
50    * Explores an object graph (defined by a root object and whatever is
51    * reachable through it, following non-static fields) while using an
52    * {@link ObjectVisitor} to both control the traversal and return a value.
53    *
54    * <p>Equivalent to {@code exploreObject(rootObject, visitor,
55    * EnumSet.noneOf(Feature.class))}.
56    *
57    * @param <T> the type of the value obtained (after the traversal) by the
58    * ObjectVisitor
59    * @param rootObject an object to be recursively explored
60    * @param visitor a visitor that is notified for each explored path and
61    * decides whether to continue exploration of that path, and constructs a
62    * return value at the end of the exploration
63    * @return whatever value is returned by the visitor at the end of the
64    * traversal
65    * @see ObjectVisitor
66    */
exploreObject(Object rootObject, ObjectVisitor<T> visitor)67   public static <T> T exploreObject(Object rootObject, ObjectVisitor<T> visitor) {
68     return exploreObject(rootObject, visitor, EnumSet.noneOf(Feature.class));
69   }
70 
71   /**
72    * Explores an object graph (defined by a root object and whatever is
73    * reachable through it, following non-static fields) while using an
74    * {@link ObjectVisitor} to both control the traversal and return a value.
75    *
76    * <p>The {@code features} further customizes the exploration behavior.
77    * In particular:
78    * <ul>
79    * <li>If {@link Feature#VISIT_PRIMITIVES} is contained in features,
80    * the visitor will also be notified about exploration of primitive values.
81    * <li>If {@link Feature#VISIT_NULL} is contained in features, the visitor
82    * will also be notified about exploration of {@code null} values.
83    * </ul>
84    * In both cases above, the return value of
85    * {@link ObjectVisitor#visit(Chain)} is ignored, since neither primitive
86    * values or {@code null} can be further explored.
87    *
88    * @param <T> the type of the value obtained (after the traversal) by the
89    * ObjectVisitor
90    * @param rootObject an object to be recursively explored
91    * @param visitor a visitor that is notified for each explored path
92    * and decides whether to continue exploration of that path, and constructs
93    * a return value at the end of the exploration
94    * @param features a set of desired features that the object exploration should have
95    * @return whatever value is returned by the visitor at the end of the traversal
96    * @see ObjectVisitor
97    */
exploreObject(Object rootObject, ObjectVisitor<T> visitor, EnumSet<Feature> features)98   public static <T> T exploreObject(Object rootObject,
99       ObjectVisitor<T> visitor, EnumSet<Feature> features) {
100     Deque<Chain> stack = new ArrayDeque<Chain>(32);
101     if (rootObject != null) stack.push(Chain.root(rootObject));
102 
103     while (!stack.isEmpty()) {
104       Chain chain = stack.pop();
105       //the only place where the return value of visit() is considered
106       ObjectVisitor.Traversal traversal = visitor.visit(chain);
107       switch (traversal) {
108         case SKIP: continue;
109         case EXPLORE: break;
110         default: throw new AssertionError();
111       }
112 
113       //only nonnull values pushed in the stack
114       @Nonnull Object value = chain.getValue();
115       Class<?> valueClass = value.getClass();
116       if (valueClass.isArray()) {
117         boolean isPrimitive = valueClass.getComponentType().isPrimitive();
118         /*
119          * Since we push paths to explore in a stack, we push references found in the array in
120          * reverse order, so when we pop them, they will be in the array's order.
121          */
122         for (int i = Array.getLength(value) - 1; i >= 0; i--) {
123           Object childValue = Array.get(value, i);
124           if (isPrimitive) {
125             if (features.contains(Feature.VISIT_PRIMITIVES)) {
126               visitor.visit(chain.appendArrayIndex(i, childValue));
127             }
128           } else if (childValue == null) {
129             if (features.contains(Feature.VISIT_NULL)) {
130               visitor.visit(chain.appendArrayIndex(i, childValue));
131             }
132           } else {
133             stack.push(chain.appendArrayIndex(i, childValue));
134           }
135         }
136       } else {
137         /*
138          * Reflection usually provides fields in declaration order. As above in arrays, we push
139          * them to the stack in reverse order, so when we pop them, we get them in the original
140          * (declaration) order.
141          */
142         final Field[] fields = getAllFields(value);
143         for (int j = fields.length - 1; j >= 0; j--) {
144           final Field field = fields[j];
145           Object childValue = null;
146           try {
147             childValue = field.get(value);
148           } catch (Exception e) {
149             throw new AssertionError(e);
150           }
151           if (childValue == null) { // handling nulls
152             if (features.contains(Feature.VISIT_NULL)) {
153               visitor.visit(chain.appendField(field, childValue));
154             }
155           } else { // handling primitives or references
156             boolean isPrimitive = field.getType().isPrimitive();
157             Chain extendedChain = chain.appendField(field, childValue);
158             if (isPrimitive) {
159               if (features.contains(Feature.VISIT_PRIMITIVES)) {
160                 visitor.visit(extendedChain);
161               }
162             } else {
163               stack.push(extendedChain);
164             }
165           }
166         }
167       }
168     }
169     return visitor.result();
170   }
171 
172   /**
173    * A stateful predicate that allows exploring an object (the tail of the chain) only once.
174    */
175   static class AtMostOncePredicate implements Predicate<Chain> {
176     private final Set<Object> seen = Collections.newSetFromMap(
177         new IdentityHashMap<Object, Boolean>());
178 
apply(Chain chain)179     @Override public boolean apply(Chain chain) {
180       return seen.add(chain.getValue());
181     }
182   }
183 
184   static final Predicate<Chain> notEnumFieldsOrClasses = new Predicate<Chain>() {
185     @Override public boolean apply(Chain chain) {
186       return !(Enum.class.isAssignableFrom(chain.getValueType())
187           || chain.getValue() instanceof Class<?>);
188     }
189   };
190 
191   static final Function<Chain, Object> chainToObject =
192       new Function<Chain, Object>() {
193     @Override public Object apply(Chain chain) {
194       return chain.getValue();
195     }
196   };
197 
198   /**
199    * A cache of {@code Field}s that are accessible for a given {@code Class<?>}.
200    */
201   private static final ConcurrentHashMap<Class<?>, Field[]> clazzFields =
202       new ConcurrentHashMap<Class<?>, Field[]>();
203 
getAllFields(Object o)204   private static Field[] getAllFields(Object o) {
205     Class<?> clazz = o.getClass();
206     return getAllFields(clazz);
207   }
208 
209   /**
210    * Keep a cache of fields per class of interest.
211    *
212    * @param clazz - the {@code Class<?>} to interrogate.
213    * @return An array of fields of the given class.
214    */
getAllFields(Class<?> clazz)215   private static Field[] getAllFields(Class<?> clazz) {
216     Field[] f = clazzFields.get(clazz);
217     if (f == null) {
218       f = computeAllFields(clazz);
219       Field[] u = clazzFields.putIfAbsent(clazz, f);
220       return u == null ? f : u;
221     }
222     return f;
223   }
224 
computeAllFields(Class<?> clazz)225   private static Field[] computeAllFields(Class<?> clazz) {
226     List<Field> fields = Lists.newArrayListWithCapacity(8);
227     while (clazz != null) {
228       for (Field field : clazz.getDeclaredFields()) {
229         // add only non-static fields
230         if (!Modifier.isStatic(field.getModifiers())) {
231           fields.add(field);
232         }
233       }
234       clazz = clazz.getSuperclass();
235     }
236 
237     // all together so there is only one security check
238     Field[] afields = fields.toArray(new Field[fields.size()]);
239     AccessibleObject.setAccessible(afields, true);
240     return afields;
241   }
242 
243   /**
244    * Enumeration of features that may be optionally requested for an object
245    * traversal.
246    *
247    * @see ObjectExplorer#exploreObject(Object, ObjectVisitor, EnumSet)
248    */
249   public enum Feature {
250     /**
251      * Null references should be visited.
252      */
253     VISIT_NULL,
254 
255     /**
256      * Primitive values should be visited.
257      */
258     VISIT_PRIMITIVES
259   }
260 }
261