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 }