Package lombok.ast.ecj

Source Code of lombok.ast.ecj.EcjTreeBuilder$JustJavadocParser

/*
* Copyright (C) 2010-2011 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.InvocationTargetException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import lombok.SneakyThrows;
import lombok.ast.AnnotationDeclaration;
import lombok.ast.AnnotationValueArray;
import lombok.ast.AstVisitor;
import lombok.ast.BinaryOperator;
import lombok.ast.Comment;
import lombok.ast.ForwardingAstVisitor;
import lombok.ast.Identifier;
import lombok.ast.JavadocContainer;
import lombok.ast.KeywordModifier;
import lombok.ast.Modifiers;
import lombok.ast.Node;
import lombok.ast.Position;
import lombok.ast.RawListAccessor;
import lombok.ast.UnaryOperator;
import lombok.ast.VariableReference;
import lombok.ast.grammar.SourceStructure;

import org.eclipse.jdt.core.compiler.CategorizedProblem;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy;
import org.eclipse.jdt.internal.compiler.IProblemFactory;
import org.eclipse.jdt.internal.compiler.ast.AND_AND_Expression;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration;
import org.eclipse.jdt.internal.compiler.ast.AllocationExpression;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.ast.AnnotationMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Argument;
import org.eclipse.jdt.internal.compiler.ast.ArrayAllocationExpression;
import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer;
import org.eclipse.jdt.internal.compiler.ast.ArrayQualifiedTypeReference;
import org.eclipse.jdt.internal.compiler.ast.ArrayReference;
import org.eclipse.jdt.internal.compiler.ast.ArrayTypeReference;
import org.eclipse.jdt.internal.compiler.ast.AssertStatement;
import org.eclipse.jdt.internal.compiler.ast.Assignment;
import org.eclipse.jdt.internal.compiler.ast.BinaryExpression;
import org.eclipse.jdt.internal.compiler.ast.Block;
import org.eclipse.jdt.internal.compiler.ast.BreakStatement;
import org.eclipse.jdt.internal.compiler.ast.CaseStatement;
import org.eclipse.jdt.internal.compiler.ast.CastExpression;
import org.eclipse.jdt.internal.compiler.ast.CharLiteral;
import org.eclipse.jdt.internal.compiler.ast.ClassLiteralAccess;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.ast.CompoundAssignment;
import org.eclipse.jdt.internal.compiler.ast.ConditionalExpression;
import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
import org.eclipse.jdt.internal.compiler.ast.ContinueStatement;
import org.eclipse.jdt.internal.compiler.ast.DoStatement;
import org.eclipse.jdt.internal.compiler.ast.DoubleLiteral;
import org.eclipse.jdt.internal.compiler.ast.EmptyStatement;
import org.eclipse.jdt.internal.compiler.ast.EqualExpression;
import org.eclipse.jdt.internal.compiler.ast.ExplicitConstructorCall;
import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.ExtendedStringLiteral;
import org.eclipse.jdt.internal.compiler.ast.FalseLiteral;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.FieldReference;
import org.eclipse.jdt.internal.compiler.ast.FloatLiteral;
import org.eclipse.jdt.internal.compiler.ast.ForStatement;
import org.eclipse.jdt.internal.compiler.ast.ForeachStatement;
import org.eclipse.jdt.internal.compiler.ast.IfStatement;
import org.eclipse.jdt.internal.compiler.ast.ImportReference;
import org.eclipse.jdt.internal.compiler.ast.Initializer;
import org.eclipse.jdt.internal.compiler.ast.InstanceOfExpression;
import org.eclipse.jdt.internal.compiler.ast.IntLiteral;
import org.eclipse.jdt.internal.compiler.ast.IntLiteralMinValue;
import org.eclipse.jdt.internal.compiler.ast.Javadoc;
import org.eclipse.jdt.internal.compiler.ast.LabeledStatement;
import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
import org.eclipse.jdt.internal.compiler.ast.LongLiteral;
import org.eclipse.jdt.internal.compiler.ast.LongLiteralMinValue;
import org.eclipse.jdt.internal.compiler.ast.MarkerAnnotation;
import org.eclipse.jdt.internal.compiler.ast.MemberValuePair;
import org.eclipse.jdt.internal.compiler.ast.MessageSend;
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.NameReference;
import org.eclipse.jdt.internal.compiler.ast.NormalAnnotation;
import org.eclipse.jdt.internal.compiler.ast.NullLiteral;
import org.eclipse.jdt.internal.compiler.ast.OR_OR_Expression;
import org.eclipse.jdt.internal.compiler.ast.OperatorIds;
import org.eclipse.jdt.internal.compiler.ast.ParameterizedQualifiedTypeReference;
import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference;
import org.eclipse.jdt.internal.compiler.ast.PostfixExpression;
import org.eclipse.jdt.internal.compiler.ast.PrefixExpression;
import org.eclipse.jdt.internal.compiler.ast.QualifiedAllocationExpression;
import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference;
import org.eclipse.jdt.internal.compiler.ast.QualifiedSuperReference;
import org.eclipse.jdt.internal.compiler.ast.QualifiedThisReference;
import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference;
import org.eclipse.jdt.internal.compiler.ast.ReturnStatement;
import org.eclipse.jdt.internal.compiler.ast.SingleMemberAnnotation;
import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference;
import org.eclipse.jdt.internal.compiler.ast.Statement;
import org.eclipse.jdt.internal.compiler.ast.StringLiteral;
import org.eclipse.jdt.internal.compiler.ast.StringLiteralConcatenation;
import org.eclipse.jdt.internal.compiler.ast.SuperReference;
import org.eclipse.jdt.internal.compiler.ast.SwitchStatement;
import org.eclipse.jdt.internal.compiler.ast.SynchronizedStatement;
import org.eclipse.jdt.internal.compiler.ast.ThisReference;
import org.eclipse.jdt.internal.compiler.ast.ThrowStatement;
import org.eclipse.jdt.internal.compiler.ast.TrueLiteral;
import org.eclipse.jdt.internal.compiler.ast.TryStatement;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeParameter;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.ast.UnaryExpression;
import org.eclipse.jdt.internal.compiler.ast.WhileStatement;
import org.eclipse.jdt.internal.compiler.ast.Wildcard;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.lookup.Binding;
import org.eclipse.jdt.internal.compiler.lookup.ExtraCompilerModifiers;
import org.eclipse.jdt.internal.compiler.parser.JavadocParser;
import org.eclipse.jdt.internal.compiler.parser.Parser;
import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import static lombok.ast.ConversionPositionInfo.getConversionPositionInfo;

/**
* Turns {@code lombok.ast} based ASTs into eclipse/ecj's {@code org.eclipse.jdt.internal.compiler.ast.ASTNode} model.
*/
public class EcjTreeBuilder {
  private static final int VISIBILITY_MASK = 7;
  static final char[] PACKAGE_INFO = "package-info".toCharArray();
 
  private final Map<lombok.ast.Node, Collection<SourceStructure>> sourceStructures;
  private List<? extends ASTNode> result = null;
  private final String rawInput;
  private final ProblemReporter reporter;
  private final ProblemReporter silentProblemReporter;
  private final CompilationResult compilationResult;
  private final CompilerOptions options;
  private final EnumSet<BubblingFlags> bubblingFlags = EnumSet.noneOf(BubblingFlags.class);
  private final EnumSet<BubblingFlags> AUTO_REMOVABLE_BUBBLING_FLAGS = EnumSet.of(BubblingFlags.LOCALTYPE);
 
  private enum BubblingFlags {
    ASSERT, LOCALTYPE, ABSTRACT_METHOD
  }
 
  private enum VariableKind {
    UNSUPPORTED {
      AbstractVariableDeclaration create() {
        throw new UnsupportedOperationException();
      }
    },
    FIELD {
      AbstractVariableDeclaration create() {
        return new FieldDeclaration();
      }
    },
    LOCAL {
      AbstractVariableDeclaration create() {
        return new LocalDeclaration(null, 0, 0);
      }
    },
    ARGUMENT {
      AbstractVariableDeclaration create() {
        return new Argument(null, 0, null, 0);
      }
    };
   
    abstract AbstractVariableDeclaration create();
   
    static VariableKind kind(lombok.ast.VariableDefinition node) {
      //TODO rewrite this whole thing.
      lombok.ast.Node parent = node.getParent();
      if (parent instanceof lombok.ast.VariableDeclaration) {
        if (parent.getParent() instanceof lombok.ast.TypeBody ||
          parent.getParent() instanceof lombok.ast.EnumTypeBody) {
          return FIELD;
        } else {
          return LOCAL;
        }
      }
      if (parent instanceof lombok.ast.For ||
        parent instanceof lombok.ast.ForEach){
        return LOCAL;
      }
      if (parent instanceof lombok.ast.Catch ||
        parent instanceof lombok.ast.MethodDeclaration ||
        parent instanceof lombok.ast.ConstructorDeclaration){
        return ARGUMENT;
      }
      return UNSUPPORTED;
    }
  }
 
  private static final IProblemFactory SILENT_PROBLEM_FACTORY = new IProblemFactory() {
   
    @Override public String getLocalizedMessage(int problemId, int elaborationId, String[] messageArguments) {
      return null;
    }
   
    @Override public String getLocalizedMessage(int problemId, String[] messageArguments) {
      return null;
    }
   
    @Override public Locale getLocale() {
      return Locale.getDefault();
    }
   
    @Override public CategorizedProblem createProblem(char[] originatingFileName, int problemId, String[] problemArguments, int elaborationId, String[] messageArguments, int severity, int startPosition, int endPosition, int lineNumber, int columnNumber) {
      return null;
    }
   
    @Override public CategorizedProblem createProblem(char[] originatingFileName, int problemId, String[] problemArguments, String[] messageArguments, int severity, int startPosition, int endPosition, int lineNumber, int columnNumber) {
      return null;
    }
  };
 
  public EcjTreeBuilder(lombok.ast.grammar.Source source, CompilerOptions options) {
    this(source, createDefaultProblemReporter(options), createSilentProblemReporter(options), new CompilationResult(source.getName().toCharArray(), 0, 0, 0));
  }
 
  public EcjTreeBuilder(String rawInput, String name, CompilerOptions options) {
    this(rawInput, createDefaultProblemReporter(options), createSilentProblemReporter(options), new CompilationResult(name.toCharArray(), 0, 0, 0));
  }
 
  private static ProblemReporter createDefaultProblemReporter(CompilerOptions options) {
    return new ProblemReporter(new IErrorHandlingPolicy() {
      public boolean proceedOnErrors() {
        return true;
      }
     
      public boolean stopOnFirstError() {
        return false;
      }
    }, options, new DefaultProblemFactory(Locale.ENGLISH));
  }
 
  private static ProblemReporter createSilentProblemReporter(CompilerOptions options) {
    return new ProblemReporter(new IErrorHandlingPolicy() {
      public boolean proceedOnErrors() {
        return true;
      }
     
      public boolean stopOnFirstError() {
        return false;
      }
    }, options, SILENT_PROBLEM_FACTORY);
  }
 
