Mercurial > hg > truffle
comparison graal/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/NodeUtil.java @ 9754:98b004bf3985
Refactoring of NodeUtil to centralize the use of reflection
author | Christian Wimmer <christian.wimmer@oracle.com> |
---|---|
date | Thu, 16 May 2013 17:03:18 -0700 |
parents | e6fe35d64b71 |
children | 84b944726df2 |
comparison
equal
deleted
inserted
replaced
9753:6c2a7fc32416 | 9754:98b004bf3985 |
---|---|
35 /** | 35 /** |
36 * Utility class that manages the special access methods for node instances. | 36 * Utility class that manages the special access methods for node instances. |
37 */ | 37 */ |
38 public class NodeUtil { | 38 public class NodeUtil { |
39 | 39 |
40 /** | |
41 * Interface that allows the customization of field offsets used for {@link Unsafe} field | |
42 * accesses. | |
43 */ | |
44 public interface FieldOffsetProvider { | |
45 | |
46 long objectFieldOffset(Field field); | |
47 } | |
48 | |
49 private static final FieldOffsetProvider unsafeFieldOffsetProvider = new FieldOffsetProvider() { | |
50 | |
51 @Override | |
52 public long objectFieldOffset(Field field) { | |
53 return unsafe.objectFieldOffset(field); | |
54 } | |
55 }; | |
56 | |
57 public static enum NodeFieldKind { | |
58 /** The single {@link Node#getParent() parent} field. */ | |
59 PARENT, | |
60 /** A field annotated with {@link Child}. */ | |
61 CHILD, | |
62 /** A field annotated with {@link Children}. */ | |
63 CHILDREN, | |
64 /** A normal non-child data field of the node. */ | |
65 DATA | |
66 } | |
67 | |
68 /** | |
69 * Information about a field in a {@link Node} class. | |
70 */ | |
71 public static final class NodeField { | |
72 | |
73 private final NodeFieldKind kind; | |
74 private final Class<?> type; | |
75 private final String name; | |
76 private long offset; | |
77 | |
78 protected NodeField(NodeFieldKind kind, Class<?> type, String name, long offset) { | |
79 this.kind = kind; | |
80 this.type = type; | |
81 this.name = name; | |
82 this.offset = offset; | |
83 } | |
84 | |
85 public NodeFieldKind getKind() { | |
86 return kind; | |
87 } | |
88 | |
89 public Class<?> getType() { | |
90 return type; | |
91 } | |
92 | |
93 public String getName() { | |
94 return name; | |
95 } | |
96 | |
97 public long getOffset() { | |
98 return offset; | |
99 } | |
100 | |
101 public Object loadValue(Node node) { | |
102 if (type == boolean.class) { | |
103 return unsafe.getBoolean(node, offset); | |
104 } else if (type == byte.class) { | |
105 return unsafe.getByte(node, offset); | |
106 } else if (type == short.class) { | |
107 return unsafe.getShort(node, offset); | |
108 } else if (type == char.class) { | |
109 return unsafe.getChar(node, offset); | |
110 } else if (type == int.class) { | |
111 return unsafe.getInt(node, offset); | |
112 } else if (type == long.class) { | |
113 return unsafe.getLong(node, offset); | |
114 } else if (type == float.class) { | |
115 return unsafe.getFloat(node, offset); | |
116 } else if (type == double.class) { | |
117 return unsafe.getDouble(node, offset); | |
118 } else { | |
119 return unsafe.getObject(node, offset); | |
120 } | |
121 } | |
122 } | |
123 | |
124 /** | |
125 * Information about a {@link Node} class. A single instance of this class is allocated for | |
126 * every subclass of {@link Node} that is used. | |
127 */ | |
40 public static final class NodeClass { | 128 public static final class NodeClass { |
41 | 129 |
42 private final Class parentClass; | 130 private static final Map<Class<?>, NodeClass> nodeClasses = new IdentityHashMap<>(); |
43 private final long[] nodeFieldOffsets; | 131 |
44 private final Class[] nodeFieldClasses; | 132 // The comprehensive list of all fields. |
45 private final long[] nodeArrayFieldOffsets; | 133 private final NodeField[] fields; |
46 private final Class[] nodeArrayFieldClasses; | 134 // Separate arrays for the frequently accessed field offsets. |
47 private final long parentOffset; | 135 private final long parentOffset; |
48 private final long[] nodeDataFieldOffsets; | 136 private final long[] childOffsets; |
49 private final Class<?>[] nodeDataFieldClasses; | 137 private final long[] childrenOffsets; |
50 | 138 |
51 private static final Map<Class<?>, NodeClass> nodeClasses = new IdentityHashMap<>(); | 139 public static NodeClass get(Class<? extends Node> clazz) { |
52 | |
53 public static NodeClass get(Class<?> clazz) { | |
54 NodeClass nodeClass = nodeClasses.get(clazz); | 140 NodeClass nodeClass = nodeClasses.get(clazz); |
55 if (nodeClass == null) { | 141 if (nodeClass == null) { |
56 nodeClass = new NodeClass(clazz); | 142 nodeClass = new NodeClass(clazz, unsafeFieldOffsetProvider); |
57 nodeClasses.put(clazz, nodeClass); | 143 nodeClasses.put(clazz, nodeClass); |
58 } | 144 } |
59 return nodeClass; | 145 return nodeClass; |
60 } | 146 } |
61 | 147 |
62 private NodeClass(Class<?> clazz) { | 148 public NodeClass(Class<? extends Node> clazz, FieldOffsetProvider fieldOffsetProvider) { |
63 // scan object fields | 149 List<NodeField> fieldsList = new ArrayList<>(); |
64 Class<?> parentClassTmp = null; | 150 List<Long> parentOffsetsList = new ArrayList<>(); |
65 List<Long> nodeFieldOffsetsList = new ArrayList<>(); | 151 List<Long> childOffsetsList = new ArrayList<>(); |
66 List<Class<?>> nodeFieldClassesList = new ArrayList<>(); | 152 List<Long> childrenOffsetsList = new ArrayList<>(); |
67 List<Long> nodeArrayFieldOffsetsList = new ArrayList<>(); | 153 |
68 List<Class<?>> nodeArrayFieldClassesList = new ArrayList<>(); | 154 for (Field field : getAllFields(clazz)) { |
69 List<Long> nodeDataFieldOffsetList = new ArrayList<>(); | |
70 List<Class<?>> nodeDataFieldClassList = new ArrayList<>(); | |
71 Field[] fields = getAllFields(clazz); | |
72 long parentOffsetTemp = -1; | |
73 for (Field field : fields) { | |
74 if (Modifier.isStatic(field.getModifiers()) || field.isSynthetic()) { | 155 if (Modifier.isStatic(field.getModifiers()) || field.isSynthetic()) { |
75 continue; | 156 continue; |
76 } | 157 } |
77 | 158 |
78 // Node fields | 159 NodeFieldKind kind; |
79 if (Node.class.isAssignableFrom(field.getType()) && field.getName().equals("parent")) { | 160 if (Node.class.isAssignableFrom(field.getType()) && field.getName().equals("parent") && field.getDeclaringClass() == Node.class) { |
80 parentOffsetTemp = unsafe.objectFieldOffset(field); | 161 kind = NodeFieldKind.PARENT; |
81 parentClassTmp = field.getType(); | 162 parentOffsetsList.add(fieldOffsetProvider.objectFieldOffset(field)); |
82 } else if (Node.class.isAssignableFrom(field.getType()) && field.getAnnotation(Child.class) != null) { | 163 } else if (Node.class.isAssignableFrom(field.getType()) && field.getAnnotation(Child.class) != null) { |
83 nodeFieldOffsetsList.add(unsafe.objectFieldOffset(field)); | 164 kind = NodeFieldKind.CHILD; |
84 nodeFieldClassesList.add(field.getType()); | 165 childOffsetsList.add(fieldOffsetProvider.objectFieldOffset(field)); |
85 } else if (field.getType().getComponentType() != null && Node.class.isAssignableFrom(field.getType().getComponentType()) && field.getAnnotation(Children.class) != null) { | 166 } else if (field.getType().isArray() && Node.class.isAssignableFrom(field.getType().getComponentType()) && field.getAnnotation(Children.class) != null) { |
86 nodeArrayFieldOffsetsList.add(unsafe.objectFieldOffset(field)); | 167 kind = NodeFieldKind.CHILDREN; |
87 nodeArrayFieldClassesList.add(field.getType()); | 168 childrenOffsetsList.add(fieldOffsetProvider.objectFieldOffset(field)); |
88 } else { | 169 } else { |
89 nodeDataFieldOffsetList.add(unsafe.objectFieldOffset(field)); | 170 kind = NodeFieldKind.DATA; |
90 nodeDataFieldClassList.add(field.getType()); | 171 } |
91 } | 172 fieldsList.add(new NodeField(kind, field.getType(), field.getName(), fieldOffsetProvider.objectFieldOffset(field))); |
92 } | 173 } |
93 this.parentClass = parentClassTmp; | 174 this.fields = fieldsList.toArray(new NodeField[fieldsList.size()]); |
94 this.nodeFieldOffsets = toLongArray(nodeFieldOffsetsList); | 175 assert parentOffsetsList.size() == 1 : "must have exactly one parent field"; |
95 this.nodeFieldClasses = nodeFieldClassesList.toArray(new Class[nodeFieldClassesList.size()]); | 176 this.parentOffset = parentOffsetsList.get(0); |
96 this.nodeArrayFieldOffsets = toLongArray(nodeArrayFieldOffsetsList); | 177 this.childOffsets = toLongArray(childOffsetsList); |
97 this.nodeArrayFieldClasses = nodeArrayFieldClassesList.toArray(new Class[nodeArrayFieldClassesList.size()]); | 178 this.childrenOffsets = toLongArray(childrenOffsetsList); |
98 this.nodeDataFieldOffsets = toLongArray(nodeDataFieldOffsetList); | 179 } |
99 this.nodeDataFieldClasses = nodeDataFieldClassList.toArray(new Class<?>[nodeDataFieldClassList.size()]); | 180 |
100 | 181 public NodeField[] getFields() { |
101 this.parentOffset = parentOffsetTemp; | 182 return fields; |
102 } | 183 } |
103 | 184 |
104 public long getParentOffset() { | 185 public long getParentOffset() { |
105 return parentOffset; | 186 return parentOffset; |
106 } | 187 } |
107 | 188 |
108 public long[] getNodeFieldOffsets() { | 189 public long[] getChildOffsets() { |
109 return nodeFieldOffsets; | 190 return childOffsets; |
110 } | 191 } |
111 | 192 |
112 public long[] getNodeArrayFieldOffsets() { | 193 public long[] getChildrenOffsets() { |
113 return nodeArrayFieldOffsets; | 194 return childrenOffsets; |
114 } | 195 } |
115 } | 196 } |
116 | 197 |
117 public static class NodeIterator implements Iterator<Node> { | 198 static class NodeIterator implements Iterator<Node> { |
118 | 199 |
119 private final Node node; | 200 private final Node node; |
120 private final NodeClass nodeClass; | 201 private final NodeClass nodeClass; |
121 private final int childrenCount; | 202 private final int childrenCount; |
122 private int index; | 203 private int index; |
123 | 204 |
124 public NodeIterator(Node node) { | 205 protected NodeIterator(Node node) { |
125 this(node, 0); | |
126 } | |
127 | |
128 public NodeIterator(Node node, int index) { | |
129 this.node = node; | 206 this.node = node; |
130 this.index = index; | 207 this.index = 0; |
131 this.nodeClass = NodeClass.get(node.getClass()); | 208 this.nodeClass = NodeClass.get(node.getClass()); |
132 this.childrenCount = childrenCount(); | 209 this.childrenCount = childrenCount(); |
133 } | 210 } |
134 | 211 |
135 private int childrenCount() { | 212 private int childrenCount() { |
136 int nodeCount = nodeClass.nodeFieldOffsets.length; | 213 int nodeCount = nodeClass.childOffsets.length; |
137 for (long fieldOffset : nodeClass.nodeArrayFieldOffsets) { | 214 for (long fieldOffset : nodeClass.childrenOffsets) { |
138 Node[] children = ((Node[]) unsafe.getObject(node, fieldOffset)); | 215 Node[] children = ((Node[]) unsafe.getObject(node, fieldOffset)); |
139 if (children != null) { | 216 if (children != null) { |
140 nodeCount += children.length; | 217 nodeCount += children.length; |
141 } | 218 } |
142 } | 219 } |
143 return nodeCount; | 220 return nodeCount; |
144 } | 221 } |
145 | 222 |
146 private Node nodeAt(int idx) { | 223 private Node nodeAt(int idx) { |
147 int nodeCount = nodeClass.nodeFieldOffsets.length; | 224 int nodeCount = nodeClass.childOffsets.length; |
148 if (idx < nodeCount) { | 225 if (idx < nodeCount) { |
149 return (Node) unsafe.getObject(node, nodeClass.nodeFieldOffsets[idx]); | 226 return (Node) unsafe.getObject(node, nodeClass.childOffsets[idx]); |
150 } else { | 227 } else { |
151 for (long fieldOffset : nodeClass.nodeArrayFieldOffsets) { | 228 for (long fieldOffset : nodeClass.childrenOffsets) { |
152 Node[] nodeArray = (Node[]) unsafe.getObject(node, fieldOffset); | 229 Node[] nodeArray = (Node[]) unsafe.getObject(node, fieldOffset); |
153 if (idx < nodeCount + nodeArray.length) { | 230 if (idx < nodeCount + nodeArray.length) { |
154 return nodeArray[idx - nodeCount]; | 231 return nodeArray[idx - nodeCount]; |
155 } | 232 } |
156 nodeCount += nodeArray.length; | 233 nodeCount += nodeArray.length; |
218 return null; | 295 return null; |
219 } | 296 } |
220 | 297 |
221 unsafe.putObject(clone, nodeClass.parentOffset, null); | 298 unsafe.putObject(clone, nodeClass.parentOffset, null); |
222 | 299 |
223 for (long fieldOffset : nodeClass.nodeFieldOffsets) { | 300 for (long fieldOffset : nodeClass.childOffsets) { |
224 Node child = (Node) unsafe.getObject(orig, fieldOffset); | 301 Node child = (Node) unsafe.getObject(orig, fieldOffset); |
225 if (child != null) { | 302 if (child != null) { |
226 Node clonedChild = cloneNode(child); | 303 Node clonedChild = cloneNode(child); |
227 if (clonedChild == null) { | 304 if (clonedChild == null) { |
228 return null; | 305 return null; |
230 | 307 |
231 unsafe.putObject(clonedChild, nodeClass.parentOffset, clone); | 308 unsafe.putObject(clonedChild, nodeClass.parentOffset, clone); |
232 unsafe.putObject(clone, fieldOffset, clonedChild); | 309 unsafe.putObject(clone, fieldOffset, clonedChild); |
233 } | 310 } |
234 } | 311 } |
235 for (long fieldOffset : nodeClass.nodeArrayFieldOffsets) { | 312 for (long fieldOffset : nodeClass.childrenOffsets) { |
236 Node[] children = (Node[]) unsafe.getObject(orig, fieldOffset); | 313 Node[] children = (Node[]) unsafe.getObject(orig, fieldOffset); |
237 if (children != null) { | 314 if (children != null) { |
238 Node[] clonedChildren = children.clone(); | 315 Node[] clonedChildren = children.clone(); |
239 Arrays.fill(clonedChildren, null); | 316 Arrays.fill(clonedChildren, null); |
240 for (int i = 0; i < children.length; i++) { | 317 for (int i = 0; i < children.length; i++) { |
254 | 331 |
255 public static List<Node> findNodeChildren(Node node) { | 332 public static List<Node> findNodeChildren(Node node) { |
256 List<Node> nodes = new ArrayList<>(); | 333 List<Node> nodes = new ArrayList<>(); |
257 NodeClass nodeClass = NodeClass.get(node.getClass()); | 334 NodeClass nodeClass = NodeClass.get(node.getClass()); |
258 | 335 |
259 for (long fieldOffset : nodeClass.nodeFieldOffsets) { | 336 for (long fieldOffset : nodeClass.childOffsets) { |
260 Object child = unsafe.getObject(node, fieldOffset); | 337 Object child = unsafe.getObject(node, fieldOffset); |
261 if (child != null) { | 338 if (child != null) { |
262 nodes.add((Node) child); | 339 nodes.add((Node) child); |
263 } | 340 } |
264 } | 341 } |
265 for (long fieldOffset : nodeClass.nodeArrayFieldOffsets) { | 342 for (long fieldOffset : nodeClass.childrenOffsets) { |
266 Node[] children = (Node[]) unsafe.getObject(node, fieldOffset); | 343 Node[] children = (Node[]) unsafe.getObject(node, fieldOffset); |
267 if (children != null) { | 344 if (children != null) { |
268 nodes.addAll(Arrays.asList(children)); | 345 nodes.addAll(Arrays.asList(children)); |
269 } | 346 } |
270 } | 347 } |
273 } | 350 } |
274 | 351 |
275 public static void replaceChild(Node parent, Node oldChild, Node newChild) { | 352 public static void replaceChild(Node parent, Node oldChild, Node newChild) { |
276 NodeClass nodeClass = NodeClass.get(parent.getClass()); | 353 NodeClass nodeClass = NodeClass.get(parent.getClass()); |
277 | 354 |
278 for (long fieldOffset : nodeClass.nodeFieldOffsets) { | 355 for (long fieldOffset : nodeClass.childOffsets) { |
279 if (unsafe.getObject(parent, fieldOffset) == oldChild) { | 356 if (unsafe.getObject(parent, fieldOffset) == oldChild) { |
280 unsafe.putObject(parent, fieldOffset, newChild); | 357 unsafe.putObject(parent, fieldOffset, newChild); |
281 } | 358 } |
282 } | 359 } |
283 for (long fieldOffset : nodeClass.nodeArrayFieldOffsets) { | 360 for (long fieldOffset : nodeClass.childrenOffsets) { |
284 Node[] array = (Node[]) unsafe.getObject(parent, fieldOffset); | 361 Node[] array = (Node[]) unsafe.getObject(parent, fieldOffset); |
285 if (array != null) { | 362 if (array != null) { |
286 for (int i = 0; i < array.length; i++) { | 363 for (int i = 0; i < array.length; i++) { |
287 if (array[i] == oldChild) { | 364 if (array[i] == oldChild) { |
288 array[i] = newChild; | 365 array[i] = newChild; |
291 } | 368 } |
292 } | 369 } |
293 } | 370 } |
294 } | 371 } |
295 | 372 |
296 public static long[] getNodeDataFieldOffsets(Class<?> nodeClass) { | |
297 NodeClass clazz = NodeClass.get(nodeClass); | |
298 return Arrays.copyOf(clazz.nodeDataFieldOffsets, clazz.nodeDataFieldClasses.length); | |
299 } | |
300 | |
301 public static Class[] getNodeDataFieldClasses(Class<?> nodeClass) { | |
302 NodeClass clazz = NodeClass.get(nodeClass); | |
303 return Arrays.copyOf(clazz.nodeDataFieldClasses, clazz.nodeDataFieldClasses.length); | |
304 } | |
305 | |
306 public static long getNodeParentOffset(Class<?> nodeClass) { | |
307 NodeClass clazz = NodeClass.get(nodeClass); | |
308 return clazz.parentOffset; | |
309 } | |
310 | |
311 /** Returns the number of Node field declarations in the class hierarchy. */ | |
312 public static long[] getNodeFieldOffsets(Class<?> nodeClass) { | |
313 NodeClass clazz = NodeClass.get(nodeClass); | |
314 return Arrays.copyOf(clazz.nodeFieldOffsets, clazz.nodeFieldOffsets.length); | |
315 } | |
316 | |
317 /** Returns the number of Node[] declaration in the class hierarchy. */ | |
318 public static long[] getNodeFieldArrayOffsets(Class<?> nodeClass) { | |
319 NodeClass clazz = NodeClass.get(nodeClass); | |
320 return Arrays.copyOf(clazz.nodeArrayFieldOffsets, clazz.nodeArrayFieldOffsets.length); | |
321 } | |
322 | |
323 public static Class[] getNodeFieldArrayClasses(Class<?> nodeClass) { | |
324 NodeClass clazz = NodeClass.get(nodeClass); | |
325 return Arrays.copyOf(clazz.nodeArrayFieldClasses, clazz.nodeArrayFieldClasses.length); | |
326 } | |
327 | |
328 public static Class getNodeParentClass(Class<?> nodeClass) { | |
329 return NodeClass.get(nodeClass).parentClass; | |
330 } | |
331 | |
332 public static Class[] getNodeFieldClasses(Class<?> nodeClass) { | |
333 NodeClass clazz = NodeClass.get(nodeClass); | |
334 return Arrays.copyOf(clazz.nodeFieldClasses, clazz.nodeFieldClasses.length); | |
335 } | |
336 | |
337 /** Returns all declared fields in the class hierarchy. */ | 373 /** Returns all declared fields in the class hierarchy. */ |
338 public static Field[] getAllFields(Class<? extends Object> clazz) { | 374 private static Field[] getAllFields(Class<? extends Object> clazz) { |
339 Field[] declaredFields = clazz.getDeclaredFields(); | 375 Field[] declaredFields = clazz.getDeclaredFields(); |
340 if (clazz.getSuperclass() != null) { | 376 if (clazz.getSuperclass() != null) { |
341 return concat(getAllFields(clazz.getSuperclass()), declaredFields); | 377 return concat(getAllFields(clazz.getSuperclass()), declaredFields); |
342 } | 378 } |
343 return declaredFields; | 379 return declaredFields; |
470 } | 506 } |
471 }); | 507 }); |
472 return nodeList; | 508 return nodeList; |
473 } | 509 } |
474 | 510 |
475 public static String printTreeToString(Node node) { | 511 public static String printCompactTreeToString(Node node) { |
476 return printTreeToString(node, false); | 512 StringWriter out = new StringWriter(); |
477 } | 513 printCompactTree(new PrintWriter(out), null, node, 1); |
478 | 514 return out.toString(); |
479 private static String printTreeToString(Node node, boolean compact) { | 515 } |
480 ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); | 516 |
481 if (compact) { | 517 public static void printCompactTree(OutputStream out, Node node) { |
482 printCompactTree(new PrintStream(byteOut), node); | 518 printCompactTree(new PrintWriter(out), null, node, 1); |
519 } | |
520 | |
521 private static void printCompactTree(PrintWriter p, Node parent, Node node, int level) { | |
522 if (node == null) { | |
523 return; | |
524 } | |
525 for (int i = 0; i < level; i++) { | |
526 p.print(" "); | |
527 } | |
528 if (parent == null) { | |
529 p.println(node.getClass().getSimpleName()); | |
483 } else { | 530 } else { |
484 printTree(new PrintStream(byteOut), node); | 531 String fieldName = "unknownField"; |
485 } | 532 NodeField[] fields = NodeClass.get(parent.getClass()).fields; |
486 try { | 533 for (NodeField field : fields) { |
487 byteOut.flush(); | 534 Object value = field.loadValue(parent); |
488 } catch (IOException e) { | 535 if (value == node) { |
489 } | 536 fieldName = field.getName(); |
490 return new String(byteOut.toByteArray()); | 537 break; |
538 } else if (value instanceof Node[]) { | |
539 int index = 0; | |
540 for (Node arrayNode : (Node[]) value) { | |
541 if (arrayNode == node) { | |
542 fieldName = field.getName() + "[" + index + "]"; | |
543 break; | |
544 } | |
545 index++; | |
546 } | |
547 } | |
548 } | |
549 p.print(fieldName); | |
550 p.print(" = "); | |
551 p.println(node.getClass().getSimpleName()); | |
552 } | |
553 | |
554 for (Node child : node.getChildren()) { | |
555 printCompactTree(p, node, child, level + 1); | |
556 } | |
557 p.flush(); | |
491 } | 558 } |
492 | 559 |
493 /** | 560 /** |
494 * Prints a human readable form of a {@link Node} AST to the given {@link PrintStream}. This | 561 * Prints a human readable form of a {@link Node} AST to the given {@link PrintStream}. This |
495 * print method does not check for cycles in the node structure. | 562 * print method does not check for cycles in the node structure. |
496 * | 563 * |
497 * @param p the {@link PrintStream} to print to. | 564 * @param out the stream to print to. |
498 * @param node the root node to write | 565 * @param node the root node to write |
499 */ | 566 */ |
500 public static void printTree(PrintStream p, Node node) { | 567 public static void printTree(OutputStream out, Node node) { |
501 printTree(p, node, new NodeTreeResolver()); | 568 printTree(new PrintWriter(out), node); |
502 } | 569 } |
503 | 570 |
504 public static String printCompactTreeToString(Node node) { | 571 public static String printTreeToString(Node node) { |
505 return printTreeToString(node, true); | 572 StringWriter out = new StringWriter(); |
506 } | 573 printTree(new PrintWriter(out), node); |
507 | 574 return out.toString(); |
508 public static void printCompactTree(PrintStream p, Node node) { | 575 } |
509 printCompactTree(p, null, node, 1); | 576 |
510 } | 577 public static void printTree(PrintWriter p, Node node) { |
511 | 578 printTree(p, node, 1); |
512 private static void printCompactTree(PrintStream p, Node parent, Node node, int level) { | |
513 if (node == null) { | |
514 return; | |
515 } | |
516 for (int i = 0; i < level; i++) { | |
517 p.print(" "); | |
518 } | |
519 if (parent == null) { | |
520 p.println(node.getClass().getSimpleName()); | |
521 } else { | |
522 String fieldName = null; | |
523 Field[] fields = NodeUtil.getAllFields(parent.getClass()); | |
524 try { | |
525 for (Field field : fields) { | |
526 field.setAccessible(true); | |
527 Object value = field.get(parent); | |
528 if (value == node) { | |
529 fieldName = field.getName(); | |
530 break; | |
531 } else if (value instanceof Node[]) { | |
532 int index = 0; | |
533 for (Node arrayNode : (Node[]) value) { | |
534 if (arrayNode == node) { | |
535 fieldName = field.getName() + "[" + index + "]"; | |
536 break; | |
537 } | |
538 index++; | |
539 } | |
540 } | |
541 } | |
542 } catch (Exception e) { | |
543 e.printStackTrace(); | |
544 } | |
545 | |
546 if (fieldName == null) { | |
547 fieldName = "unknownField"; | |
548 } | |
549 p.print(fieldName); | |
550 p.print(" = "); | |
551 p.println(node.getClass().getSimpleName()); | |
552 } | |
553 | |
554 for (Node child : node.getChildren()) { | |
555 printCompactTree(p, node, child, level + 1); | |
556 } | |
557 } | |
558 | |
559 /** | |
560 * Prints a human readable form of a tree to the given {@link PrintStream}. The | |
561 * {@link TreeResolver} interface needs to be implemented to specify how the method can read the | |
562 * tree from plain a object. | |
563 * | |
564 * @param p the {@link PrintStream} to print to. | |
565 * @param o the root object to be printed. | |
566 * @param resolver an implementation of a tree resolver | |
567 */ | |
568 public static void printTree(PrintStream p, Object o, TreeResolver resolver) { | |
569 printTree(p, o, resolver, 1); | |
570 p.println(); | 579 p.println(); |
571 } | 580 p.flush(); |
572 | 581 } |
573 private static void printTree(PrintStream p, Object node, TreeResolver resolver, int level) { | 582 |
583 private static void printTree(PrintWriter p, Node node, int level) { | |
574 if (node == null) { | 584 if (node == null) { |
575 p.print("null"); | 585 p.print("null"); |
576 return; | 586 return; |
577 } | 587 } |
578 | 588 |
579 p.print(node.getClass().getSimpleName()); | 589 p.print(node.getClass().getSimpleName()); |
580 | 590 |
581 Field[] fields = NodeUtil.getAllFields(node.getClass()); | 591 ArrayList<NodeField> childFields = new ArrayList<>(); |
592 String sep = ""; | |
582 p.print("("); | 593 p.print("("); |
583 | 594 for (NodeField field : NodeClass.get(node.getClass()).fields) { |
584 ArrayList<Field> childFields = new ArrayList<>(); | 595 if (field.getKind() == NodeFieldKind.CHILD || field.getKind() == NodeFieldKind.CHILDREN) { |
585 | |
586 boolean first = true; | |
587 for (int i = 0; i < fields.length; i++) { | |
588 Field field = fields[i]; | |
589 if (Modifier.isStatic(field.getModifiers()) || resolver.isFiltered(field)) { | |
590 continue; | |
591 } | |
592 if (resolver.isChildObject(field) || resolver.isChildArrayObject(field)) { | |
593 childFields.add(field); | 596 childFields.add(field); |
594 } | 597 } else if (field.getKind() == NodeFieldKind.DATA) { |
595 if (!resolver.isDataField(field)) { | 598 p.print(sep); |
596 continue; | 599 sep = ", "; |
597 } | 600 |
598 if (!first) { | 601 p.print(field.getName()); |
599 p.print(", "); | 602 p.print(" = "); |
600 } else { | 603 p.print(field.loadValue(node)); |
601 first = false; | 604 } |
602 } | |
603 | |
604 Object value = getObjectValue(node, field.getType(), unsafe.objectFieldOffset(field)); | |
605 | |
606 p.print(field.getName()); | |
607 p.print(" = "); | |
608 p.print(resolver.toString(value)); | |
609 } | 605 } |
610 p.print(")"); | 606 p.print(")"); |
611 | 607 |
612 if (childFields.size() != 0) { | 608 if (childFields.size() != 0) { |
613 p.print(" {"); | 609 p.print(" {"); |
614 } | 610 for (NodeField field : childFields) { |
615 | 611 printNewLine(p, level); |
616 // I refetch the fields to get declaration order. | 612 p.print(field.getName()); |
617 for (int i = 0; i < childFields.size(); i++) { | 613 |
618 Field field = childFields.get(i); | 614 Object value = field.loadValue(node); |
619 Class<?> fieldClass = field.getType(); | 615 if (value == null) { |
620 String name = field.getName(); | 616 p.print(" = null "); |
621 | 617 } else if (field.getKind() == NodeFieldKind.CHILD) { |
622 long offset = unsafe.objectFieldOffset(field); | 618 p.print(" = "); |
623 | 619 printTree(p, (Node) value, level + 1); |
624 Object value = getObjectValue(node, fieldClass, offset); | 620 } else if (field.getKind() == NodeFieldKind.CHILDREN) { |
625 | 621 Node[] children = (Node[]) value; |
626 printNewLine(p, level); | |
627 p.print(name); | |
628 if (value == null) { | |
629 p.print(" = null "); | |
630 } else if (resolver.isChildObject(field)) { | |
631 p.print(" = "); | |
632 printTree(p, value, resolver, level + 1); | |
633 } else if (resolver.isChildArrayObject(field)) { | |
634 Object[] objectArray = resolver.convertToArray(field, value); | |
635 if (objectArray.length == 0) { | |
636 p.print(" = []"); | |
637 } else { | |
638 p.print(" = ["); | 622 p.print(" = ["); |
639 for (int j = 0; j < objectArray.length; j++) { | 623 sep = ""; |
640 printTree(p, objectArray[j], resolver, level + 1); | 624 for (Node child : children) { |
641 if (j < objectArray.length - 1) { | 625 p.print(sep); |
642 p.print(", "); | 626 sep = ", "; |
643 } | 627 printTree(p, child, level + 1); |
644 } | 628 } |
645 p.print("]"); | 629 p.print("]"); |
646 } | 630 } |
647 } else { | 631 } |
648 assert false; | |
649 } | |
650 } | |
651 | |
652 if (childFields.size() != 0) { | |
653 printNewLine(p, level - 1); | 632 printNewLine(p, level - 1); |
654 p.print("}"); | 633 p.print("}"); |
655 } | 634 } |
656 } | 635 } |
657 | 636 |
658 private static Object getObjectValue(Object base, Class<?> fieldClass, long offset) { | 637 private static void printNewLine(PrintWriter p, int level) { |
659 if (fieldClass == boolean.class) { | |
660 return unsafe.getBoolean(base, offset); | |
661 } else if (fieldClass == byte.class) { | |
662 return unsafe.getByte(base, offset); | |
663 } else if (fieldClass == short.class) { | |
664 return unsafe.getShort(base, offset); | |
665 } else if (fieldClass == char.class) { | |
666 return unsafe.getChar(base, offset); | |
667 } else if (fieldClass == int.class) { | |
668 return unsafe.getInt(base, offset); | |
669 } else if (fieldClass == long.class) { | |
670 return unsafe.getLong(base, offset); | |
671 } else if (fieldClass == float.class) { | |
672 return unsafe.getFloat(base, offset); | |
673 } else if (fieldClass == double.class) { | |
674 return unsafe.getDouble(base, offset); | |
675 } else { | |
676 return unsafe.getObject(base, offset); | |
677 } | |
678 } | |
679 | |
680 private static void printNewLine(PrintStream p, int level) { | |
681 p.println(); | 638 p.println(); |
682 for (int i = 0; i < level; i++) { | 639 for (int i = 0; i < level; i++) { |
683 p.print(" "); | 640 p.print(" "); |
684 } | 641 } |
685 } | 642 } |
686 | |
687 private static class NodeTreeResolver implements TreeResolver { | |
688 | |
689 @Override | |
690 public boolean isFiltered(Field f) { | |
691 if (f.getName().equals("parent")) { | |
692 return true; | |
693 } | |
694 return f.isSynthetic(); | |
695 } | |
696 | |
697 @Override | |
698 public boolean isDataField(Field f) { | |
699 return !isChildArrayObject(f) && !isChildObject(f); | |
700 } | |
701 | |
702 @Override | |
703 public boolean isChildObject(Field f) { | |
704 return Node.class.isAssignableFrom(f.getType()) && f.getAnnotation(Child.class) != null; | |
705 } | |
706 | |
707 @Override | |
708 public boolean isChildArrayObject(Field f) { | |
709 return f.getType().getComponentType() != null && Node.class.isAssignableFrom(f.getType().getComponentType()) && f.getAnnotation(Children.class) != null; | |
710 } | |
711 | |
712 @Override | |
713 public Object[] convertToArray(Field f, Object data) { | |
714 return (Object[]) data; | |
715 } | |
716 | |
717 @Override | |
718 public String toString(Object o) { | |
719 return o == null ? "null" : o.toString(); | |
720 } | |
721 } | |
722 | |
723 /** | |
724 * Specifies how a tree can be built from plain objects. | |
725 */ | |
726 public interface TreeResolver { | |
727 | |
728 /** | |
729 * Returns true if a {@link Field} is filtered from the tree. | |
730 * | |
731 * @param f the field to check | |
732 */ | |
733 boolean isFiltered(Field f); | |
734 | |
735 /** | |
736 * Returns true if a {@link Field} is a field that contains a data value which should not be | |
737 * traversed recursively. | |
738 * | |
739 * @param f the field to check | |
740 * @return true if a the given field is a data field else false. | |
741 */ | |
742 boolean isDataField(Field f); | |
743 | |
744 /** | |
745 * Returns true if a {@link Field} is a field that contains an {@link Object} which should | |
746 * be recursively visited. | |
747 * | |
748 * @param f the field to check | |
749 * @return true if a the given field is a child field else false. | |
750 */ | |
751 boolean isChildObject(Field f); | |
752 | |
753 /** | |
754 * Returns true if a {@link Field} is a field that contains any kind of list/array structure | |
755 * which itself holds values that should be recursively visited. | |
756 * | |
757 * @param f the field to check | |
758 * @return true if a the given field is a child array/list field else false. | |
759 */ | |
760 boolean isChildArrayObject(Field f); | |
761 | |
762 /** | |
763 * Converts an given child array object to array which can be traversed. This is especially | |
764 * useful to convert any kind of list structure to a traversable array. | |
765 * | |
766 * @param f the field for meta data needed to convert the data {@link Object}. | |
767 * @param value the actual value of the child array/list object. | |
768 * @return the converted {@link Object} array. | |
769 */ | |
770 Object[] convertToArray(Field f, Object value); | |
771 | |
772 /** | |
773 * Returns a human readable string for any data field object in the tree. | |
774 * | |
775 * @param o the object to convert to string. | |
776 * @return the converted string | |
777 */ | |
778 String toString(Object o); | |
779 } | |
780 | |
781 } | 643 } |