1 /***
2 * Copyright 2003-2010 Terracotta, 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 net.sf.ehcache.pool.sizeof;
18
19 import java.lang.ref.SoftReference;
20 import java.lang.reflect.Array;
21 import java.lang.reflect.Field;
22 import java.lang.reflect.Modifier;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.IdentityHashMap;
26 import java.util.Stack;
27 import java.util.concurrent.ConcurrentHashMap;
28 import java.util.concurrent.ConcurrentMap;
29
30 import net.sf.ehcache.pool.sizeof.filter.SizeOfFilter;
31
32 /***
33 * This will walk an object graph and let you execute some "function" along the way
34 * @author Alex Snaps
35 */
36 final class ObjectGraphWalker {
37
38
39 private final ConcurrentMap<Class<?>, SoftReference<Collection<Field>>> fieldCache =
40 new ConcurrentHashMap<Class<?>, SoftReference<Collection<Field>>>();
41 private final ConcurrentMap<Class<?>, Boolean> classCache =
42 new ConcurrentHashMap<Class<?>, Boolean>();
43
44 private final SizeOfFilter sizeOfFilter;
45
46 private final Visitor visitor;
47
48 /***
49 * The visitor to execute the function on each node of the graph
50 * This is only to be used for the sizing of an object graph in memory!
51 */
52 static interface Visitor {
53 /***
54 * The visit method executed on each node
55 * @param object the reference at that node
56 * @return a long for you to do things with...
57 */
58 public long visit(Object object);
59 }
60
61
62 /***
63 * Constructor
64 * @param visitor the visitor to use
65 * @param filter the filtering
66 * @see Visitor
67 * @see SizeOfFilter
68 */
69 ObjectGraphWalker(Visitor visitor, SizeOfFilter filter) {
70 this.visitor = visitor;
71 this.sizeOfFilter = filter;
72 }
73
74 /***
75 * Walk the graph and call into the "visitor"
76 * @param root the roots of the objects (a shared graph will only be visited once)
77 * @return the sum of all Visitor#visit returned values
78 */
79 long walk(Object... root) {
80
81 long result = 0;
82
83 Stack<Object> toVisit = new Stack<Object>();
84 IdentityHashMap<Object, Object> visited = new IdentityHashMap<Object, Object>();
85
86 if (root != null) {
87 for (Object object : root) {
88 nullSafeAdd(toVisit, object);
89 }
90 }
91
92 while (!toVisit.isEmpty()) {
93
94 Object ref = toVisit.pop();
95
96 if (visited.containsKey(ref)) {
97 continue;
98 }
99
100 Class<?> refClass = ref.getClass();
101 if (shouldWalkClass(refClass)) {
102 if (refClass.isArray() && !refClass.getComponentType().isPrimitive()) {
103 for (int i = 0; i < Array.getLength(ref); i++) {
104 nullSafeAdd(toVisit, Array.get(ref, i));
105 }
106 } else {
107 for (Field field : getFilteredFields(refClass)) {
108 try {
109 nullSafeAdd(toVisit, field.get(ref));
110 } catch (IllegalAccessException ex) {
111 throw new RuntimeException(ex);
112 }
113 }
114 }
115
116 result += visitor.visit(ref);
117 }
118 visited.put(ref, null);
119 }
120
121 return result;
122 }
123
124 /***
125 * Returns the filtered fields for a particular type
126 * @param refClass the type
127 * @return A collection of fields to be visited
128 */
129 private Collection<Field> getFilteredFields(Class<?> refClass) {
130 SoftReference<Collection<Field>> ref = fieldCache.get(refClass);
131 Collection<Field> fieldList = ref != null ? ref.get() : null;
132 if (fieldList != null) {
133 return fieldList;
134 } else {
135 Collection<Field> result = sizeOfFilter.filterFields(refClass, getAllFields(refClass));
136 fieldCache.put(refClass, new SoftReference<Collection<Field>>(result));
137 return result;
138 }
139 }
140
141 private boolean shouldWalkClass(Class<?> refClass) {
142 Boolean cached = classCache.get(refClass);
143 if (cached == null) {
144 cached = sizeOfFilter.filterClass(refClass);
145 classCache.put(refClass, cached);
146 }
147 return cached.booleanValue();
148 }
149
150 private static void nullSafeAdd(final Stack<Object> toVisit, final Object o) {
151 if (o != null) {
152 toVisit.push(o);
153 }
154 }
155
156 /***
157 * Returns all non-primitive fields for the entire class hierarchy of a type
158 * @param refClass the type
159 * @return all fields for that type
160 */
161 private static Collection<Field> getAllFields(Class<?> refClass) {
162 Collection<Field> fields = new ArrayList<Field>();
163 for (Class<?> klazz = refClass; klazz != null; klazz = klazz.getSuperclass()) {
164 for (Field field : klazz.getDeclaredFields()) {
165 if (!Modifier.isStatic(field.getModifiers()) && !field.getType().isPrimitive()) {
166 field.setAccessible(true);
167 fields.add(field);
168 }
169 }
170 }
171 return fields;
172 }
173 }