  public EcjTreeBuilder(lombok.ast.grammar.Source source, ProblemReporter reporter, ProblemReporter silentProblemReporter, CompilationResult compilationResult) {
    this.options = reporter.options;
    this.sourceStructures = source.getSourceStructures();
    this.rawInput = source.getRawInput();
    this.reporter = reporter;
    this.silentProblemReporter = silentProblemReporter;
    this.compilationResult = compilationResult;
  }
 
  public EcjTreeBuilder(String rawInput, ProblemReporter reporter, ProblemReporter silentProblemReporter, CompilationResult compilationResult) {
    this.options = reporter.options;
    this.sourceStructures = null;
    this.rawInput = rawInput;
    this.reporter = reporter;
    this.silentProblemReporter = silentProblemReporter;
    this.compilationResult = compilationResult;
  }
 
  private EcjTreeBuilder(EcjTreeBuilder parent) {
    this.reporter = parent.reporter;
    this.silentProblemReporter = parent.silentProblemReporter;
    this.options = parent.options;
    this.rawInput = parent.rawInput;
    this.compilationResult = parent.compilationResult;
    this.sourceStructures = parent.sourceStructures;
  }
 
  private EcjTreeBuilder create() {
    return new EcjTreeBuilder(this);
  }
 
  private Expression toExpression(lombok.ast.Node node) {
    return (Expression) toTree(node);
  }
 
  private Statement toStatement(lombok.ast.Node node) {
    return (Statement) toTree(node);
  }
 
  private ASTNode toTree(lombok.ast.Node node) {
    if (node == null) return null;
    EcjTreeBuilder newBuilder = create();
    node.accept(newBuilder.visitor);
    bubblingFlags.addAll(newBuilder.bubblingFlags);
    try {
      return newBuilder.get();
    } catch (RuntimeException e) {
      System.err.printf("Node '%s' (%s) did not produce any results\n", node, node.getClass().getSimpleName());
      throw e;
    }
  }
 
  private char[] toName(lombok.ast.Identifier node) {
    if (node == null) {
      return null;
    }
    return node.astValue().toCharArray();
  }
 
  private <T extends ASTNode> T[] toArray(Class<T> type, List<T> list) {
    if (list.isEmpty()) return null;
    @SuppressWarnings("unchecked")
    T[] emptyArray = (T[]) Array.newInstance(type, 0);
    return list.toArray(emptyArray);
  }
 
  private <T extends ASTNode> T[] toArray(Class<T> type, lombok.ast.Node node) {
    return toArray(type, toList(type, node));
  }
 
  private <T extends ASTNode> T[] toArray(Class<T> type, lombok.ast.StrictListAccessor<?, ?> accessor) {
    List<T> list = Lists.newArrayList();
    for (lombok.ast.Node node : accessor) {
      EcjTreeBuilder newBuilder = create();
      node.accept(newBuilder.visitor);
      bubblingFlags.addAll(newBuilder.bubblingFlags);
     
      List<? extends ASTNode> values;
     
      values = newBuilder.getAll();
     
      for (ASTNode value : values) {
        if (value != null && !type.isInstance(value)) {
          throw new ClassCastException(value.getClass().getName() + " cannot be cast to " + type.getName());
        }
        list.add(type.cast(value));
      }
    }
   
    return toArray(type, list);
  }
 
  private <T extends ASTNode> List<T> toList(Class<T> type, lombok.ast.Node node) {
    if (node == null) return Lists.newArrayList();
    EcjTreeBuilder newBuilder = create();
    node.accept(newBuilder.visitor);
    bubblingFlags.addAll(newBuilder.bubblingFlags);
    @SuppressWarnings("unchecked")
    List<T> all = (List<T>)newBuilder.getAll();
    return Lists.newArrayList(all);
  }
 
  public void visit(lombok.ast.Node node) {
    node.accept(visitor);
  }
 
  public ASTNode get() {
    if (result.isEmpty()) {
      return null;
    }
    if (result.size() == 1) {
      return result.get(0);
    }
    throw new RuntimeException("Expected only one result but got " + result.size());
  }
 
  public List<? extends ASTNode> getAll() {
    return result;
  }
 
  private static <T extends ASTNode> T posParen(T in, lombok.ast.Node node) {
    if (in == null) return null;
    if (node instanceof lombok.ast.Expression) {
      List<Position> parensPositions = ((lombok.ast.Expression)node).astParensPositions();
      if (!parensPositions.isEmpty()) {
        in.sourceStart = parensPositions.get(parensPositions.size() - 1).getStart();
        in.sourceEnd = parensPositions.get(parensPositions.size() - 1).getEnd() - 1;
      }
    }
   
    return in;
  }
 
  private static boolean isExplicitlyAbstract(Modifiers m) {
    for (KeywordModifier keyword : m.astKeywords()) {
      if ("abstract".equals(keyword.astName())) return true;
    }
    return false;
  }
 
  private static final EnumMap<UnaryOperator, Integer> UNARY_OPERATORS = Maps.newEnumMap(UnaryOperator.class);
  static {
    UNARY_OPERATORS.put(UnaryOperator.BINARY_NOT, OperatorIds.TWIDDLE);
    UNARY_OPERATORS.put(UnaryOperator.LOGICAL_NOT, OperatorIds.NOT);
    UNARY_OPERATORS.put(UnaryOperator.UNARY_PLUS, OperatorIds.PLUS);
    UNARY_OPERATORS.put(UnaryOperator.PREFIX_INCREMENT, OperatorIds.PLUS);
    UNARY_OPERATORS.put(UnaryOperator.UNARY_MINUS, OperatorIds.MINUS);
    UNARY_OPERATORS.put(UnaryOperator.PREFIX_DECREMENT, OperatorIds.MINUS);
    UNARY_OPERATORS.put(UnaryOperator.POSTFIX_INCREMENT, OperatorIds.PLUS);
    UNARY_OPERATORS.put(UnaryOperator.POSTFIX_DECREMENT, OperatorIds.MINUS);
  }
 
  private static final EnumMap<BinaryOperator, Integer> BINARY_OPERATORS = Maps.newEnumMap(BinaryOperator.class);
  static {
    BINARY_OPERATORS.put(BinaryOperator.PLUS_ASSIGN, OperatorIds.PLUS);
    BINARY_OPERATORS.put(BinaryOperator.MINUS_ASSIGN, OperatorIds.MINUS);
    BINARY_OPERATORS.put(BinaryOperator.MULTIPLY_ASSIGN, OperatorIds.MULTIPLY);
    BINARY_OPERATORS.put(BinaryOperator.DIVIDE_ASSIGN, OperatorIds.DIVIDE);
    BINARY_OPERATORS.put(BinaryOperator.REMAINDER_ASSIGN, OperatorIds.REMAINDER);
    BINARY_OPERATORS.put(BinaryOperator.AND_ASSIGN, OperatorIds.AND);
    BINARY_OPERATORS.put(BinaryOperator.XOR_ASSIGN, OperatorIds.XOR);
    BINARY_OPERATORS.put(BinaryOperator.OR_ASSIGN, OperatorIds.OR);
    BINARY_OPERATORS.put(BinaryOperator.SHIFT_LEFT_ASSIGN, OperatorIds.LEFT_SHIFT);
    BINARY_OPERATORS.put(BinaryOperator.SHIFT_RIGHT_ASSIGN, OperatorIds.RIGHT_SHIFT);
    BINARY_OPERATORS.put(BinaryOperator.BITWISE_SHIFT_RIGHT_ASSIGN, OperatorIds.UNSIGNED_RIGHT_SHIFT);
    BINARY_OPERATORS.put(BinaryOperator.LOGICAL_OR, OperatorIds.OR_OR);
    BINARY_OPERATORS.put(BinaryOperator.LOGICAL_AND, OperatorIds.AND_AND);
    BINARY_OPERATORS.put(BinaryOperator.BITWISE_OR, OperatorIds.OR);
    BINARY_OPERATORS.put(BinaryOperator.BITWISE_XOR, OperatorIds.XOR);
    BINARY_OPERATORS.put(BinaryOperator.BITWISE_AND, OperatorIds.AND);
    BINARY_OPERATORS.put(BinaryOperator.EQUALS, OperatorIds.EQUAL_EQUAL);
    BINARY_OPERATORS.put(BinaryOperator.NOT_EQUALS, OperatorIds.NOT_EQUAL);
    BINARY_OPERATORS.put(BinaryOperator.GREATER, OperatorIds.GREATER);
    BINARY_OPERATORS.put(BinaryOperator.GREATER_OR_EQUAL, OperatorIds.GREATER_EQUAL);
    BINARY_OPERATORS.put(BinaryOperator.LESS, OperatorIds.LESS);
    BINARY_OPERATORS.put(BinaryOperator.LESS_OR_EQUAL, OperatorIds.LESS_EQUAL);
    BINARY_OPERATORS.put(BinaryOperator.SHIFT_LEFT, OperatorIds.LEFT_SHIFT);
    BINARY_OPERATORS.put(BinaryOperator.SHIFT_RIGHT, OperatorIds.RIGHT_SHIFT);
    BINARY_OPERATORS.put(BinaryOperator.BITWISE_SHIFT_RIGHT, OperatorIds.UNSIGNED_RIGHT_SHIFT);
    BINARY_OPERATORS.put(BinaryOperator.PLUS, OperatorIds.PLUS);
    BINARY_OPERATORS.put(BinaryOperator.MINUS, OperatorIds.MINUS);
    BINARY_OPERATORS.put(BinaryOperator.MULTIPLY, OperatorIds.MULTIPLY);
    BINARY_OPERATORS.put(BinaryOperator.DIVIDE, OperatorIds.DIVIDE);
    BINARY_OPERATORS.put(BinaryOperator.REMAINDER, OperatorIds.REMAINDER);
  }
 
