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