/* * Copyright (C) 2010 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package lombok.ast.ecj; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import lombok.Data; import lombok.SneakyThrows; import lombok.val; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.ArrayTypeReference; import org.eclipse.jdt.internal.compiler.ast.ConditionalExpression; import org.eclipse.jdt.internal.compiler.impl.Constant; import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; import com.google.common.base.Joiner; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.MapMaker; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; public class EcjTreePrinter { private static final Multimap<Class<?>, ComponentField> visitedClasses = ArrayListMultimap.create(); private static final List<String> POSITION_FIELDNAMES = ImmutableList.of( "sourceStart", "sourceEnd", "originalSourceEnd", "bodyStart", "bodyEnd", "blockStart", "declarationSourceStart", "declarationSourceEnd", "declarationEnd", "endPart1Position", "endPart2Position", "valuePositions", "sourcePositions", "modifiersSourceStart", "typeArgumentsSourceStart", "statementEnd", "labelEnd", "nameSourcePosition", "tagSourceStart", "tagSourceEnd" ); private final Printer printer; private Set<String> propertySkipList = Sets.newHashSet(); private Multimap<String, Object> propertyIfValueSkipList = ArrayListMultimap.create(); private Map<String, String> stringReplacements = Maps.newHashMap(); private List<ReferenceTrackingSkip> referenceTrackingSkipList = Lists.newArrayList(); private EcjTreePrinter(boolean printPositions) { printer = new Printer(printPositions); } public static EcjTreePrinter printerWithPositions() { return new EcjTreePrinter(true); } public static EcjTreePrinter printerWithoutPositions() { return new EcjTreePrinter(false); } @Override public String toString() { return getContent(); } public String getContent() { String result = printer.content.toString(); for (val entry : stringReplacements.entrySet()) { result = result.replace(entry.getKey(), entry.getValue()); } return result; } public void visit(ASTNode node) { visitor.visitEcjNode(node); } public EcjTreePrinter skipProperty(Class<? extends ASTNode> type, String propertyName) { propertySkipList.add(type.getSimpleName() + "/" + propertyName); return this; } public EcjTreePrinter skipPropertyIfHasValue(Class<? extends ASTNode> type, String propertyName, Object value) { propertyIfValueSkipList.put(type.getSimpleName() + "/" + propertyName, value); return this; } public void stringReplace(String original, String replacement) { stringReplacements.put(original, replacement); } @Data private static class ReferenceTrackingSkip { private final Class<? extends ASTNode> parent; private final Class<?> type; } public EcjTreePrinter skipReferenceTracking(Class<? extends ASTNode> parent, Class<?> type) { referenceTrackingSkipList.add(new ReferenceTrackingSkip(parent, type)); return this; } private final EcjTreeVisitor visitor = new EcjTreeVisitor() { @Override public void visitAny(ASTNode node) { Collection<ComponentField> fields = findFields(node); for (ComponentField f : fields) { String skipListKey = node.getClass().getSimpleName() + "/" + f.field.getName(); if (propertySkipList.contains(skipListKey)) continue; Object value; if (node instanceof ConditionalExpression) ((ConditionalExpression)node).valueIfTrue.sourceEnd = -2; if ("originalSourceEnd".equals(f.field.getName()) && node instanceof ArrayTypeReference) { //workaround for eclipse arbitrarily skipping this field and setting it. value = -2; } else { value = readField(f.field, node); } if (value == null) { continue; } if (propertyIfValueSkipList.get(skipListKey).contains(value)) continue; boolean trackRef = true; for (ReferenceTrackingSkip skip : referenceTrackingSkipList) { if (skip.getParent() != null && !skip.getParent().isInstance(node)) continue; if (skip.getType() != null && !skip.getType().isInstance(value)) continue; trackRef = false; break; } f.print(printer, this, value, trackRef); } } //TODO all the javadocy nodes need to be as methods in EcjTreeVisitor. @Override public void visitOther(ASTNode node) { visitAny(node); } }; @SneakyThrows(IllegalAccessException.class) private Object readField(Field field, ASTNode node) { return field.get(node); } private static Collection<ComponentField> findFields(ASTNode node) { Class<? extends ASTNode> clazz = node.getClass(); if (visitedClasses.containsKey(clazz)) { return visitedClasses.get(clazz); } List<ComponentField> fields = Lists.newArrayList(); for (Field f : findAllFields(clazz)) { if ((f.getModifiers() & Modifier.STATIC) != 0) continue; fields.add(new ComponentField(f)); } Collections.sort(fields); visitedClasses.putAll(clazz, fields); return fields; } private static List<Field> findAllFields(Class<?> clazz) { List<Field> allFields = Lists.newArrayList(); findAllFieldsRecursively(allFields, clazz); return allFields; } private static void findAllFieldsRecursively(List<Field> allFields, Class<?> clazz) { if (clazz == Object.class) { return; } allFields.addAll(Arrays.asList(clazz.getDeclaredFields())); findAllFieldsRecursively(allFields, clazz.getSuperclass()); } static class Printer { final Map<Object, Integer> visited = new MapMaker().weakKeys().makeMap(); int objectCounter = 0; private final StringBuilder content = new StringBuilder(); private int indent; private final boolean printPositions; public Printer(boolean printPositions) { this.printPositions = printPositions; } void begin(String typeName, String description, Class<?> clazz, int id) { printIndent(); content.append(typeName).append(" ").append(description) .append(" = ").append(clazz.getSimpleName()); if (id != -1) content.append(" (id:").append(id).append(")"); content.append("\n"); indent++; } void end() { indent--; } public void printProperty(String typeName, String name, Object value, boolean posField) { printIndent(); String stringValue; if (posField) { if (value instanceof Long) { long longValue = (Long)value; stringValue = String.format("(%d, %d)", (int)(longValue >> 32), (int)(longValue & 0xFFFFFFFFL)); } else { stringValue = String.valueOf(value); } } else if ("bits".equals(name) && value instanceof Integer) { stringValue = formatBits((Integer)value); } else if (value instanceof Long) { long longValue = ((Long)value).longValue(); stringValue = String.format("0x%1$016x (%1$d) %2$d<<32 | %3$d", value, (int)(longValue>>32), (int)(longValue & 0xFFFFFFFFL)); } else if (value instanceof Integer) { stringValue = String.format("0x%1$08x (%1$d)", value); } else if (value instanceof char[]) { stringValue = new lombok.ast.StringLiteral().astValue(new String((char[])value)).rawValue(); } else if (value instanceof char[][]) { StringBuilder sb = new StringBuilder(); for (char[] single : ((char[][])value)) { if (sb.length() != 0) { sb.append(", "); } sb.append(new lombok.ast.StringLiteral().astValue(new String(single)).rawValue()); } stringValue = "{" + sb.toString() + "}"; } else if ("compilationResult".equals(name)) { stringValue = value == null ? "[NULL]" : "[SET]"; } else if ("problemReporter".equals(name)) { return; } else { stringValue = String.valueOf(value); } content.append(typeName).append(" ").append(name).append(" = ").append(stringValue).append("\n"); } private static String formatBits(int value) { List<Integer> elems = Lists.newArrayList(); int pos = 0; while (value != 0) { pos++; if ((value & 1) != 0) elems.add(pos); value >>>= 1; } return elems.isEmpty() ? "NONE" : "[" + Joiner.on(",").join(elems) + "]"; } private void printIndent() { for (int i = 0; i < indent; i++) { content.append(" "); } } } static class ComponentField implements Comparable<ComponentField>{ private static final List<Package> KNOWN_PACKAGES = ImmutableList.of( String.class.getPackage(), ASTNode.class.getPackage(), TypeBinding.class.getPackage(), Constant.class.getPackage() ); private ImmutableMap<Class<?>, Object> DEFAULTS = ImmutableMap.<Class<?>, Object>builder() .put(boolean.class, false) .put(byte.class, (byte)0) .put(short.class, (short)0) .put(int.class, 0) .put(char.class, '\0') .put(long.class, 0L) .put(float.class, 0f) .put(double.class, .0) .build(); private final Field field; private final int dimensions; private final Class<?> type; public ComponentField(Field field) { this.field = field; field.setAccessible(true); Class<?> type = field.getType(); int dimensions = 0; while (type.isArray()) { dimensions++; type = type.getComponentType(); } this.dimensions = dimensions; this.type = type; } @Override public String toString() { return createDescription(); } private String createDescription() { StringBuilder result = new StringBuilder(); result.append(typeName()); for (int dim = 0; dim < dimensions; dim++) { result.append("[]"); } result.append(" ").append(field.getName()); return result.toString(); } private String typeName() { if (type.isPrimitive() || KNOWN_PACKAGES.contains(type.getPackage())) { return type.getSimpleName(); } return type.getName(); } boolean isPositionField() { return POSITION_FIELDNAMES.contains(field.getName()); } @Override public int compareTo(ComponentField o) { Class<?> otherType = o.type; if (isPositionField() && o.isPositionField()) { return POSITION_FIELDNAMES.indexOf(field.getName()) - POSITION_FIELDNAMES.indexOf(o.field.getName()); } if (isPositionField() || o.isPositionField()) return isPositionField() ? -1 : 1; if (type.isPrimitive() == otherType.isPrimitive()) { return String.CASE_INSENSITIVE_ORDER.compare(field.getName(), o.field.getName()); } return type.isPrimitive() ? -1 : 1; } public void printVisited(Printer printer, Integer id) { printer.printProperty(typeName(), field.getName(), "reference to " + id, false); } public void print(Printer printer, EcjTreeVisitor visitor, Object value, boolean trackRef) { boolean posField = isPositionField(); if (!printer.printPositions && posField) return; if (!posField && isDefault(value)) return; unroll(printer, visitor, value, 0, field.getName(), posField, trackRef); } private void unroll(Printer printer, EcjTreeVisitor visitor, Object value, int dim, String description, boolean posField, boolean trackRef) { if (dim == dimensions) { if (value instanceof ASTNode) { if (!trackRef) { printer.begin(typeName(), description, value.getClass(), printer.objectCounter++); visitor.visitEcjNode((ASTNode)value); printer.end(); } else if (printer.visited.containsKey(value)) { printVisited(printer, printer.visited.get(value)); } else { printer.visited.put(value, printer.objectCounter); printer.begin(typeName(), description, value.getClass(), printer.objectCounter++); visitor.visitEcjNode((ASTNode)value); printer.end(); } } else { printer.printProperty(typeName(), description, value, posField); } } else { if (value == null) { printer.printProperty(typeName(), description, "NULL", posField); } else { if (type == char.class && dimensions - dim <= 2) { if (dimensions - dim == 1) { printer.printProperty(typeName(), description + "[]", value, posField); } else { printer.printProperty(typeName(), description + "[][]", value, posField); } return; } int length = Array.getLength(value); for (int i = 0; i < length; i++) { unroll(printer, visitor, Array.get(value, i), dim + 1, description + "[" + i + "]", posField, trackRef); } } } } private boolean isDefault(Object value) { if (value == null) return true; if (type.isPrimitive() && dimensions == 0) { return DEFAULTS.get(type).equals(value); } return false; } } }