  private final AstVisitor visitor = new ForwardingAstVisitor() {
    @Override
    public boolean visitCompilationUnit(lombok.ast.CompilationUnit node) {
      int sourceLength = rawInput == null ? 0 : rawInput.length();
      CompilationUnitDeclaration cud = new CompilationUnitDeclaration(reporter, compilationResult, sourceLength);
      cud.bits |= ASTNode.HasAllMethodBodies;
     
      cud.currentPackage = (ImportReference) toTree(node.astPackageDeclaration());
      cud.imports = toArray(ImportReference.class, node.astImportDeclarations());
      cud.types = toArray(TypeDeclaration.class, node.astTypeDeclarations());
     
      if (CharOperation.equals(PACKAGE_INFO, cud.getMainTypeName())) {
        TypeDeclaration[] newTypes;
        if (cud.types == null) {
          newTypes = new TypeDeclaration[1];
        } else {
          newTypes = new TypeDeclaration[cud.types.length + 1];
          System.arraycopy(cud.types, 0, newTypes, 1, cud.types.length);
        }
        TypeDeclaration decl = new TypeDeclaration(compilationResult);
        decl.name = PACKAGE_INFO.clone();
        decl.modifiers = ClassFileConstants.AccDefault | ClassFileConstants.AccInterface;
       
        newTypes[0] = decl;
        cud.types = newTypes;
       
        lombok.ast.PackageDeclaration pkgDeclaration = node.astPackageDeclaration();
        Comment javadoc = pkgDeclaration == null ? null : pkgDeclaration.astJavadoc();
        if (javadoc != null) {
          boolean markDep = javadoc.isMarkedDeprecated();
          cud.javadoc = (Javadoc) toTree(javadoc);
          if (markDep) decl.modifiers |= ClassFileConstants.AccDeprecated;
          decl.javadoc = cud.javadoc;
        }
      }
     
      bubblingFlags.remove(BubblingFlags.ASSERT);
      bubblingFlags.removeAll(AUTO_REMOVABLE_BUBBLING_FLAGS);
      if (!bubblingFlags.isEmpty()) {
        throw new RuntimeException("Unhandled bubbling flags left: " + bubblingFlags);
      }
      return set(node, cud);
    }
   
    private boolean set(lombok.ast.Node node, ASTNode value) {
      if (result != null) throw new IllegalStateException("result is already set");
     
      if (node instanceof lombok.ast.Expression) {
        int parens = ((lombok.ast.Expression)node).getIntendedParens();
        value.bits |= (parens << ASTNode.ParenthesizedSHIFT) & ASTNode.ParenthesizedMASK;
        posParen(value, node);
      }
      if (value instanceof NameReference) {
        updateRestrictionFlags(node, (NameReference)value);
      }
      List<ASTNode> result = Lists.newArrayList();
      if (value != null) result.add(value);
      EcjTreeBuilder.this.result = result;
     
      return true;
    }
   
    private boolean set(lombok.ast.Node node, List<? extends ASTNode> values) {
      if (values.isEmpty()) System.err.printf("Node '%s' (%s) did not produce any results\n", node, node.getClass().getSimpleName());
     
      if (result != null) throw new IllegalStateException("result is already set");
      result = values;
      return true;
    }
   
    private int calculateExplicitDeclarations(Iterable<lombok.ast.Statement> statements) {
      int explicitDeclarations = 0;
      if (statements != null) {
        for (lombok.ast.Statement s : statements) {
          if (s instanceof lombok.ast.VariableDeclaration) explicitDeclarations++;
          if (s instanceof lombok.ast.ClassDeclaration) explicitDeclarations++;
        }
      }
     
      return explicitDeclarations;
    }
   
    @Override
    public boolean visitPackageDeclaration(lombok.ast.PackageDeclaration node) {
      long[] pos = partsToPosArray(node.rawParts());
      ImportReference pkg = new ImportReference(chain(node.astParts()), pos, true, ClassFileConstants.AccDefault);
      pkg.annotations = toArray(Annotation.class, node.astAnnotations());
      pkg.declarationSourceStart = jstart(node);
      pkg.declarationSourceEnd = pkg.declarationEnd = end(node);
     
      return set(node, pkg);
    }
   
    //TODO Create a test file with a whole bunch of comments. Possibly in a separate non-idempotency subdir, as printing comments idempotently is a rough nut to crack.
   
    @Override
    public boolean visitImportDeclaration(lombok.ast.ImportDeclaration node) {
      int staticFlag = node.astStaticImport() ? ClassFileConstants.AccStatic : ClassFileConstants.AccDefault;
      long[] pos = partsToPosArray(node.rawParts());
      ImportReference imp = new ImportReference(chain(node.astParts()), pos, node.astStarImport(), staticFlag);
      imp.declarationSourceStart = start(node);
      imp.declarationSourceEnd = imp.declarationEnd = end(node);
      return set(node, imp);
    }
   
    @Override
    public boolean visitClassDeclaration(lombok.ast.ClassDeclaration node) {
      TypeDeclaration decl = createTypeBody(node.astBody().astMembers(), node, true, 0);
     
      decl.annotations = toArray(Annotation.class, node.astModifiers().astAnnotations());
      decl.superclass = (TypeReference) toTree(node.astExtending());
      decl.superInterfaces = toArray(TypeReference.class, node.astImplementing());
     
      markTypeReferenceIsSuperType(decl);
     
      decl.typeParameters = toArray(TypeParameter.class, node.astTypeVariables());
     
      decl.name = toName(node.astName());
     
      updateTypeBits(node.getParent(), decl, false);
     
      setupJavadoc(decl, node);
     
      //TODO test inner types. Give em everything - (abstract) methods, initializers, static initializers, MULTIPLE initializers.
      return set(node, decl);
    }
   
    private void updateTypeBits(lombok.ast.Node parent, TypeDeclaration decl, boolean isEnum) {
      if (parent == null) {
        return;
      }
      if (parent instanceof lombok.ast.CompilationUnit) {
        char[] mainTypeName = new CompilationUnitDeclaration(reporter, compilationResult, 0).getMainTypeName();
        if (!CharOperation.equals(decl.name, mainTypeName)) {
          decl.bits |= ASTNode.IsSecondaryType;
        }
        return;
      }
     
      if (parent instanceof lombok.ast.TypeBody ||
        parent instanceof lombok.ast.EnumTypeBody) {
        decl.bits |= ASTNode.IsMemberType;
        return;
      }
     
      //TODO test if a type declared in an enum constant is possible
      decl.bits |= ASTNode.IsLocalType;
      bubblingFlags.add(BubblingFlags.LOCALTYPE);
    }
   
    private void markTypeReferenceIsSuperType(TypeDeclaration decl) {
      if (decl.superclass != null) {
        decl.superclass.bits |= ASTNode.IsSuperType;
      }
      if (decl.superInterfaces != null) {
        for (TypeReference t : decl.superInterfaces) {
          t.bits |= ASTNode.IsSuperType;
        }
      }
    }
   
   
    @Override
    public boolean visitInterfaceDeclaration(lombok.ast.InterfaceDeclaration node) {
      TypeDeclaration decl = createTypeBody(node.astBody().astMembers(), node, false, ClassFileConstants.AccInterface);
     
      decl.annotations = toArray(Annotation.class, node.astModifiers().astAnnotations());
      decl.superInterfaces = toArray(TypeReference.class, node.astExtending());
     
      markTypeReferenceIsSuperType(decl);
     
      decl.typeParameters = toArray(TypeParameter.class, node.astTypeVariables());
      decl.name = toName(node.astName());
     
      updateTypeBits(node.getParent(), decl, false);
     
      setupJavadoc(decl, node);
     
      return set(node, decl);
   
   
    @Override
    public boolean visitEnumDeclaration(lombok.ast.EnumDeclaration node) {
      FieldDeclaration[] fields = null;
     
      if (node.astBody() != null) {
        fields = toArray(FieldDeclaration.class, node.astBody().astConstants());
      }
      TypeDeclaration decl = createTypeBody(node.astBody().astMembers(), node, true, ClassFileConstants.AccEnum, fields);
     
      decl.annotations = toArray(Annotation.class, node.astModifiers().astAnnotations());
      decl.superInterfaces = toArray(TypeReference.class, node.astImplementing());
     
      markTypeReferenceIsSuperType(decl);
     
      decl.name = toName(node.astName());
     
      updateTypeBits(node.getParent(), decl, true);
     
      setupJavadoc(decl, node);
     
      return set(node, decl);
    }
   
    @Override
    public boolean visitEnumConstant(lombok.ast.EnumConstant node) {
      //TODO check where the javadoc and annotations go: the field or the type
     
      FieldDeclaration decl = new FieldDeclaration();
      decl.annotations = toArray(Annotation.class, node.astAnnotations());
      decl.name = toName(node.astName());
     
      decl.sourceStart = start(node.astName());
      decl.sourceEnd = end(node.astName());
      decl.declarationSourceStart = decl.modifiersSourceStart = jstart(node);
      decl.declarationSourceEnd = decl.declarationEnd = end(node);
      Position ecjDeclarationSourcePos = getConversionPositionInfo(node, "declarationSource");
      if (ecjDeclarationSourcePos != null) decl.declarationSourceEnd = ecjDeclarationSourcePos.getEnd() - 1;
     
      AllocationExpression init;
      if (node.astBody() == null) {
        init = new AllocationExpression();
        init.enumConstant = decl;
      } else {
        TypeDeclaration type = createTypeBody(node.astBody().astMembers(), null, false, 0);
        type.sourceStart = type.sourceEnd = start(node.astBody());
        type.bodyEnd--;
        type.declarationSourceStart = type.sourceStart;
        type.declarationSourceEnd = end(node);
        type.name = CharOperation.NO_CHAR;
        type.bits &= ~ASTNode.IsMemberType;
        decl.bits |= ASTNode.HasLocalType;
        type.bits |= ASTNode.IsLocalType | ASTNode.IsAnonymousType;
        init = new QualifiedAllocationExpression(type);
        init.enumConstant = decl;
      }
      init.arguments = toArray(Expression.class, node.astArguments());
      decl.initialization = init;
     
      if (bubblingFlags.remove(BubblingFlags.LOCALTYPE)) {
        decl.bits |= ASTNode.HasLocalType;
      }
     
      setupJavadoc(decl, node);
     
      return set(node, decl);
    }
   
    @Override
    public boolean visitAnnotationDeclaration(lombok.ast.AnnotationDeclaration node) {
      TypeDeclaration decl = createTypeBody(node.astBody().astMembers(), node, false,
          ClassFileConstants.AccAnnotation | ClassFileConstants.AccInterface);
      decl.annotations = toArray(Annotation.class, node.astModifiers().astAnnotations());
      decl.name = toName(node.astName());
      updateTypeBits(node.getParent(), decl, false);
     
      setupJavadoc(decl, node);
     
      return set(node, decl);
    }
   
    private void setupJavadoc(ASTNode node, lombok.ast.JavadocContainer container) {
      if (container != null && container.rawJavadoc() instanceof lombok.ast.Comment) {
        lombok.ast.Comment javadoc = (Comment) container.rawJavadoc();
        boolean markDep = javadoc.isMarkedDeprecated();
       
        if (node instanceof AbstractMethodDeclaration) {
          AbstractMethodDeclaration decl = (AbstractMethodDeclaration) node;
          decl.javadoc = (Javadoc) toTree(javadoc);
          if (markDep) decl.modifiers |= ClassFileConstants.AccDeprecated;
        }
        if (node instanceof FieldDeclaration) {
          FieldDeclaration decl = (FieldDeclaration) node;
          decl.javadoc = (Javadoc) toTree(javadoc);
          if (markDep) decl.modifiers |= ClassFileConstants.AccDeprecated;
        }
        if (node instanceof TypeDeclaration) {
          TypeDeclaration decl = (TypeDeclaration) node;
          decl.javadoc = (Javadoc) toTree(javadoc);
          if (markDep) decl.modifiers |= ClassFileConstants.AccDeprecated;
        }
      }
    }
   
    @Override
    public boolean visitConstructorDeclaration(lombok.ast.ConstructorDeclaration node) {
      ConstructorDeclaration decl = new ConstructorDeclaration(compilationResult);
      decl.bodyStart = start(node.rawBody()) + 1;
      decl.bodyEnd = end(node.rawBody()) - 1;
      decl.declarationSourceStart = jstart(node);
      decl.declarationSourceEnd = end(node);
      decl.sourceStart = start(node.astTypeName());
      /* set sourceEnd */ {
        Position ecjPos = getConversionPositionInfo(node, "signature");
        decl.sourceEnd = ecjPos == null ? posOfStructure(node, ")", false) - 1 : ecjPos.getEnd() - 1;
       
        if (!node.rawThrownTypeReferences().isEmpty()) {
          decl.sourceEnd = end(node.rawThrownTypeReferences().last());
        }
      }
      decl.annotations = toArray(Annotation.class, node.astModifiers().astAnnotations());
      decl.modifiers = toModifiers(node.astModifiers());
      decl.typeParameters = toArray(TypeParameter.class, node.astTypeVariables());
      decl.arguments = toArray(Argument.class, node.astParameters());
      decl.thrownExceptions = toArray(TypeReference.class, node.astThrownTypeReferences());
      decl.statements = toArray(Statement.class, node.astBody().astContents());
      decl.selector = toName(node.astTypeName());
     
      setupJavadoc(decl, node);
     
      if (decl.statements == null || decl.statements.length == 0 || !(decl.statements[0] instanceof ExplicitConstructorCall)) {
        decl.constructorCall = new ExplicitConstructorCall(ExplicitConstructorCall.ImplicitSuper);
        decl.constructorCall.sourceStart = decl.sourceStart;
        decl.constructorCall.sourceEnd = decl.sourceEnd;
      } else {
        //TODO check how super() and this() work
        decl.constructorCall = (ExplicitConstructorCall)decl.statements[0];
        if (decl.statements.length > 1) {
          Statement[] newStatements = new Statement[decl.statements.length - 1];
          System.arraycopy(decl.statements, 1, newStatements, 0, newStatements.length);
          decl.statements = newStatements;
        } else {
          decl.statements = null;
        }
      }
     
      if (bubblingFlags.remove(BubblingFlags.LOCALTYPE)) {
        decl.bits |= ASTNode.HasLocalType;
      }
     
      // Unlike other method(-like) constructs, while ConstructorDeclaration has a
      // explicitDeclarations field, its kept at 0, so we don't need to calculate that value here.
     
      if (isUndocumented(node.astBody())) decl.bits |= ASTNode.UndocumentedEmptyBlock;
     
      setupJavadoc(decl, node);
     
      return set(node, decl);
    }
   
    @Override
    public boolean visitMethodDeclaration(lombok.ast.MethodDeclaration node) {
      MethodDeclaration decl = new MethodDeclaration(compilationResult);
      decl.declarationSourceStart = jstart(node);
      decl.declarationSourceEnd = end(node);
      decl.sourceStart = start(node.astMethodName());
      boolean setOriginalPosOnType = false;
      /* set sourceEnd */ {
        Position ecjPos = getConversionPositionInfo(node, "signature");
        decl.sourceEnd = ecjPos == null ? posOfStructure(node, ")", false) - 1: ecjPos.getEnd() - 1;
        if (countStructure(node, "]") > 0) {
          decl.sourceEnd = posOfStructure(node, "]", false) - 1;
          setOriginalPosOnType = true;
        }
       
        if (!node.rawThrownTypeReferences().isEmpty()) {
          decl.sourceEnd = end(node.rawThrownTypeReferences().last());
        }
      }
      if (node.rawBody() == null) {
        decl.bodyStart = decl.sourceEnd + 1;
        decl.bodyEnd = end(node) - 1;
      } else {
        decl.bodyStart = start(node.rawBody()) + 1;
        decl.bodyEnd = end(node.rawBody()) - 1;
      }
      decl.annotations = toArray(Annotation.class, node.astModifiers().astAnnotations());
      decl.modifiers = toModifiers(node.astModifiers());
      decl.returnType = (TypeReference) toTree(node.astReturnTypeReference());
      if (setOriginalPosOnType) {
        if (decl.returnType instanceof ArrayTypeReference) {
          ((ArrayTypeReference)decl.returnType).originalSourceEnd = end(node.rawReturnTypeReference());
        }
      }
      decl.typeParameters = toArray(TypeParameter.class, node.astTypeVariables());
      decl.arguments = toArray(Argument.class, node.astParameters());
      decl.selector = toName(node.astMethodName());
      decl.thrownExceptions = toArray(TypeReference.class, node.astThrownTypeReferences());
      if (node.astBody() == null) {
        decl.modifiers |= ExtraCompilerModifiers.AccSemicolonBody;
      } else {
        decl.statements = toArray(Statement.class, node.astBody().astContents());
        decl.explicitDeclarations = calculateExplicitDeclarations(node.astBody().astContents());
      }
     
      if (bubblingFlags.remove(BubblingFlags.LOCALTYPE)) {
        decl.bits |= ASTNode.HasLocalType;
      }
     
      if (isExplicitlyAbstract(node.astModifiers())) {
        bubblingFlags.add(BubblingFlags.ABSTRACT_METHOD);
      }
      if (isUndocumented(node.astBody())) decl.bits |= ASTNode.UndocumentedEmptyBlock;
     
      setupJavadoc(decl, node);
     
      return set(node, decl);
    }
   
    @Override
    public boolean visitAnnotationMethodDeclaration(lombok.ast.AnnotationMethodDeclaration node) {
      AnnotationMethodDeclaration decl = new AnnotationMethodDeclaration(compilationResult);
      decl.modifiers = toModifiers(node.astModifiers()) + ExtraCompilerModifiers.AccSemicolonBody;
      decl.declarationSourceStart = jstart(node);
      decl.declarationSourceEnd = end(node);
      decl.sourceStart = start(node.astMethodName());
      boolean setOriginalPosOnType = false;
      /* set sourceEnd */ {
        Position ecjSigPos = getConversionPositionInfo(node, "signature");
        Position ecjExtDimPos = getConversionPositionInfo(node, "extendedDimensions");
        if (ecjSigPos != null && ecjExtDimPos != null) {
          decl.sourceEnd = ecjSigPos.getEnd() - 1;
          decl.extendedDimensions = ecjExtDimPos.getStart();
        } else {
          decl.sourceEnd = posOfStructure(node, ")", false) - 1;
          decl.extendedDimensions = countStructure(node, "]");
          if (decl.extendedDimensions > 0) {
            decl.sourceEnd = posOfStructure(node, "]", false) - 1;
            setOriginalPosOnType = true;
          }
        }
      }
      decl.bodyStart = end(node);
      decl.bodyEnd = end(node);
      if (node.astDefaultValue() != null) {
        decl.modifiers |= ClassFileConstants.AccAnnotationDefault;
      }
      decl.annotations = toArray(Annotation.class, node.astModifiers().astAnnotations());
      decl.defaultValue = toExpression(node.astDefaultValue());
      decl.selector = toName(node.astMethodName());
      decl.returnType = (TypeReference) toTree(node.astReturnTypeReference());
      if (setOriginalPosOnType) {
        if (decl.returnType instanceof ArrayTypeReference) {
          ((ArrayTypeReference)decl.returnType).originalSourceEnd = end(node.rawReturnTypeReference());
        }
      }
     
      if (isExplicitlyAbstract(node.astModifiers())) {
        bubblingFlags.add(BubblingFlags.ABSTRACT_METHOD);
      }
     
      setupJavadoc(decl, node);
     
      return set(node, decl);
    }
   
    private TypeDeclaration createTypeBody(lombok.ast.StrictListAccessor<lombok.ast.TypeMember, ?> members, lombok.ast.TypeDeclaration type, boolean canHaveConstructor, int extraModifiers, FieldDeclaration... initialFields) {
      TypeDeclaration decl = new TypeDeclaration(compilationResult);
      decl.modifiers = (type == null ? 0 : toModifiers(type.astModifiers())) | extraModifiers;
      if (members.isEmpty() && isUndocumented(members.owner())) decl.bits |= ASTNode.UndocumentedEmptyBlock;
     
      if (type != null) {
        decl.sourceStart = start(type.astName());
        decl.sourceEnd = end(type.astName());
        decl.declarationSourceStart = jstart(type);
        decl.declarationSourceEnd = end(type);
        if (!(type instanceof AnnotationDeclaration) || !type.astModifiers().isEmpty() || type.rawJavadoc() != null) {
          decl.modifiersSourceStart = jstart(type.astModifiers());
        } else {
          decl.modifiersSourceStart = -1;
        }
      }
      decl.bodyStart = start(members.owner()) + 1;
      decl.bodyEnd = end(members.owner());
     
      boolean hasExplicitConstructor = false;
      List<AbstractMethodDeclaration> methods = Lists.newArrayList();
      List<FieldDeclaration> fields = Lists.newArrayList();
      List<TypeDeclaration> types = Lists.newArrayList();
     
      if (initialFields != null) fields.addAll(Arrays.asList(initialFields));
     
      for (lombok.ast.TypeMember member : members) {
        if (member instanceof lombok.ast.ConstructorDeclaration) {
          hasExplicitConstructor = true;
          AbstractMethodDeclaration method =(AbstractMethodDeclaration)toTree(member);
          methods.add(method);
        } else if (member instanceof lombok.ast.MethodDeclaration ||
            member instanceof lombok.ast.AnnotationMethodDeclaration) {
          AbstractMethodDeclaration method =(AbstractMethodDeclaration)toTree(member);
          methods.add(method);
        } else if (member instanceof lombok.ast.VariableDeclaration) {
          for (FieldDeclaration field : toList(FieldDeclaration.class, member)) {
            fields.add(field);
          }
        } else if (member instanceof lombok.ast.StaticInitializer) {
          fields.add((FieldDeclaration) toTree(member));
        } else if (member instanceof lombok.ast.InstanceInitializer) {
          fields.add((FieldDeclaration) toTree(member));
        } else if (member instanceof lombok.ast.TypeDeclaration) {
          TypeDeclaration innerType = (TypeDeclaration) toTree(member);
          //TODO check if you need to do this too for static inners.
          if (innerType != null) {
            innerType.enclosingType = decl;
            types.add(innerType);
          }
        } else {
          throw new RuntimeException("Unhandled member type " + member.getClass().getSimpleName());
        }
       
      }
     
      if (!hasExplicitConstructor && canHaveConstructor) {
        ConstructorDeclaration defaultConstructor = new ConstructorDeclaration(compilationResult);
        defaultConstructor.bits |= ASTNode.IsDefaultConstructor;
        defaultConstructor.constructorCall = new ExplicitConstructorCall(ExplicitConstructorCall.ImplicitSuper);
        defaultConstructor.modifiers = decl.modifiers & VISIBILITY_MASK;
        defaultConstructor.selector = toName(type.astName());
        defaultConstructor.sourceStart = defaultConstructor.declarationSourceStart =
            defaultConstructor.constructorCall.sourceStart =
            start(type.astName());
        defaultConstructor.sourceEnd = defaultConstructor.bodyEnd =
            defaultConstructor.constructorCall.sourceEnd =
            defaultConstructor.declarationSourceEnd =
            end(type.astName());
        methods.add(0, defaultConstructor);
      }
     
      decl.memberTypes = toArray(TypeDeclaration.class, types);
      decl.methods = toArray(AbstractMethodDeclaration.class, methods);
      decl.fields = toArray(FieldDeclaration.class, fields);
      if (bubblingFlags.contains(BubblingFlags.ASSERT)) {
        decl.bits |= ASTNode.ContainsAssertion;
      }
      if (bubblingFlags.remove(BubblingFlags.ABSTRACT_METHOD)) {
        decl.bits |= ASTNode.HasAbstractMethods;
      }
     
      decl.addClinit();
      return decl;
    }
   
    @Override
    public boolean visitExpressionStatement(lombok.ast.ExpressionStatement node) {
      Statement statement = toStatement(node.astExpression());
      try {
        Field f = statement.getClass().getField("statementEnd");
        f.set(statement, end(node));
      } catch (Exception ignore) {
        // Not all these classes may have a statementEnd.
      }
      return set(node, statement);
    }
   
    @Override
    public boolean visitConstructorInvocation(lombok.ast.ConstructorInvocation node) {
      AllocationExpression inv;
      if (node.astQualifier() != null || node.astAnonymousClassBody() != null) {
        if (node.astAnonymousClassBody() != null) {
          TypeDeclaration decl = createTypeBody(node.astAnonymousClassBody().astMembers(), null, false, 0);
          Position ecjSigPos = getConversionPositionInfo(node, "signature");
          decl.sourceStart = ecjSigPos == null ? start(node.rawTypeReference()) : ecjSigPos.getStart();
          decl.sourceEnd = ecjSigPos == null ? posOfStructure(node, ")", false) - 1 : ecjSigPos.getEnd() - 1;
          decl.declarationSourceStart = decl.sourceStart;
          decl.declarationSourceEnd = end(node);
          decl.name = CharOperation.NO_CHAR;
          decl.bits |= ASTNode.IsAnonymousType | ASTNode.IsLocalType;
          bubblingFlags.add(BubblingFlags.LOCALTYPE);
          inv = new QualifiedAllocationExpression(decl);
        } else {
          inv = new QualifiedAllocationExpression();
        }
        if (node.astQualifier() != null) {
          ((QualifiedAllocationExpression)inv).enclosingInstance = toExpression(node.astQualifier());
        }
       
      } else {
        inv = new AllocationExpression();
      }
     
      if (!node.astConstructorTypeArguments().isEmpty()) {
        inv.typeArguments = toArray(TypeReference.class, node.astConstructorTypeArguments());
      }
      inv.type = (TypeReference) toTree(node.astTypeReference());
      inv.arguments = toArray(Expression.class, node.astArguments());
      inv.sourceStart = start(node);
      inv.sourceEnd = end(node);
      return set(node, inv);
    }
   
    @Override
    public boolean visitAlternateConstructorInvocation(lombok.ast.AlternateConstructorInvocation node) {
      ExplicitConstructorCall inv = new ExplicitConstructorCall(ExplicitConstructorCall.This);
      inv.sourceStart = posOfStructure(node, "this", true);
      inv.sourceEnd = end(node);
  //    inv.modifiers = decl.modifiers & VISIBILITY_MASK;
      if (!node.astConstructorTypeArguments().isEmpty()) {
        inv.typeArguments = toArray(TypeReference.class, node.astConstructorTypeArguments());
        Position ecjTypeArgsPos = getConversionPositionInfo(node, "typeArguments");
        inv.typeArgumentsSourceStart = ecjTypeArgsPos == null ? posOfStructure(node, "<", true) : ecjTypeArgsPos.getStart();
      }
      inv.arguments = toArray(Expression.class, node.astArguments());
      return set(node, inv);
    }
   
    @Override
    public boolean visitSuperConstructorInvocation(lombok.ast.SuperConstructorInvocation node) {
      ExplicitConstructorCall inv = new ExplicitConstructorCall(ExplicitConstructorCall.Super);
      inv.sourceStart = start(node);
      inv.sourceEnd = end(node);
  //    inv.modifiers = decl.modifiers & VISIBILITY_MASK;
      if (!node.astConstructorTypeArguments().isEmpty()) {
        inv.typeArguments = toArray(TypeReference.class, node.astConstructorTypeArguments());
        Position ecjTypeArgsPos = getConversionPositionInfo(node, "typeArguments");
        inv.typeArgumentsSourceStart = ecjTypeArgsPos == null ? posOfStructure(node, "<", true) : ecjTypeArgsPos.getStart();
      }
      inv.arguments = toArray(Expression.class, node.astArguments());
      inv.qualification = toExpression(node.astQualifier());
      return set(node, inv);
    }
 
    @Override
    public boolean visitMethodInvocation(lombok.ast.MethodInvocation node) {
      MessageSend inv = new MessageSend();
      inv.sourceStart = start(node);
      inv.sourceEnd = end(node);
      inv.nameSourcePosition = pos(node.astName());
     
      inv.arguments = toArray(Expression.class, node.astArguments());
      inv.receiver = toExpression(node.astOperand());
      if (inv.receiver instanceof NameReference) {
        inv.receiver.bits |= Binding.TYPE;
      }
     
      //TODO do we have an implicit this style call somewhere in our test sources?
      if (inv.receiver == null) {
        inv.receiver = new ThisReference(0, 0);
        inv.receiver.bits |= ASTNode.IsImplicitThis;
      }
      if (!node.astMethodTypeArguments().isEmpty()) inv.typeArguments = toArray(TypeReference.class, node.astMethodTypeArguments());
      inv.selector = toName(node.astName());
      return set(node, inv);
    }
   
    @Override
    public boolean visitSuper(lombok.ast.Super node) {
      if (node.astQualifier() == null) {
        return set(node, new SuperReference(start(node), end(node)));
      }
      return set(node, new QualifiedSuperReference((TypeReference) toTree(node.astQualifier()), start(node), end(node)));
    }
   
    @Override
    public boolean visitUnaryExpression(lombok.ast.UnaryExpression node) {
      if (node.astOperator() == UnaryOperator.UNARY_MINUS) {
        if (node.astOperand() instanceof lombok.ast.IntegralLiteral && node.astOperand().getParens() == 0) {
          lombok.ast.IntegralLiteral lit = (lombok.ast.IntegralLiteral)node.astOperand();
          if (!lit.astMarkedAsLong() && lit.astIntValue() == Integer.MIN_VALUE) {
            IntLiteralMinValue minLiteral = new IntLiteralMinValue(
              lit.rawValue().toCharArray(), null, start(node), end(node));
            return set(node, minLiteral);
          }
          if (lit.astMarkedAsLong() && lit.astLongValue() == Long.MIN_VALUE) {
            LongLiteralMinValue minLiteral = new LongLiteralMinValue(
              lit.rawValue().toCharArray(), null, start(node), end(node));
            return set(node, minLiteral);
          }
        }
      }
     
      Expression operand = toExpression(node.astOperand());
      int ecjOperator = UNARY_OPERATORS.get(node.astOperator());
     
      switch (node.astOperator()) {
      case PREFIX_INCREMENT:
      case PREFIX_DECREMENT:
        return set(node, new PrefixExpression(operand, IntLiteral.One, ecjOperator, start(node)));
      case POSTFIX_INCREMENT:
      case POSTFIX_DECREMENT:
        return set(node, new PostfixExpression(operand, IntLiteral.One, ecjOperator, end(node)));
      default:
        UnaryExpression expr = new UnaryExpression(toExpression(node.astOperand()), ecjOperator);
        expr.sourceStart = start(node);
        expr.sourceEnd = end(node);
        return set(node, expr);
      }
    }
   
    @Override
    public boolean visitBinaryExpression(lombok.ast.BinaryExpression node) {
      Expression lhs = toExpression(node.astLeft());
      Expression rhs = toExpression(node.astRight());
     
      if (node.astOperator() == BinaryOperator.ASSIGN) {
        return set(node, posParen(new Assignment(lhs, rhs, end(node)), node));
      }
     
      //TODO add a test with 1 + 2 + 3 + "" + 4 + 5 + 6 + "foo"; as well as 5 + 2 + 3 - 5 - 8 -7 - 8 * 10 + 20;
     
      int ecjOperator = BINARY_OPERATORS.get(node.astOperator());
      if (node.astOperator().isAssignment()) {
        return set(node, posParen(new CompoundAssignment(lhs, rhs, ecjOperator, end(node)), node));
      } else if (node.astOperator() == BinaryOperator.EQUALS || node.astOperator() == BinaryOperator.NOT_EQUALS) {
        return set(node, posParen(new EqualExpression(lhs, rhs, ecjOperator), node));
      } else if (node.astOperator() == BinaryOperator.LOGICAL_AND) {
        return set(node, posParen(new AND_AND_Expression(lhs, rhs, ecjOperator), node));
      } else if (node.astOperator() == BinaryOperator.LOGICAL_OR) {
        return set(node, posParen(new OR_OR_Expression(lhs, rhs, ecjOperator), node));
      } else if (node.astOperator() == BinaryOperator.PLUS && node.astLeft().getParens() == 0) {
        Expression stringConcatExpr = posParen(tryStringConcat(lhs, rhs), node);
        if (stringConcatExpr != null) return set(node, stringConcatExpr);
      }
     
      return set(node, posParen(new BinaryExpression(lhs, rhs, ecjOperator), node));
    }
   
    private Expression tryStringConcat(Expression lhs, Expression rhs) {
      if (options.parseLiteralExpressionsAsConstants) {
        if (lhs instanceof ExtendedStringLiteral) {
          if (rhs instanceof CharLiteral) {
            return ((ExtendedStringLiteral)lhs).extendWith((CharLiteral)rhs);
          } else if (rhs instanceof StringLiteral) {
            return ((ExtendedStringLiteral)lhs).extendWith((StringLiteral)rhs);
          }
        } else if (lhs instanceof StringLiteral) {
          if (rhs instanceof CharLiteral) {
            return new ExtendedStringLiteral((StringLiteral)lhs, (CharLiteral)rhs);
          } else if (rhs instanceof StringLiteral) {
            return new ExtendedStringLiteral((StringLiteral)lhs, (StringLiteral)rhs);
          }
        }
      } else {
        if (lhs instanceof StringLiteralConcatenation) {
          if (rhs instanceof StringLiteral) {
            return ((StringLiteralConcatenation)lhs).extendsWith((StringLiteral)rhs);
          }
        } else if (lhs instanceof StringLiteral) {
          if (rhs instanceof StringLiteral) {
            return new StringLiteralConcatenation((StringLiteral) lhs, (StringLiteral) rhs);
          }
        }
      }
     
      return null;
    }
   
    @Override
    public boolean visitCast(lombok.ast.Cast node) {
      Expression typeRef = toExpression(node.astTypeReference());
      Expression operand = toExpression(node.astOperand());
     
      CastExpression expr = createCastExpression(typeRef, operand);
     
      Position ecjTypePos = getConversionPositionInfo(node, "type");
      typeRef.sourceStart = ecjTypePos == null ? posOfStructure(node, "(", true) + 1 : ecjTypePos.getStart();
      typeRef.sourceEnd = ecjTypePos == null ? posOfStructure(node, ")", 0, false) - 2 : ecjTypePos.getEnd() - 1;
      expr.sourceStart = start(node);
      expr.sourceEnd = end(node);
      return set(node, expr);
    }
   
    @SneakyThrows
    private CastExpression createCastExpression(Expression typeRef, Expression operand) {
      try {
        return (CastExpression) CastExpression.class.getConstructors()[0].newInstance(operand, typeRef);
      } catch (InvocationTargetException e) {
        throw e.getCause();
      }
    }
   
    @Override
    public boolean visitInstanceOf(lombok.ast.InstanceOf node) {
      return set(node, new InstanceOfExpression(toExpression(node.astObjectReference()), (TypeReference) toTree(node.astTypeReference())));
    }
   
    @Override
    public boolean visitInlineIfExpression(lombok.ast.InlineIfExpression node) {
      return set(node, new ConditionalExpression(toExpression(node.astCondition()), toExpression(node.astIfTrue()), toExpression(node.astIfFalse())));
    }
   
    @Override
    public boolean visitSelect(lombok.ast.Select node) {
      //TODO for something like ("" + "").foo.bar;
      /* try chain-of-identifiers */ {
        List<lombok.ast.Identifier> selects = Lists.newArrayList();
        List<Long> pos = Lists.newArrayList();
        lombok.ast.Select current = node;
        while (true) {
          selects.add(current.astIdentifier());
          pos.add(pos(current.astIdentifier()));
          if (current.astOperand() instanceof lombok.ast.Select) current = (lombok.ast.Select) current.astOperand();
          else if (current.astOperand() instanceof lombok.ast.VariableReference) {
            selects.add(((lombok.ast.VariableReference) current.astOperand()).astIdentifier());
            pos.add(pos(current.rawOperand()));
            Collections.reverse(selects);
            long[] posArray = new long[pos.size()];
            for (int i = 0; i < posArray.length; i++) posArray[i] = pos.get(posArray.length - i - 1);
            char[][] tokens = chain(selects, selects.size());
            QualifiedNameReference ref = new QualifiedNameReference(tokens, posArray, start(node), end(node));
            return set(node, ref);
          } else {
            break;
          }
        }
      }
     
      FieldReference ref = new FieldReference(toName(node.astIdentifier()), pos(node));
      ref.nameSourcePosition = pos(node.astIdentifier());
      ref.receiver = toExpression(node.astOperand());
     
      //TODO ("" + 10).a.b = ... DONT forget to doublecheck var/type restriction flags
      return set(node, ref);
    }
   
    @Override
    public boolean visitTypeReference(lombok.ast.TypeReference node) {
      // TODO make sure there's a test case that covers every eclipsian type ref: hierarchy on "org.eclipse.jdt.internal.compiler.ast.TypeReference".
     
      Wildcard wildcard = null;
      TypeReference ref = null;
     
      switch (node.astWildcard()) {
      case UNBOUND:
        wildcard = new Wildcard(Wildcard.UNBOUND);
        wildcard.sourceStart = start(node);
        wildcard.sourceEnd = end(node);
        return set(node, wildcard);
      case EXTENDS:
        wildcard = new Wildcard(Wildcard.EXTENDS);
        break;
      case SUPER:
        wildcard = new Wildcard(Wildcard.SUPER);
        break;
      }
     
      char[][] qualifiedName = null;
      char[] singleName = null;
      boolean qualified = node.astParts().size() != 1;
      int dims = node.astArrayDimensions();
      TypeReference[][] params = new TypeReference[node.astParts().size()][];
      boolean hasGenerics = false;
     
      if (!qualified) {
        singleName = toName(node.astParts().first().astIdentifier());
      } else {
        List<lombok.ast.Identifier> identifiers = Lists.newArrayList();
        for (lombok.ast.TypeReferencePart part : node.astParts()) identifiers.add(part.astIdentifier());
        qualifiedName = chain(identifiers, identifiers.size());
      }
     
      {
        int ctr = 0;
        for (lombok.ast.TypeReferencePart part : node.astParts()) {
          params[ctr] = new TypeReference[part.astTypeArguments().size()];
          int ctr2 = 0;
          boolean partHasGenerics = false;
          for (lombok.ast.TypeReference x : part.astTypeArguments()) {
            hasGenerics = true;
            partHasGenerics = true;
            params[ctr][ctr2++] = (TypeReference) toTree(x);
          }
          if (!partHasGenerics) params[ctr] = null;
          ctr++;
        }
      }
     
      if (!qualified) {
        if (!hasGenerics) {
          if (dims == 0) {
            ref = new SingleTypeReference(singleName, partsToPosArray(node.rawParts())[0]);
          } else {
            ref = new ArrayTypeReference(singleName, dims, 0L);
            ref.sourceStart = start(node);
            ref.sourceEnd = end(node);
            ((ArrayTypeReference)ref).originalSourceEnd = end(node.rawParts().last());
          }
        } else {
          ref = new ParameterizedSingleTypeReference(singleName, params[0], dims, partsToPosArray(node.rawParts())[0]);
          if (dims > 0) ref.sourceEnd = end(node);
        }
      } else {
        if (!hasGenerics) {
          if (dims == 0) {
            long[] pos = partsToPosArray(node.rawParts());
            ref = new QualifiedTypeReference(qualifiedName, pos);
          } else {
            long[] pos = partsToPosArray(node.rawParts());
            ref = new ArrayQualifiedTypeReference(qualifiedName, dims, pos);
            ref.sourceEnd = end(node);
          }
        } else {
          //TODO test what happens with generic types that also have an array dimension or two.
          long[] pos = partsToPosArray(node.rawParts());
          ref = new ParameterizedQualifiedTypeReference(qualifiedName, params, dims, pos);
          if (dims > 0) ref.sourceEnd = end(node);
        }
      }
     
      if (wildcard != null) {
        wildcard.bound = ref;
        ref = wildcard;
        ref.sourceStart = start(node);
        ref.sourceEnd = wildcard.bound.sourceEnd;
      }
     
      return set(node, ref);
    }
   
    @Override
    public boolean visitTypeVariable(lombok.ast.TypeVariable node) {
      // TODO test multiple bounds on a variable, e.g. <T extends A & B & C>
      TypeParameter param = new TypeParameter();
      param.declarationSourceStart = start(node);
      param.declarationSourceEnd = end(node);
      param.sourceStart = start(node.astName());
      param.sourceEnd = end(node.astName());
      param.name = toName(node.astName());
      if (!node.astExtending().isEmpty()) {
        TypeReference[] p = toArray(TypeReference.class, node.astExtending());
        for (TypeReference t : p) t.bits |= ASTNode.IsSuperType;
        param.type = p[0];
        if (p.length > 1) {
          param.bounds = new TypeReference[p.length - 1];
          System.arraycopy(p, 1, param.bounds, 0, p.length - 1);
        }
        param.declarationSourceEnd = p[p.length - 1].sourceEnd;
      }
     
      return set(node, param);
    }
   
    @Override
    public boolean visitStaticInitializer(lombok.ast.StaticInitializer node) {
      Initializer init = new Initializer((Block) toTree(node.astBody()), ClassFileConstants.AccStatic);
      init.declarationSourceStart = start(node);
      init.sourceStart = start(node.astBody());
      init.sourceEnd = init.declarationSourceEnd = end(node);
      init.bodyStart = init.sourceStart + 1;
      init.bodyEnd = init.sourceEnd - 1;
      return set(node, init);
    }
 
    @Override
    public boolean visitInstanceInitializer(lombok.ast.InstanceInitializer node) {
      Initializer init = new Initializer((Block) toTree(node.astBody()), 0);
      if (bubblingFlags.remove(BubblingFlags.LOCALTYPE)) {
        init.bits |= ASTNode.HasLocalType;
      }
      init.sourceStart = init.declarationSourceStart = start(node);
      init.sourceEnd = init.declarationSourceEnd = end(node);
      init.bodyStart = init.sourceStart + 1;
      init.bodyEnd = init.sourceEnd - 1;
      return set(node, init);
    }
   
    @Override
    public boolean visitIntegralLiteral(lombok.ast.IntegralLiteral node) {
      if (node.astMarkedAsLong()) {
        return set(node, LongLiteral.buildLongLiteral(node.rawValue().toCharArray(), start(node), end(node)));
      }
      return set(node, IntLiteral.buildIntLiteral(node.rawValue().toCharArray(), start(node), end(node)));
    }
   
    @Override
    public boolean visitFloatingPointLiteral(lombok.ast.FloatingPointLiteral node) {
      if (node.astMarkedAsFloat()) {
        return set(node, new FloatLiteral(node.rawValue().toCharArray(), start(node), end(node)));
      }
      return set(node, new DoubleLiteral(node.rawValue().toCharArray(), start(node), end(node)));
    }
   
    @Override
    public boolean visitBooleanLiteral(lombok.ast.BooleanLiteral node) {
      return set(node, node.astValue() ? new TrueLiteral(start(node), end(node)) : new FalseLiteral(start(node), end(node)));
    }
   
    @Override
    public boolean visitNullLiteral(lombok.ast.NullLiteral node) {
      return set(node, new NullLiteral(start(node), end(node)));
    }
   
    @Override
    public boolean visitVariableReference(VariableReference node) {
      SingleNameReference ref = new SingleNameReference(toName(node.astIdentifier()), pos(node));
      return set(node, ref);
    }
   
    @Override
    public boolean visitIdentifier(lombok.ast.Identifier node) {
      SingleNameReference ref = new SingleNameReference(toName(node), pos(node));
      return set(node, ref);
    }
   
    @Override
    public boolean visitCharLiteral(lombok.ast.CharLiteral node) {
      return set(node, new CharLiteral(node.rawValue().toCharArray(), start(node), end(node)));
    }
   
    @Override
    public boolean visitStringLiteral(lombok.ast.StringLiteral node) {
      return set(node, new StringLiteral(node.astValue().toCharArray(), start(node), end(node), 0));
    }
   
    @Override
    public boolean visitBlock(lombok.ast.Block node) {
      Block block = new Block(0);
      block.statements = toArray(Statement.class, node.astContents());
      if (block.statements == null) {
        if (isUndocumented(node)) block.bits |= ASTNode.UndocumentedEmptyBlock;
      } else {
        block.explicitDeclarations = calculateExplicitDeclarations(node.astContents());
      }
      block.sourceStart = start(node);
      block.sourceEnd = end(node);
      return set(node, block);
    }
   
    @Override
    public boolean visitAnnotationValueArray(AnnotationValueArray node) {
      ArrayInitializer init = new ArrayInitializer();
      init.sourceStart = start(node);
      init.sourceEnd = end(node);
      init.expressions = toArray(Expression.class, node.astValues());
     
      return set(node, init);
    }
   
    @Override
    public boolean visitArrayInitializer(lombok.ast.ArrayInitializer node) {
      ArrayInitializer init = new ArrayInitializer();
      init.sourceStart = start(node);
      init.sourceEnd = end(node);
      init.expressions = toArray(Expression.class, node.astExpressions());
     
      return set(node, init);
    }
   
    @Override
    public boolean visitArrayCreation(lombok.ast.ArrayCreation node) {
      ArrayAllocationExpression aae = new ArrayAllocationExpression();
      aae.sourceStart = start(node);
      aae.sourceEnd = end(node);
      aae.type = (TypeReference) toTree(node.astComponentTypeReference());
      // TODO uncompilable parser test: new Type<Generics>[]...
      // TODO uncompilable parser test: new Type[][expr][][expr]...
      aae.type.bits |= ASTNode.IgnoreRawTypeCheck;
     
      int i = 0;
      Expression[] dimensions = new Expression[node.astDimensions().size()];
      for (lombok.ast.ArrayDimension dim : node.astDimensions()) {
        dimensions[i++] = (Expression) toTree(dim.astDimension());
      }
      aae.dimensions = dimensions;
      aae.initializer = (ArrayInitializer) toTree(node.astInitializer());
      return set(node, aae);
    }
   
    @Override
    public boolean visitArrayDimension(lombok.ast.ArrayDimension node) {
      return set(node, toExpression(node.astDimension()));
    }
   
    @Override
    public boolean visitThis(lombok.ast.This node) {
      if (node.astQualifier() == null) {
        return set(node, new ThisReference(start(node), end(node)));
      }
      return set(node, new QualifiedThisReference((TypeReference) toTree(node.astQualifier()), start(node), end(node)));
    }
   
    @Override
    public boolean visitClassLiteral(lombok.ast.ClassLiteral node) {
      return set(node, new ClassLiteralAccess(end(node), (TypeReference) toTree(node.astTypeReference())));
    }
   
    @Override
    public boolean visitArrayAccess(lombok.ast.ArrayAccess node) {
      ArrayReference ref = new ArrayReference(toExpression(node.astOperand()), toExpression(node.astIndexExpression()));
      ref.sourceEnd = end(node);
      return set(node, ref);
    }
   
    @Override
    public boolean visitAssert(lombok.ast.Assert node) {
      //TODO check the flags after more test have been added: asserts in constructors, methods etc.
      bubblingFlags.add(BubblingFlags.ASSERT);
      if (node.astMessage() == null) {
        return set(node, new AssertStatement(toExpression(node.astAssertion()), start(node)));
      }
      return set(node, new AssertStatement(toExpression(node.astMessage()), toExpression(node.astAssertion()), start(node)));
    }
   
    @Override
    public boolean visitDoWhile(lombok.ast.DoWhile node) {
      return set(node, new DoStatement(toExpression(node.astCondition()), toStatement(node.astStatement()), start(node), end(node)));
    }
   
    @Override
    public boolean visitContinue(lombok.ast.Continue node) {
      return set(node, new ContinueStatement(toName(node.astLabel()), start(node), end(node)));
    }
   
    @Override
    public boolean visitBreak(lombok.ast.Break node) {
      return set(node, new BreakStatement(toName(node.astLabel()), start(node), end(node)));
    }
   
    @Override
    public boolean visitForEach(lombok.ast.ForEach node) {
      ForeachStatement forEach = new ForeachStatement((LocalDeclaration) toTree(node.astVariable()), start(node));
      forEach.sourceEnd = end(node);
      forEach.collection = toExpression(node.astIterable());
      forEach.action = toStatement(node.astStatement());
      return set(node, forEach);
    }
   
    @Override
    public boolean visitVariableDeclaration(lombok.ast.VariableDeclaration node) {
      List<AbstractVariableDeclaration> list = toList(AbstractVariableDeclaration.class, node.astDefinition());
      if (list.size() > 0) setupJavadoc(list.get(0), node);
      return set(node, list);
    }
   
    @Override
    public boolean visitVariableDefinition(lombok.ast.VariableDefinition node) {
      List<AbstractVariableDeclaration> values = Lists.newArrayList();
      Annotation[] annotations = toArray(Annotation.class, node.astModifiers().astAnnotations());
      int modifiers = toModifiers(node.astModifiers());
      TypeReference base = (TypeReference) toTree(node.astTypeReference());
      AbstractVariableDeclaration prevDecl = null, firstDecl = null;
      for (lombok.ast.VariableDefinitionEntry entry : node.astVariables()) {
        VariableKind kind = VariableKind.kind(node);
        AbstractVariableDeclaration decl = kind.create();
        decl.annotations = annotations;
        decl.initialization = toExpression(entry.astInitializer());
        decl.modifiers = modifiers;
        decl.name = toName(entry.astName());
        if (entry.astArrayDimensions() == 0 && !node.astVarargs()) {
          decl.type = base;
        } else if (entry.astArrayDimensions() > 0 || node.astVarargs()) {
          decl.type = (TypeReference) toTree(entry.getEffectiveTypeReference());
          decl.type.sourceStart = base.sourceStart;
          Position ecjTypeSourcePos = getConversionPositionInfo(entry, "typeSourcePos");
          if (ecjTypeSourcePos != null) {
            decl.type.sourceEnd = ecjTypeSourcePos.getEnd() - 1;
          } else {
            // This makes no sense whatsoever but eclipse wants it this way.
            if (firstDecl == null && (base.dimensions() > 0 || node.getParent() instanceof lombok.ast.ForEach)) {
              decl.type.sourceEnd = posOfStructure(entry, "]", false) - 1;
            } else if (firstDecl != null) {
              // This replicates an eclipse bug; the end pos of the type of b in: int[] a[][], b[]; is in fact the second closing ] of a.
              decl.type.sourceEnd = firstDecl.type.sourceEnd;
            } else decl.type.sourceEnd = base.sourceEnd;
            // Yet another eclipse inconsistency.
            if (kind == VariableKind.FIELD && base instanceof ArrayQualifiedTypeReference) {
              long[] poss = ((ArrayQualifiedTypeReference)base).sourcePositions;
              decl.type.sourceEnd = (int) poss[poss.length - 1];
            }
          }
          if (node.astVarargs()) {
            if (decl.type instanceof ArrayTypeReference) {
              ((ArrayTypeReference)decl.type).originalSourceEnd = decl.type.sourceEnd;
            }
            Position ecjTyperefPos = getConversionPositionInfo(node, "typeref");
            decl.type.sourceEnd = ecjTyperefPos == null ? posOfStructure(node, "...", false) - 1 : ecjTyperefPos.getEnd() - 1;
          } else {
            if (decl.type instanceof ArrayTypeReference) {
              ((ArrayTypeReference)decl.type).originalSourceEnd = decl.type.sourceEnd;
            }
            if (decl.type instanceof ArrayQualifiedTypeReference) {
              ((ArrayQualifiedTypeReference)decl.type).sourcePositions = ((QualifiedTypeReference)base).sourcePositions.clone();
            }
          }
        }
        if (node.astVarargs()) {
          decl.type.bits |= ASTNode.IsVarArgs;
        }
        if (decl instanceof FieldDeclaration) {
          if (bubblingFlags.remove(BubblingFlags.LOCALTYPE)) {
            decl.bits |= ASTNode.HasLocalType;
          }
        }
       
        decl.sourceStart = start(entry.astName());
        decl.sourceEnd = end(entry.astName());
        decl.declarationSourceStart = jstart(node);
        switch (kind) {
        case LOCAL:
          int end;
          if (node.getParent() instanceof lombok.ast.VariableDeclaration) end = end(node.getParent());
          else {
            if (entry.rawInitializer() != null) end = end(entry.rawInitializer());
            else end = end(entry.astName());
          }
          decl.declarationSourceEnd = decl.declarationEnd = end;
          Position ecjDeclarationSourcePos = getConversionPositionInfo(entry, "declarationSource");
          if (ecjDeclarationSourcePos != null) decl.declarationSourceEnd = ecjDeclarationSourcePos.getEnd() - 1;
          break;
        case ARGUMENT:
          decl.declarationSourceEnd = decl.declarationEnd = end(entry.astName());
          ecjDeclarationSourcePos = getConversionPositionInfo(entry, "declarationSource");
          if (ecjDeclarationSourcePos != null) decl.declarationSourceEnd = ecjDeclarationSourcePos.getEnd() - 1;
          break;
        case FIELD:
          decl.declarationSourceEnd = decl.declarationEnd = end(node.getParent());
          ecjDeclarationSourcePos = getConversionPositionInfo(entry, "declarationSource");
          Position ecjPart1Pos = getConversionPositionInfo(entry, "varDeclPart1");
          Position ecjPart2Pos = getConversionPositionInfo(entry, "varDeclPart2");
          if (ecjDeclarationSourcePos != null) decl.declarationSourceEnd = ecjDeclarationSourcePos.getEnd() - 1;
          ((FieldDeclaration)decl).endPart1Position = ecjPart1Pos == null ? end(node.rawTypeReference()) + 1 : ecjPart1Pos.getEnd() - 1;
          ((FieldDeclaration)decl).endPart2Position = ecjPart2Pos == null ? end(node.getParent()) : ecjPart2Pos.getEnd() - 1;
          if (ecjPart2Pos == null && prevDecl instanceof FieldDeclaration) {
            ((FieldDeclaration)prevDecl).endPart2Position = start(entry) - 1;
          }
          break;
        }
        values.add(decl);
        prevDecl = decl;
        if (firstDecl == null) firstDecl = decl;
      }
     
      return set(node, values);
    }
   
    @Override
    public boolean visitIf(lombok.ast.If node) {
      if (node.astElseStatement() == null) {
        return set(node, new IfStatement(toExpression(node.astCondition()), toStatement(node.astStatement()), start(node), end(node)));
      }
      return set(node, new IfStatement(toExpression(node.astCondition()), toStatement(node.astStatement()), toStatement(node.astElseStatement()), start(node), end(node)));
    }
   
    @Override
    public boolean visitLabelledStatement(lombok.ast.LabelledStatement node) {
      return set(node, new LabeledStatement(toName(node.astLabel()), toStatement(node.astStatement()), pos(node.astLabel()), end(node)));
    }
   
    @Override
    public boolean visitFor(lombok.ast.For node) {
      //TODO make test for modifiers on variable declarations
      //TODO make test for empty for/foreach etc.
      if(node.isVariableDeclarationBased()) {
        return set(node, new ForStatement(toArray(Statement.class, node.astVariableDeclaration()),
            toExpression(node.astCondition()),
            toArray(Statement.class, node.astUpdates()),
            toStatement(node.astStatement()),
            true, start(node), end(node)));
      }
      return set(node, new ForStatement(toArray(Statement.class, node.astExpressionInits()),
          toExpression(node.astCondition()),
          toArray(Statement.class, node.astUpdates()),
          toStatement(node.astStatement()),
          false, start(node), end(node)));
    }
   
    @Override
    public boolean visitSwitch(lombok.ast.Switch node) {
      SwitchStatement value = new SwitchStatement();
      value.sourceStart = start(node);
      value.sourceEnd = end(node);
      value.blockStart = start(node.rawBody());
      value.expression = toExpression(node.astCondition());
      value.statements = toArray(Statement.class, node.astBody().astContents());
      if (value.statements == null) {
        if (isUndocumented(node.astBody())) value.bits |= ASTNode.UndocumentedEmptyBlock;
      } else {
        value.explicitDeclarations = calculateExplicitDeclarations(node.astBody().astContents());
      }
      return set(node, value);
    }
   
    @Override
    public boolean visitSynchronized(lombok.ast.Synchronized node) {
      return set(node, new SynchronizedStatement(toExpression(node.astLock()), (Block) toTree(node.astBody()), start(node), end(node)));
    }
   
    @Override
    public boolean visitTry(lombok.ast.Try node) {
      TryStatement tryStatement = new TryStatement();
      tryStatement.sourceStart = start(node);
      tryStatement.sourceEnd = end(node);
     
      tryStatement.tryBlock = (Block) toTree(node.astBody());
      int catchSize = node.astCatches().size();
      if (catchSize > 0) {
        tryStatement.catchArguments = new Argument[catchSize];
        tryStatement.catchBlocks = new Block[catchSize];
        int i = 0;
        for (lombok.ast.Catch c : node.astCatches()) {
          tryStatement.catchArguments[i] = (Argument)toTree(c.astExceptionDeclaration());
          tryStatement.catchBlocks[i] = (Block) toTree(c.astBody());
          i++;
        }
      }
      tryStatement.finallyBlock = (Block) toTree(node.astFinally());
      return set(node, tryStatement);
    }
   
    @Override
    public boolean visitThrow(lombok.ast.Throw node) {
      return set(node, new ThrowStatement(toExpression(node.astThrowable()), start(node), end(node)));
    }
   
    @Override
    public boolean visitWhile(lombok.ast.While node) {
      return set(node, new WhileStatement(toExpression(node.astCondition()), toStatement(node.astStatement()), start(node), end(node)));
    }
   
    @Override
    public boolean visitReturn(lombok.ast.Return node) {
      return set(node, new ReturnStatement(toExpression(node.astValue()), start(node), end(node)));
    }
   
    @Override
    public boolean visitAnnotation(lombok.ast.Annotation node) {
      //TODO add test where the value is the result of string concatenation
      TypeReference type = (TypeReference) toTree(node.astAnnotationTypeReference());
      boolean isEcjNormal = Position.UNPLACED == getConversionPositionInfo(node, "isNormalAnnotation");
     
      if (node.astElements().isEmpty() && countStructure(node, "(") == 0 && !isEcjNormal) {
        MarkerAnnotation ann = new MarkerAnnotation(type, start(node));
        ann.declarationSourceEnd = end(node);
        return set(node, ann);
      }
      MemberValuePair[] values = toArray(MemberValuePair.class, node.astElements());
      if (values != null && (values.length == 1 && values[0].name == null)) {
        SingleMemberAnnotation ann = new SingleMemberAnnotation(type, start(node));
        ann.declarationSourceEnd = end(node);
        ann.memberValue = values[0].value;
        return set(node, ann);
      }
      NormalAnnotation ann = new NormalAnnotation(type, start(node));
      ann.declarationSourceEnd = end(node);
      ann.memberValuePairs = values;
      return set(node, ann);
    }
   
    @Override
    public boolean visitAnnotationElement(lombok.ast.AnnotationElement node) {
      //TODO make a test where the array initializer is the default value
      MemberValuePair pair = new MemberValuePair(toName(node.astName()), start(node), end(node.astName()), null);
      // giving the value to the constructor will set the ASTNode.IsAnnotationDefaultValue flag
      pair.value = toExpression(node.astValue());
      if (pair.name != null && pair.value instanceof ArrayInitializer) {
        pair.value.bits |= ASTNode.IsAnnotationDefaultValue;
      }
      return set(node, pair);
    }
   
   
    @Override
    public boolean visitCase(lombok.ast.Case node) {
      // end and start args are switched around on CaseStatement, presumably because the API designer was drunk at the time.
      return set(node, new CaseStatement(toExpression(node.astCondition()), end(node.rawCondition()), start(node)));
    }
   
    @Override
    public boolean visitDefault(lombok.ast.Default node) {
      // end and start args are switched around on CaseStatement, presumably because the API designer was drunk at the time.
      return set(node, new CaseStatement(null, posOfStructure(node, "default", false) - 1, start(node)));
    }
   
    @Override
    public boolean visitComment(lombok.ast.Comment node) {
      if (!node.isJavadoc()) {
        throw new RuntimeException("Only javadoc expected here");
      }
     
      Node parent = node.getParent();
      parent = parent == null ? null : parent.getParent();
      while (parent != null && !(parent instanceof lombok.ast.TypeDeclaration)) parent = parent.getParent();
      String typeName = null;
      if (parent instanceof lombok.ast.TypeDeclaration) {
        Identifier identifier = ((lombok.ast.TypeDeclaration)parent).astName();
        if (identifier != null) typeName = identifier.astValue();
      }
     
      if (typeName == null) {
        typeName = getTypeNameFromFileName(compilationResult.getFileName());
      }
     
      return set(node, new JustJavadocParser(silentProblemReporter, typeName).parse(rawInput, node.getPosition().getStart(), node.getPosition().getEnd()));
    }
   
    @Override
    public boolean visitEmptyStatement(lombok.ast.EmptyStatement node) {
      return set(node, new EmptyStatement(start(node), end(node)));
    }
   
    @Override
    public boolean visitEmptyDeclaration(lombok.ast.EmptyDeclaration node) {
      return set(node, (ASTNode)null);
    }
   
    private int toModifiers(lombok.ast.Modifiers modifiers) {
      return modifiers.getExplicitModifierFlags();
    }
   
    @Override
    public boolean visitNode(lombok.ast.Node node) {
      throw new UnsupportedOperationException(String.format("Unhandled node '%s' (%s)", node, node.getClass().getSimpleName()));
    }
   
    private char[][] chain(lombok.ast.StrictListAccessor<lombok.ast.Identifier, ?> parts) {
      return chain(parts, parts.size());
    }
   
    private char[][] chain(Iterable<lombok.ast.Identifier> parts, int size) {
      char[][] c = new char[size][];
      int i = 0;
      for (lombok.ast.Identifier part : parts) {
        c[i++] = part.astValue().toCharArray();
      }
      return c;
    }
   
    private void updateRestrictionFlags(lombok.ast.Node node, NameReference ref) {
      ref.bits &= ~ASTNode.RestrictiveFlagMASK;
      ref.bits |= Binding.VARIABLE;
     
      if (node.getParent() instanceof lombok.ast.MethodInvocation) {
        if (((lombok.ast.MethodInvocation)node.getParent()).astOperand() == node) {
          ref.bits |= Binding.TYPE;
        }
      }
     
      if (node.getParent() instanceof lombok.ast.Select) {
        if (((lombok.ast.Select)node.getParent()).astOperand() == node) {
          ref.bits |= Binding.TYPE;
        }
      }
    }
   
    // Only works for TypeBody, EnumTypeBody and Block
    private boolean isUndocumented(lombok.ast.Node block) {
      if (block == null) return false;
      if (rawInput == null) return false;
     
      lombok.ast.Position pos = block.getPosition();
      if (pos.isUnplaced() || pos.size() < 3) return true;
     
      String content = rawInput.substring(pos.getStart() + 1, pos.getEnd() - 1);
      return content.trim().isEmpty();
    }
  };
 
  private static class JustJavadocParser extends JavadocParser {
    private static final char[] GENERIC_JAVA_CLASS_SUFFIX = "class Y{}".toCharArray();
   
    JustJavadocParser(ProblemReporter reporter, String mainTypeName) {
      super(makeDummyParser(reporter, mainTypeName));
    }
   
    private static Parser makeDummyParser(ProblemReporter reporter, String mainTypeName) {
      Parser parser = new Parser(reporter, false);
      CompilationResult cr = new CompilationResult((mainTypeName + ".java").toCharArray(), 0, 1, 0);
      parser.compilationUnit = new CompilationUnitDeclaration(reporter, cr, 0);
      return parser;
    }
   
    Javadoc parse(String rawInput, int from, int to) {
      char[] rawContent;
     
      rawContent = new char[to + GENERIC_JAVA_CLASS_SUFFIX.length];
      Arrays.fill(rawContent, 0, from, ' ');
      System.arraycopy(rawInput.substring(from, to).toCharArray(), 0, rawContent, from, to - from);
      // Eclipse crashes if there's no character following the javadoc.
      System.arraycopy(GENERIC_JAVA_CLASS_SUFFIX, 0, rawContent, to, GENERIC_JAVA_CLASS_SUFFIX.length);
     
      this.sourceLevel = ClassFileConstants.JDK1_6;
      this.scanner.setSource(rawContent);
      this.source = rawContent;
      this.javadocStart = from;
      this.javadocEnd = to;
      this.reportProblems = true;
      this.docComment = new Javadoc(this.javadocStart, this.javadocEnd);
      commentParse();
      this.docComment.valuePositions = -1;
      this.docComment.sourceEnd--;
      return docComment;
    }
  }
 
  private static int jstart(lombok.ast.Node node) {
    if (node == null) return 0;
    int start = start(node);
    if (node instanceof lombok.ast.JavadocContainer) {
      lombok.ast.Node javadoc = ((lombok.ast.JavadocContainer)node).rawJavadoc();
      if (javadoc != null) return Math.min(start, start(javadoc));
    }
    if (node instanceof lombok.ast.VariableDefinition && node.getParent() instanceof lombok.ast.VariableDeclaration) {
      lombok.ast.Node javadoc = ((lombok.ast.JavadocContainer)node.getParent()).rawJavadoc();
      if (javadoc != null) return Math.min(start, start(javadoc));
    }
    if (node instanceof lombok.ast.Modifiers && node.getParent() instanceof JavadocContainer) {
      lombok.ast.Node javadoc = ((lombok.ast.JavadocContainer)node.getParent()).rawJavadoc();
      if (javadoc != null) return Math.min(start, start(javadoc));
    }
    return start;
  }
 
  private static int start(lombok.ast.Node node) {
    if (node == null || node.getPosition().isUnplaced()) return 0;
    return node.getPosition().getStart();
  }
 
  private static int end(lombok.ast.Node node) {
    if (node == null || node.getPosition().isUnplaced()) return 0;
    return node.getPosition().getEnd() - 1;
  }
 
  private static long pos(lombok.ast.Node n) {
    return (((long)start(n)) << 32) | end(n);
  }
 
  private static long[] partsToPosArray(RawListAccessor<?, ?> parts) {
    long[] pos = new long[parts.size()];
    int idx = 0;
    for (lombok.ast.Node n : parts) {
      if (n instanceof lombok.ast.TypeReferencePart) {
        pos[idx++] = pos(((lombok.ast.TypeReferencePart) n).astIdentifier());
      } else {
        pos[idx++] = pos(n);
      }
    }
    return pos;
  }
 
  private int countStructure(lombok.ast.Node node, String structure) {
    int result = 0;
    if (sourceStructures != null && sourceStructures.containsKey(node)) {
      for (SourceStructure struct : sourceStructures.get(node)) {
        if (structure.equals(struct.getContent())) result++;
      }
    }
   
    return result;
  }
 
  private int posOfStructure(lombok.ast.Node node, String structure, boolean atStart) {
    return posOfStructure(node, structure, atStart ? 0 : Integer.MAX_VALUE, atStart);
  }
 
  private int posOfStructure(lombok.ast.Node node, String structure, int idx, boolean atStart) {
    int start = node.getPosition().getStart();
    int end = node.getPosition().getEnd();
    Integer result = null;
   
    if (sourceStructures != null && sourceStructures.containsKey(node)) {
      for (SourceStructure struct : sourceStructures.get(node)) {
        if (structure.equals(struct.getContent())) {
          result = atStart ? struct.getPosition().getStart() : struct.getPosition().getEnd();
          if (idx-- <= 0) break;
        }
      }
    }
   
    if (result != null) return result;
    return atStart ? start : end;
  }
 
  private static String getTypeNameFromFileName(char[] fileName) {
    String f = new String(fileName);
    int start = Math.max(f.lastIndexOf('/'), f.lastIndexOf('\\'));
    int end = f.lastIndexOf('.');
    if (end == -1) end = f.length();
    return f.substring(start + 1, end);
  }
}
TOP

Related Classes of lombok.ast.ecj.EcjTreeBuilder$JustJavadocParser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.