View Javadoc

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      // Todo this is probably not what we want...
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 }