-
Notifications
You must be signed in to change notification settings - Fork 107
/
Traverser.java
230 lines (207 loc) · 7.02 KB
/
Traverser.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
package com.cedarsoftware.util;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Deque;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.Map;
/**
* Java Object Graph traverser. It will visit all Java object
* reference fields and call the passed in Visitor instance with
* each object encountered, including the root. It will properly
* detect cycles within the graph and not hang.
*
* @author John DeRegnaucourt (jdereg@gmail.com)
* <br>
* Copyright (c) Cedar Software LLC
* <br><br>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <br><br>
* <a href="http://www.apache.org/licenses/LICENSE-2.0">License</a>
* <br><br>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@SuppressWarnings("unchecked")
public class Traverser
{
public interface Visitor
{
void process(Object o);
}
private final Map<Object, Object> _objVisited = new IdentityHashMap<>();
protected final Map<Class, ClassInfo> _classCache = new HashMap<>();
/**
* @param o Any Java Object
* @param visitor Visitor is called for every object encountered during
* the Java object graph traversal.
*/
public static void traverse(Object o, Visitor visitor)
{
traverse(o, null, visitor);
}
/**
* @param o Any Java Object
* @param skip String[] of class names to not include in the tally
* @param visitor Visitor is called for every object encountered during
* the Java object graph traversal.
*/
public static void traverse(Object o, Class<?>[] skip, Visitor visitor)
{
Traverser traverse = new Traverser();
traverse.walk(o, skip, visitor);
traverse._objVisited.clear();
traverse._classCache.clear();
}
/**
* Traverse the object graph referenced by the passed in root.
* @param root Any Java object.
* @param skip Set of classes to skip (ignore). Allowed to be null.
*/
public void walk(Object root, Class<?>[] skip, Visitor visitor)
{
Deque stack = new LinkedList();
stack.add(root);
while (!stack.isEmpty())
{
Object current = stack.removeFirst();
if (current == null || _objVisited.containsKey(current))
{
continue;
}
final Class clazz = current.getClass();
ClassInfo classInfo = getClassInfo(clazz, skip);
if (classInfo._skip)
{ // Do not process any classes that are assignableFrom the skip classes list.
continue;
}
_objVisited.put(current, null);
visitor.process(current);
if (clazz.isArray())
{
final int len = Array.getLength(current);
Class compType = clazz.getComponentType();
if (!compType.isPrimitive())
{ // Speed up: do not walk primitives
ClassInfo info = getClassInfo(compType, skip);
if (!info._skip)
{ // Do not walk array elements of a class type that is to be skipped.
for (int i=0; i < len; i++)
{
Object element = Array.get(current, i);
if (element != null)
{ // Skip processing null array elements
stack.add(Array.get(current, i));
}
}
}
}
}
else
{ // Process fields of an object instance
if (current instanceof Collection)
{
walkCollection(stack, (Collection) current);
}
else if (current instanceof Map)
{
walkMap(stack, (Map) current);
}
else
{
walkFields(stack, current, skip);
}
}
}
}
private void walkFields(Deque stack, Object current, Class<?>[] skip)
{
ClassInfo classInfo = getClassInfo(current.getClass(), skip);
for (Field field : classInfo._refFields)
{
try
{
Object value = field.get(current);
if (value == null || value.getClass().isPrimitive())
{
continue;
}
stack.add(value);
}
catch (IllegalAccessException ignored) { }
}
}
private static void walkCollection(Deque stack, Collection<?> col)
{
for (Object o : col)
{
if (o != null && !o.getClass().isPrimitive())
{
stack.add(o);
}
}
}
private static void walkMap(Deque stack, Map<?, ?> map)
{
for (Map.Entry entry : map.entrySet())
{
Object o = entry.getKey();
if (o != null && !o.getClass().isPrimitive())
{
stack.add(entry.getKey());
stack.add(entry.getValue());
}
}
}
private ClassInfo getClassInfo(Class<?> current, Class<?>[] skip)
{
ClassInfo classCache = _classCache.get(current);
if (classCache != null)
{
return classCache;
}
classCache = new ClassInfo(current, skip);
_classCache.put(current, classCache);
return classCache;
}
/**
* This class wraps a class in order to cache the fields so they
* are only reflectively obtained once.
*/
public static class ClassInfo
{
private boolean _skip = false;
private final Collection<Field> _refFields = new ArrayList<>();
public ClassInfo(Class<?> c, Class<?>[] skip)
{
if (skip != null)
{
for (Class<?> klass : skip)
{
if (klass.isAssignableFrom(c))
{
_skip = true;
return;
}
}
}
Collection<Field> fields = ReflectionUtils.getDeepDeclaredFields(c);
for (Field field : fields)
{
Class<?> fc = field.getType();
if (!fc.isPrimitive())
{
_refFields.add(field);
}
}
}
}
}