/*
 * 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;
			}
			
			@Override
			public boolean ignoreAllErrors() {
				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;
			}
			
			@Override
			public boolean ignoreAllErrors() {
				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);
	}
}
