org.codehaus.jdt.groovy.internal.compiler.ast.GroovyCompilationUnitDeclaration.java Source code

Java tutorial

Introduction

Here is the source code for org.codehaus.jdt.groovy.internal.compiler.ast.GroovyCompilationUnitDeclaration.java

Source

/*******************************************************************************
 * Copyright (c) 2009 Codehaus.org, SpringSource, and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Andy Clement     - Initial API and implementation
 *     Andrew Eisenberg - Additional work
 *******************************************************************************/
package org.codehaus.jdt.groovy.internal.compiler.ast;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.codehaus.groovy.GroovyBugError;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.Comment;
import org.codehaus.groovy.ast.ConstructorNode;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.GenericsType;
import org.codehaus.groovy.ast.ImportNode;
import org.codehaus.groovy.ast.ImportNodeCompatibilityWrapper;
import org.codehaus.groovy.ast.InnerClassNode;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.ast.PackageNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.TaskEntry;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.ListExpression;
import org.codehaus.groovy.ast.expr.PropertyExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.control.CompilationUnit;
import org.codehaus.groovy.control.ErrorCollector;
import org.codehaus.groovy.control.MultipleCompilationErrorsException;
import org.codehaus.groovy.control.Phases;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.control.messages.ExceptionMessage;
import org.codehaus.groovy.control.messages.LocatedMessage;
import org.codehaus.groovy.control.messages.Message;
import org.codehaus.groovy.control.messages.SimpleMessage;
import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
import org.codehaus.groovy.syntax.CSTNode;
import org.codehaus.groovy.syntax.PreciseSyntaxException;
import org.codehaus.groovy.syntax.RuntimeParserException;
import org.codehaus.groovy.syntax.SyntaxException;
import org.codehaus.groovy.syntax.Token;
import org.codehaus.groovy.tools.GroovyClass;
import org.eclipse.jdt.core.compiler.CategorizedProblem;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ast.SingleMemberAnnotation;
import org.eclipse.jdt.internal.compiler.ast.MarkerAnnotation;
import org.eclipse.jdt.internal.compiler.ASTVisitor;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
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.ArrayInitializer;
import org.eclipse.jdt.internal.compiler.ast.ArrayQualifiedTypeReference;
import org.eclipse.jdt.internal.compiler.ast.ArrayTypeReference;
import org.eclipse.jdt.internal.compiler.ast.ClassLiteralAccess;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.ImportReference;
import org.eclipse.jdt.internal.compiler.ast.Javadoc;
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.ParameterizedQualifiedTypeReference;
import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference;
import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference;
import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference;
import org.eclipse.jdt.internal.compiler.ast.StringLiteral;
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.Wildcard;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.impl.IrritantSet;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
import org.eclipse.jdt.internal.compiler.lookup.LocalTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment;
import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
import org.eclipse.jdt.internal.compiler.problem.AbortCompilation;
import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;
import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities;
import org.eclipse.jdt.internal.core.util.Util;
import org.objectweb.asm.Opcodes;

/**
 * A subtype of JDT CompilationUnitDeclaration that represents a groovy source file. It overrides methods as appropriate, delegating
 * to the groovy infrastructure.
 * 
 * @author Andy Clement
 */
@SuppressWarnings("restriction")
public class GroovyCompilationUnitDeclaration extends CompilationUnitDeclaration {

    // The groovy compilation unit shared by all files in the same project
    private CompilationUnit groovyCompilationUnit;
    // The groovy sourceunit (a member of the groovyCompilationUnit)
    private SourceUnit groovySourceUnit;
    private CompilerOptions compilerOptions;
    private boolean checkGenerics;
    public static boolean defaultCheckGenerics = false;
    public static boolean earlyTransforms = true;

    private boolean isScript = false;

    private static final boolean DEBUG_TASK_TAGS = false;

    public GroovyCompilationUnitDeclaration(ProblemReporter problemReporter, CompilationResult compilationResult,
            int sourceLength, CompilationUnit groovyCompilationUnit, SourceUnit groovySourceUnit,
            CompilerOptions compilerOptions) {
        super(problemReporter, compilationResult, sourceLength);
        this.groovyCompilationUnit = groovyCompilationUnit;
        this.groovySourceUnit = groovySourceUnit;
        this.compilerOptions = compilerOptions;
        this.checkGenerics = defaultCheckGenerics;
    }

    /**
     * Take the comments information from the parse and apply it to the compilation unit
     */
    private void setComments() {
        List<Comment> groovyComments = this.groovySourceUnit.getComments();
        if (groovyComments == null || groovyComments.size() == 0) {
            return;
        }
        this.comments = new int[groovyComments.size()][2];
        for (int c = 0, max = groovyComments.size(); c < max; c++) {
            Comment groovyComment = groovyComments.get(c);
            this.comments[c] = groovyComment.getPositions(compilationResult.lineSeparatorPositions);
            // System.out.println("Comment recorded on " + groovySourceUnit.getName() + "  " + this.comments[c][0] + ">"
            // + this.comments[c][1]);
        }
    }

    /**
     * Drives the Groovy Compilation Unit for this project through to the specified phase. Yes on a call for one groovy file to
     * processToPhase(), all the groovy files in the project proceed to that phase. This isn't ideal but doesn't necessarily cause a
     * problem. But it does mean progress reporting for the compilation is incorrect as it jumps rather than smoothly going from 1
     * to 100%.
     * 
     * @param phase the phase to process up to
     * @return true if clean processing, false otherwise
     */
    public boolean processToPhase(int phase) {
        boolean alreadyHasProblems = compilationResult.hasProblems();
        // Our replacement error collector doesn't cause an exception, instead they are checked for post 'compile'
        try {
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            try {
                Thread.currentThread().setContextClassLoader(groovyCompilationUnit.getTransformLoader());
                groovyCompilationUnit.compile(phase);
            } finally {
                Thread.currentThread().setContextClassLoader(cl);
            }
            if (groovySourceUnit.getErrorCollector().hasErrors()) {
                recordProblems(groovySourceUnit.getErrorCollector().getErrors());
                return false;
            } else {
                return true;
            }
        } catch (MultipleCompilationErrorsException problems) {
            AbortCompilation abort = getAbortCompilation(problems);
            if (abort != null) {
                System.out.println("Abort compilation");
                throw abort;
            } else {
                System.err.println("exception handling");
                // alternative to catching this is fleshing out the ErrorCollector
                // sub type we have and asking it if there
                // are errors at the end of a run...
                problems.printStackTrace();
                recordProblems(problems.getErrorCollector().getErrors());
            }
        } catch (GroovyBugError gbr) {
            // FIXASC (M3) really, the GBE should not be thrown in the first place
            // we shouldn't need to silently fail here.
            if (alreadyHasProblems) {
                // do not log the error because it is likely to have
                // occurred because of the existing problem
                System.err.println(
                        "Ignoring GroovyBugError since it is likely caused by earlier issues.  Ignored problem is '"
                                + gbr.getMessage() + "'");
            } else {
                boolean reportit = true;
                if (gbr.getCause() instanceof AbortCompilation) {
                    // might be nothing to log - AbortCompilations can occur 'normally' during processing
                    // when jobs are stopped due to any results they produce being stale.
                    AbortCompilation abort = (AbortCompilation) gbr.getCause();
                    if (abort.isSilent) {
                        reportit = false;
                    }
                }
                if (reportit) {
                    System.err.println("Internal Groovy Error --- " + gbr.getBugText());
                    gbr.printStackTrace();
                    // The groovy compiler threw an exception
                    // FIXASC (M3) Should record these errors as a problem on the project
                    // should *not* throw these because of bad syntax in the file
                    Util.log(gbr, "Internal groovy compiler error.");

                    // Also need to record these problems as compiler errors since some users will not think to check the log
                    // This is mostly a fix for problems like those in GRECLIPSE-1420, where a GBError is thrown when it is really
                    // just a syntax problem.
                    groovySourceUnit.getErrorCollector().addError(new SyntaxErrorMessage(
                            new SyntaxException("Internal groovy compiler error.\n" + gbr.getBugText(), gbr, 1, 0),
                            groovySourceUnit));
                    recordProblems(groovySourceUnit.getErrorCollector().getErrors());
                }
            }
        }
        return false;
    }

    private org.eclipse.jdt.internal.compiler.problem.AbortCompilation getAbortCompilation(
            MultipleCompilationErrorsException problems) {
        ErrorCollector collector = problems.getErrorCollector();
        if (collector.getErrorCount() == 1
                && problems.getErrorCollector().getError(0) instanceof ExceptionMessage) {
            Exception abort = ((ExceptionMessage) problems.getErrorCollector().getError(0)).getCause();
            return abort instanceof AbortCompilation ? (AbortCompilation) abort : null;
        }
        return null;
    }

    /**
     * @return the *groovy* compilation unit shared by all files in the same project
     */
    public CompilationUnit getCompilationUnit() {
        return groovyCompilationUnit;
    }

    /**
     * Populate the compilation unit based on the successful parse.
     */
    public void populateCompilationUnitDeclaration() {
        ModuleNode moduleNode = groovySourceUnit.getAST();
        // if (moduleNode.encounteredUnrecoverableError()) {
        // String msg = "Groovy: Unrecoverable error during processing - source file contains invalid syntax";
        // int sev = ProblemSeverities.Error;
        // CategorizedProblem p = new DefaultProblemFactory().createProblem(getFileName(), 0, new String[] { msg }, 0,
        // new String[] { msg }, sev, 0, 1, 1, 0);
        // this.problemReporter.record(p, compilationResult, this);
        // }
        createPackageDeclaration(moduleNode);
        createImports(moduleNode);
        createTypeDeclarations(moduleNode);
    }

    // make protected for testing
    protected void createImports(ModuleNode moduleNode) {
        List<ImportNode> importNodes = moduleNode.getImports();
        List<ImportNode> importPackages = ImportNodeCompatibilityWrapper.getStarImports(moduleNode);
        Map<String, ImportNode> importStatics = ImportNodeCompatibilityWrapper.getStaticImports(moduleNode);
        Map<String, ImportNode> importStaticStars = ImportNodeCompatibilityWrapper.getStaticStarImports(moduleNode);
        if (importNodes.size() > 0 || importPackages.size() > 0 || importStatics.size() > 0
                || importStaticStars.size() > 0) {
            List<ImportReference> importReferences = new ArrayList<ImportReference>();
            for (ImportNode importNode : importNodes) {
                char[][] splits = CharOperation.splitOn('.', importNode.getClassName().toCharArray());
                ImportReference ref = null;
                ClassNode type = importNode.getType();
                int typeStartOffset = startOffset(type);
                int typeEndOffset = endOffset(type);
                if (typeStartOffset == 0) {
                    // not a real import, a fake one created during recovery
                    continue;
                }
                if (importNode.getAlias() != null && importNode.getAlias().length() > 0) {
                    // FIXASC will need extra positional info for the 'as' and the alias
                    ref = new AliasImportReference(importNode.getAlias().toCharArray(), splits,
                            positionsFor(splits, typeStartOffset, typeEndOffset), false,
                            ClassFileConstants.AccDefault);
                } else {
                    ref = new ImportReference(splits, positionsFor(splits, typeStartOffset, typeEndOffset), false,
                            ClassFileConstants.AccDefault);
                }
                ref.sourceEnd = Math.max(typeEndOffset - 1, ref.sourceStart); // For error reporting, Eclipse wants -1
                int start = importNode.getStart();
                ref.declarationSourceStart = start;
                int end = importNode.getEnd();
                ref.declarationSourceEnd = end;

                ref.declarationEnd = ref.sourceEnd;
                importReferences.add(ref);
            }

            for (ImportNode importPackage : importPackages) {
                String importText = importPackage.getText();

                // when calculating these offsets, assume no extraneous whitespace
                int packageStartOffset = importPackage.getStart() + "import ".length();
                int packageEndOffset = packageStartOffset + importText.length() - "import ".length()
                        - ".*".length();

                char[][] splits = CharOperation.splitOn('.', importPackage.getPackageName()
                        .substring(0, importPackage.getPackageName().length() - 1).toCharArray());
                ImportReference ref = new ImportReference(splits,
                        positionsFor(splits, packageStartOffset, packageEndOffset), true,
                        ClassFileConstants.AccDefault);
                // import * style only have slocs for the entire ImportNode and not for the embedded type
                ref.sourceEnd = packageEndOffset;
                ref.declarationSourceStart = importPackage.getStart();
                ref.declarationSourceEnd = importPackage.getEnd();
                ref.declarationEnd = ref.sourceEnd;
                importReferences.add(ref);
            }
            for (Map.Entry<String, ImportNode> importStatic : importStatics.entrySet()) {
                ImportNode importNode = importStatic.getValue();
                String importName = importNode.getClassName() + "." + importStatic.getKey();
                char[][] splits = CharOperation.splitOn('.', importName.toCharArray());

                ImportReference ref = null;
                ClassNode type = importNode.getType();
                int typeStartOffset = startOffset(type);
                int typeEndOffset = endOffset(type);
                if (importNode.getAlias() != null && importNode.getAlias().length() > 0) {
                    // FIXASC will need extra positional info for the 'as' and the alias
                    ref = new AliasImportReference(importNode.getAlias().toCharArray(), splits,
                            positionsFor(splits, typeStartOffset, typeEndOffset), false,
                            ClassFileConstants.AccDefault | ClassFileConstants.AccStatic);
                } else {
                    ref = new ImportReference(splits, positionsFor(splits, typeStartOffset, typeEndOffset), false,
                            ClassFileConstants.AccDefault | ClassFileConstants.AccStatic);
                }
                ref.sourceEnd = Math.max(typeEndOffset - 1, ref.sourceStart); // For error reporting, Eclipse wants -1
                ref.declarationSourceStart = importNode.getStart();
                ref.declarationSourceEnd = importNode.getEnd();
                ref.declarationEnd = ref.sourceEnd;
                importReferences.add(ref);
            }
            for (Map.Entry<String, ImportNode> importStaticStar : importStaticStars.entrySet()) {
                String classname = importStaticStar.getKey();
                ImportNode importNode = importStaticStar.getValue();
                ClassNode importedType = importNode.getType();
                int typeStartOffset = importedType != null ? startOffset(importedType) : 0;
                int typeEndOffset = importedType != null ? endOffset(importedType) : 0;
                char[][] splits = CharOperation.splitOn('.', classname.toCharArray());
                ImportReference ref = new ImportReference(splits,
                        positionsFor(splits, typeStartOffset, typeEndOffset), true,
                        ClassFileConstants.AccDefault | ClassFileConstants.AccStatic);
                ref.sourceEnd = Math.max(typeEndOffset - 1, ref.sourceStart); // For error reporting, Eclipse wants -1
                ref.declarationSourceStart = importNode.getStart();
                ref.declarationSourceEnd = importNode.getEnd();
                ref.declarationEnd = ref.sourceEnd;
                importReferences.add(ref);
            }

            // ensure proper lexical order
            if (importReferences.size() != 0) {
                imports = importReferences.toArray(new ImportReference[importReferences.size()]);
                Arrays.sort(imports, new Comparator<ImportReference>() {
                    public int compare(ImportReference left, ImportReference right) {
                        return left.sourceStart - right.sourceStart;
                    }
                });
                for (ImportReference ref : imports) {
                    if (ref.declarationSourceStart > 0
                            && (ref.declarationEnd - ref.declarationSourceStart + 1) < 0) {
                        throw new IllegalStateException(
                                "Import reference alongside class " + moduleNode.getClasses().get(0)
                                        + " will trigger later failure: " + ref.toString() + " declSourceStart="
                                        + ref.declarationSourceStart + " declEnd=" + +ref.declarationEnd);
                    }

                }
            }
        }
    }

    /**
     * Build a JDT package declaration based on the groovy one
     */
    private void createPackageDeclaration(ModuleNode moduleNode) {
        if (moduleNode.hasPackageName()) {
            PackageNode packageNode = moduleNode.getPackage();// Node();
            String packageName = moduleNode.getPackageName();
            if (packageName.endsWith(".")) {
                packageName = packageName.substring(0, packageName.length() - 1);
            }
            long start = startOffset(packageNode);
            long end = endOffset(packageNode);
            char[][] packageReference = CharOperation.splitOn('.', packageName.toCharArray());
            currentPackage = new ImportReference(packageReference, positionsFor(packageReference, start, end), true,
                    ClassFileConstants.AccDefault);
            currentPackage.declarationSourceStart = currentPackage.sourceStart;
            currentPackage.declarationSourceEnd = currentPackage.sourceEnd;
            currentPackage.declarationEnd = currentPackage.sourceEnd;

            // FIXASC (M3) not right, there may be spaces between package keyword and decl. Just the first example of position
            // problems
            currentPackage.declarationSourceStart = currentPackage.sourceStart - "package ".length();
            currentPackage.declarationEnd = currentPackage.declarationSourceEnd = currentPackage.sourceEnd;
        }
    }

    /**
     * Convert groovy annotations into JDT annotations
     * 
     * @return an array of annotations or null if there are none
     */
    private Annotation[] transformAnnotations(List<AnnotationNode> groovyAnnotations) {
        // FIXASC positions are crap
        if (groovyAnnotations != null && groovyAnnotations.size() > 0) {
            List<Annotation> annotations = new ArrayList<Annotation>();
            for (AnnotationNode annotationNode : groovyAnnotations) {
                ClassNode annoType = annotationNode.getClassNode();
                Map<String, Expression> memberValuePairs = annotationNode.getMembers();
                // FIXASC (M3) do more than pure marker annotations and do annotation values

                if (memberValuePairs == null || memberValuePairs.size() == 0) {
                    // Marker annotation:
                    TypeReference annotationReference = createTypeReferenceForClassNode(annoType);
                    annotationReference.sourceStart = annotationNode.getStart();
                    annotationReference.sourceEnd = annotationNode.getEnd();

                    MarkerAnnotation annotation = new MarkerAnnotation(annotationReference,
                            annotationNode.getStart());
                    annotation.declarationSourceEnd = annotation.sourceEnd;
                    annotations.add(annotation);
                } else {

                    if (memberValuePairs.size() == 1 && memberValuePairs.containsKey("value")) {
                        // Single member annotation

                        // Code written to only manage a single class literal value annotation - so that @RunWith works
                        Expression value = memberValuePairs.get("value");
                        if (value instanceof PropertyExpression) {
                            String pExpression = ((PropertyExpression) value).getPropertyAsString();
                            if (pExpression.equals("class")) {
                                TypeReference annotationReference = createTypeReferenceForClassNode(annoType);
                                annotationReference.sourceStart = annotationNode.getStart();
                                annotationReference.sourceEnd = annotationNode.getEnd();
                                SingleMemberAnnotation annotation = new SingleMemberAnnotation(annotationReference,
                                        annotationNode.getStart());
                                annotation.memberValue = new ClassLiteralAccess(value.getEnd(),
                                        classLiteralToTypeReference((PropertyExpression) value));
                                annotation.declarationSourceEnd = annotation.sourceStart
                                        + annoType.getNameWithoutPackage().length();
                                annotations.add(annotation);
                            }
                        } else if (value instanceof VariableExpression && annoType.getName().endsWith("RunWith")) {
                            // FIXASC special case for 'RunWith(Foo)' where for some reason groovy doesn't mind the missing
                            // '.class'
                            // FIXASC test this
                            TypeReference annotationReference = createTypeReferenceForClassNode(annoType);
                            annotationReference.sourceStart = annotationNode.getStart();
                            annotationReference.sourceEnd = annotationNode.getEnd();
                            SingleMemberAnnotation annotation = new SingleMemberAnnotation(annotationReference,
                                    annotationNode.getStart());
                            String v = ((VariableExpression) value).getName();
                            TypeReference ref = null;
                            int start = annotationReference.sourceStart;
                            int end = annotationReference.sourceEnd;
                            if (v.indexOf(".") == -1) {
                                ref = new SingleTypeReference(v.toCharArray(), toPos(start, end - 1));
                            } else {
                                char[][] splits = CharOperation.splitOn('.', v.toCharArray());
                                ref = new QualifiedTypeReference(splits, positionsFor(splits, start, end - 2));
                            }
                            annotation.memberValue = new ClassLiteralAccess(value.getEnd(), ref);
                            annotation.declarationSourceEnd = annotation.sourceStart
                                    + annoType.getNameWithoutPackage().length();
                            annotations.add(annotation);
                            // FIXASC underlining for SuppressWarnings doesn't seem right when included in messages
                        } else if (annoType.getName().equals("SuppressWarnings")
                                && (value instanceof ConstantExpression || value instanceof ListExpression)) {
                            if (value instanceof ListExpression) {
                                ListExpression listExpression = (ListExpression) value;
                                // FIXASC tidy up all this junk (err, i mean 'refactor') once we have confidence in test
                                // coverage
                                List<Expression> listOfExpressions = listExpression.getExpressions();
                                TypeReference annotationReference = createTypeReferenceForClassNode(annoType);
                                annotationReference.sourceStart = annotationNode.getStart();
                                annotationReference.sourceEnd = annotationNode.getEnd() - 1;
                                SingleMemberAnnotation annotation = new SingleMemberAnnotation(annotationReference,
                                        annotationNode.getStart());

                                ArrayInitializer arrayInitializer = new ArrayInitializer();
                                arrayInitializer.expressions = new org.eclipse.jdt.internal.compiler.ast.Expression[listOfExpressions
                                        .size()];
                                for (int c = 0; c < listOfExpressions.size(); c++) {
                                    ConstantExpression cExpression = (ConstantExpression) listOfExpressions.get(c);
                                    String v = (String) cExpression.getValue();
                                    TypeReference ref = null;
                                    int start = cExpression.getStart();
                                    int end = cExpression.getEnd() - 1;
                                    if (v.indexOf(".") == -1) {
                                        ref = new SingleTypeReference(v.toCharArray(), toPos(start, end - 1));
                                        annotation.declarationSourceEnd = annotation.sourceStart
                                                + annoType.getNameWithoutPackage().length() - 1;
                                    } else {
                                        char[][] splits = CharOperation.splitOn('.', v.toCharArray());
                                        ref = new QualifiedTypeReference(splits,
                                                positionsFor(splits, start, end - 2));
                                        annotation.declarationSourceEnd = annotation.sourceStart
                                                + annoType.getName().length() - 1;
                                    }
                                    arrayInitializer.expressions[c] = new StringLiteral(v.toCharArray(), start, end,
                                            -1);
                                }
                                annotation.memberValue = arrayInitializer;
                                annotations.add(annotation);
                            } else {
                                ConstantExpression constantExpression = (ConstantExpression) value;
                                if (value.getType().getName().equals("java.lang.String")) {
                                    // single value, eg. @SuppressWarnings("unchecked")
                                    // FIXASC tidy up all this junk (err, i mean 'refactor') once we have confidence in test
                                    // coverage
                                    // FIXASC test positional info for conjured up anno refs
                                    TypeReference annotationReference = createTypeReferenceForClassNode(annoType);
                                    annotationReference.sourceStart = annotationNode.getStart();
                                    annotationReference.sourceEnd = annotationNode.getEnd() - 1;
                                    SingleMemberAnnotation annotation = new SingleMemberAnnotation(
                                            annotationReference, annotationNode.getStart());
                                    String v = (String) constantExpression.getValue();
                                    TypeReference ref = null;
                                    int start = constantExpression.getStart();
                                    int end = constantExpression.getEnd() - 1;
                                    if (v.indexOf(".") == -1) {
                                        ref = new SingleTypeReference(v.toCharArray(), toPos(start, end - 1));
                                        annotation.declarationSourceEnd = annotation.sourceStart
                                                + annoType.getNameWithoutPackage().length() - 1;
                                    } else {
                                        char[][] splits = CharOperation.splitOn('.', v.toCharArray());
                                        ref = new QualifiedTypeReference(splits,
                                                positionsFor(splits, start, end - 2));
                                        annotation.declarationSourceEnd = annotation.sourceStart
                                                + annoType.getName().length() - 1;
                                    }
                                    annotation.memberValue = new StringLiteral(v.toCharArray(), start, end, -1);
                                    annotations.add(annotation);
                                }
                            }
                        }
                    } else if (annoType.getNameWithoutPackage().equals("Test")) {
                        // normal annotation (with at least one member value pair)
                        // GRECLIPSE-569
                        // treat as a marker annotation
                        // this is specifically written so that annotations like @Test(expected = FooException) can be found
                        TypeReference annotationReference = createTypeReferenceForClassNode(annoType);
                        annotationReference.sourceStart = annotationNode.getStart();
                        annotationReference.sourceEnd = annotationNode.getEnd();

                        MarkerAnnotation annotation = new MarkerAnnotation(annotationReference,
                                annotationNode.getStart());
                        annotation.declarationSourceEnd = annotation.sourceEnd;
                        annotations.add(annotation);
                    }
                }
            }
            if (annotations.size() > 0) {
                return annotations.toArray(new Annotation[annotations.size()]);
            }
        }
        return null;
    }

    private TypeReference classLiteralToTypeReference(PropertyExpression value) {
        // should be a class literal node
        assert value.getPropertyAsString().equals("class");

        // FIXASC ignore type parameters for now
        Expression candidate = value.getObjectExpression();
        List<char[]> nameParts = new LinkedList<char[]>();
        while (candidate instanceof PropertyExpression) {
            nameParts.add(0, ((PropertyExpression) candidate).getPropertyAsString().toCharArray());
            candidate = ((PropertyExpression) candidate).getObjectExpression();
        }
        if (candidate instanceof VariableExpression) {
            nameParts.add(0, ((VariableExpression) candidate).getName().toCharArray());
        }

        char[][] namePartsArr = nameParts.toArray(new char[nameParts.size()][]);
        long[] poss = positionsFor(namePartsArr, value.getObjectExpression().getStart(),
                value.getObjectExpression().getEnd());
        TypeReference ref;
        if (namePartsArr.length > 1) {
            ref = new QualifiedTypeReference(namePartsArr, poss);
        } else if (namePartsArr.length == 1) {
            ref = new SingleTypeReference(namePartsArr[0], poss[0]);
        } else {
            // should not happen
            ref = TypeReference.baseTypeReference(nameToPrimitiveTypeId.get("void"), 0);
        }

        return ref;
    }

    /**
     * Find any javadoc that terminates on one of the two lines before the specified line, return the first bit encountered. A
     * little crude but will cover a lot of common cases... <br>
     */
    // FIXASC when the parser correctly records javadoc for nodes alongside them during a parse, we will not have to search
    private Javadoc findJavadoc(int line) {
        // System.out.println("Looking for javadoc for line " + line);
        for (Comment comment : groovySourceUnit.getComments()) {
            if (comment.isJavadoc()) {
                // System.out.println("Checking against comment ending on " + comment.getLastLine());
                if (comment.getLastLine() + 1 == line || (comment.getLastLine() + 2 == line && !comment.usedUp)) {
                    int[] pos = comment.getPositions(compilationResult.lineSeparatorPositions);
                    // System.out.println("Comment says it is from line=" + comment.sline + ",col=" + comment.scol + " to line="
                    // + comment.eline + ",col=" + comment.ecol);
                    // System.out.println("Returning positions " + pos[0] + ">" + pos[1]);
                    comment.usedUp = true;
                    return new Javadoc(pos[0], pos[1]);
                }
            }
        }
        return null;
    }

    /**
     * Build JDT TypeDeclarations for the groovy type declarations that were parsed from the source file.
     */
    private void createTypeDeclarations(ModuleNode moduleNode) {
        List<ClassNode> moduleClassNodes = moduleNode.getClasses();
        List<TypeDeclaration> typeDeclarations = new ArrayList<TypeDeclaration>();
        Map<ClassNode, TypeDeclaration> fromClassNodeToDecl = new HashMap<ClassNode, TypeDeclaration>();

        char[] mainName = toMainName(compilationResult.getFileName());
        boolean isInner = false;
        List<ClassNode> classNodes = null;
        classNodes = moduleClassNodes;
        Map<ClassNode, List<TypeDeclaration>> innersToRecord = new HashMap<ClassNode, List<TypeDeclaration>>();
        for (ClassNode classNode : classNodes) {
            if (!classNode.isPrimaryClassNode()) {
                continue;
            }

            GroovyTypeDeclaration typeDeclaration = new GroovyTypeDeclaration(compilationResult, classNode);

            typeDeclaration.annotations = transformAnnotations(classNode.getAnnotations());
            // FIXASC duplicates code further down, tidy this up
            if (classNode instanceof InnerClassNode) {
                InnerClassNode innerClassNode = (InnerClassNode) classNode;
                ClassNode outerClass = innerClassNode.getOuterClass();
                String outername = outerClass.getNameWithoutPackage();
                String newInner = innerClassNode.getNameWithoutPackage().substring(outername.length() + 1);
                typeDeclaration.name = newInner.toCharArray();
                isInner = true;
            } else {
                typeDeclaration.name = classNode.getNameWithoutPackage().toCharArray();
                isInner = false;
            }

            // classNode.getInnerClasses();
            // classNode.
            boolean isInterface = classNode.isInterface();
            int mods = classNode.getModifiers();
            if ((mods & Opcodes.ACC_ENUM) != 0) {
                // remove final
                mods = mods & ~Opcodes.ACC_FINAL;
            }
            // FIXASC should this modifier be set?
            // mods |= Opcodes.ACC_PUBLIC;
            // FIXASC should not do this for inner classes, just for top level types
            // FIXASC does this make things visible that shouldn't be?
            mods = mods & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED);
            if (!isInner) {
                if ((mods & Opcodes.ACC_STATIC) != 0) {
                    mods = mods & ~(Opcodes.ACC_STATIC);
                }
            }
            typeDeclaration.modifiers = mods & ~(isInterface ? Opcodes.ACC_ABSTRACT : 0);

            if (!(classNode instanceof InnerClassNode)) {
                if (!CharOperation.equals(typeDeclaration.name, mainName)) {
                    typeDeclaration.bits |= ASTNode.IsSecondaryType;
                }
            }

            fixupSourceLocationsForTypeDeclaration(typeDeclaration, classNode);

            if (classNode.getGenericsTypes() != null) {
                GenericsType[] genericInfo = classNode.getGenericsTypes();
                // Example case here: Foo<T extends Number & I>
                // the type parameter is T, the 'type' is Number and the bounds for the type parameter are just the extra bound
                // I.
                typeDeclaration.typeParameters = new TypeParameter[genericInfo.length];
                for (int tp = 0; tp < genericInfo.length; tp++) {
                    TypeParameter typeParameter = new TypeParameter();
                    typeParameter.name = genericInfo[tp].getName().toCharArray();
                    ClassNode[] upperBounds = genericInfo[tp].getUpperBounds();
                    if (upperBounds != null) {
                        // FIXASC (M3) Positional info for these references?
                        typeParameter.type = createTypeReferenceForClassNode(upperBounds[0]);
                        typeParameter.bounds = (upperBounds.length > 1 ? new TypeReference[upperBounds.length - 1]
                                : null);
                        for (int b = 1, max = upperBounds.length; b < max; b++) {
                            typeParameter.bounds[b - 1] = createTypeReferenceForClassNode(upperBounds[b]);
                            typeParameter.bounds[b - 1].bits |= ASTNode.IsSuperType;
                        }
                    }
                    typeDeclaration.typeParameters[tp] = typeParameter;
                }
            }

            boolean isEnum = (classNode.getModifiers() & Opcodes.ACC_ENUM) != 0;
            configureSuperClass(typeDeclaration, classNode.getSuperClass(), isEnum);
            configureSuperInterfaces(typeDeclaration, classNode);
            typeDeclaration.methods = createMethodAndConstructorDeclarations(classNode, isEnum, compilationResult);
            typeDeclaration.fields = createFieldDeclarations(classNode);
            typeDeclaration.properties = classNode.getProperties();
            if (classNode instanceof InnerClassNode) {
                InnerClassNode innerClassNode = (InnerClassNode) classNode;
                ClassNode outerClass = innerClassNode.getOuterClass();
                String outername = outerClass.getNameWithoutPackage();
                String newInner = innerClassNode.getNameWithoutPackage().substring(outername.length() + 1);
                typeDeclaration.name = newInner.toCharArray();

                // Record that we need to set the parent of this inner type later
                List<TypeDeclaration> inners = innersToRecord.get(outerClass);
                if (inners == null) {
                    inners = new ArrayList<TypeDeclaration>();
                    innersToRecord.put(outerClass, inners);
                }
                inners.add(typeDeclaration);
            } else {
                typeDeclarations.add(typeDeclaration);
            }
            fromClassNodeToDecl.put(classNode, typeDeclaration);
        }

        // For inner types, now attach them to their parents. This was not done earlier as sometimes the types are processed in
        // such an order that inners are dealt with before outers
        for (Map.Entry<ClassNode, List<TypeDeclaration>> innersToRecordEntry : innersToRecord.entrySet()) {
            TypeDeclaration outerTypeDeclaration = fromClassNodeToDecl.get(innersToRecordEntry.getKey());
            // Check if there is a problem locating the parent for the inner
            if (outerTypeDeclaration == null) {
                throw new GroovyEclipseBug(
                        "Failed to find the type declaration for " + innersToRecordEntry.getKey().getName());
            }
            List<TypeDeclaration> newInnersList = innersToRecordEntry.getValue();
            outerTypeDeclaration.memberTypes = newInnersList.toArray(new TypeDeclaration[newInnersList.size()]);
        }

        types = typeDeclarations.toArray(new TypeDeclaration[typeDeclarations.size()]);
    }

    public char[] toMainName(char[] fileName) {
        if (fileName == null) {
            return new char[0];
        }
        int start = CharOperation.lastIndexOf('/', fileName) + 1;
        if (start == 0 || start < CharOperation.lastIndexOf('\\', fileName))
            start = CharOperation.lastIndexOf('\\', fileName) + 1;

        int end = CharOperation.lastIndexOf('.', fileName);
        if (end == -1)
            end = fileName.length;

        return CharOperation.subarray(fileName, start, end);
    }

    /**
     * Build JDT representations of all the method/ctors on the groovy type
     */
    private AbstractMethodDeclaration[] createMethodAndConstructorDeclarations(ClassNode classNode, boolean isEnum,
            CompilationResult compilationResult) {
        List<AbstractMethodDeclaration> accumulatedDeclarations = new ArrayList<AbstractMethodDeclaration>();
        createConstructorDeclarations(classNode, isEnum, accumulatedDeclarations);
        createMethodDeclarations(classNode, isEnum, accumulatedDeclarations);
        return accumulatedDeclarations.toArray(new AbstractMethodDeclaration[accumulatedDeclarations.size()]);
    }

    /**
     * Hold onto the groovy initializer so we can return it later. This is much easier than translating it into a JDT initializer
     * and back again later.
     */
    static class FieldDeclarationWithInitializer extends FieldDeclaration {
        private Expression initializer;

        public FieldDeclarationWithInitializer(char[] name, int sourceStart, int sourceEnd) {
            super(name, sourceStart, sourceEnd);
        }

        public void setGroovyInitializer(Expression initializer) {
            this.initializer = initializer;
        }

        public Expression getGroovyInitializer() {
            return this.initializer;
        }
    }

    /**
     * Build JDT representations of all the fields on the groovy type. <br>
     * Enum field handling<br>
     * Groovy handles them as follows: they have the ACC_ENUM bit set and the type is the type of the declaring enum type. When
     * building declarations, if you want the SourceTypeBinding to correctly build an enum field binding (in
     * SourceTypeBinding.resolveTypeFor(FieldBinding)) then you need to: (1) avoid setting modifiers, the enum fields are not
     * expected to have any modifiers (2) leave the type as null, that is how these things are identified by JDT.
     */
    private FieldDeclaration[] createFieldDeclarations(ClassNode classNode) {
        List<FieldDeclaration> fieldDeclarations = new ArrayList<FieldDeclaration>();
        List<FieldNode> fieldNodes = classNode.getFields();
        if (fieldNodes != null) {
            for (FieldNode fieldNode : fieldNodes) {
                boolean isEnumField = (fieldNode.getModifiers() & Opcodes.ACC_ENUM) != 0;
                boolean isSynthetic = (fieldNode.getModifiers() & Opcodes.ACC_SYNTHETIC) != 0;
                if (!isSynthetic) {
                    // JavaStubGenerator ignores private fields but I don't
                    // think we want to here
                    FieldDeclarationWithInitializer fieldDeclaration = new FieldDeclarationWithInitializer(
                            fieldNode.getName().toCharArray(), 0, 0);
                    fieldDeclaration.annotations = transformAnnotations(fieldNode.getAnnotations());
                    if (!isEnumField) {
                        fieldDeclaration.modifiers = fieldNode.getModifiers() & ~0x4000; // 4000 == AccEnum
                        fieldDeclaration.type = createTypeReferenceForClassNode(fieldNode.getType());
                    }
                    fieldDeclaration.javadoc = new Javadoc(108, 132);
                    fixupSourceLocationsForFieldDeclaration(fieldDeclaration, fieldNode, isEnumField);
                    fieldDeclaration.setGroovyInitializer(fieldNode.getInitialExpression());
                    fieldDeclarations.add(fieldDeclaration);
                }
            }
        }
        return fieldDeclarations.toArray(new FieldDeclaration[fieldDeclarations.size()]);
    }

    /**
     * Build JDT representations of all the constructors on the groovy type
     */
    private void createConstructorDeclarations(ClassNode classNode, boolean isEnum,
            List<AbstractMethodDeclaration> accumulatedMethodDeclarations) {
        List<ConstructorNode> constructorNodes = classNode.getDeclaredConstructors();

        char[] ctorName = null;
        if (classNode instanceof InnerClassNode) {
            InnerClassNode innerClassNode = (InnerClassNode) classNode;
            ClassNode outerClass = innerClassNode.getOuterClass();
            String outername = outerClass.getNameWithoutPackage();
            String newInner = innerClassNode.getNameWithoutPackage().substring(outername.length() + 1);
            ctorName = newInner.toCharArray();
        } else {
            ctorName = classNode.getNameWithoutPackage().toCharArray();
        }

        // Do we need a default constructor?
        boolean needsDefaultCtor = constructorNodes.size() == 0 && !classNode.isInterface();

        if (needsDefaultCtor) {
            ConstructorDeclaration constructor = new ConstructorDeclaration(compilationResult);
            constructor.bits |= ASTNode.IsDefaultConstructor;
            if (isEnum) {
                constructor.modifiers = ClassFileConstants.AccPrivate;
            } else {
                constructor.modifiers = ClassFileConstants.AccPublic;
            }
            constructor.selector = ctorName;
            accumulatedMethodDeclarations.add(constructor);
        }

        for (ConstructorNode constructorNode : constructorNodes) {
            ConstructorDeclaration constructorDeclaration = new ConstructorDeclaration(compilationResult);

            fixupSourceLocationsForConstructorDeclaration(constructorDeclaration, constructorNode);

            constructorDeclaration.annotations = transformAnnotations(constructorNode.getAnnotations());
            // FIXASC should we just use the constructor node modifiers or does groovy make all constructors public apart from
            // those on enums?
            constructorDeclaration.modifiers = isEnum ? ClassFileConstants.AccPrivate
                    : ClassFileConstants.AccPublic;
            constructorDeclaration.selector = ctorName;
            constructorDeclaration.arguments = createArguments(constructorNode.getParameters(), false);
            constructorDeclaration.thrownExceptions = createTypeReferencesForClassNodes(
                    constructorNode.getExceptions());
            if (constructorNode.hasDefaultValue()) {
                createConstructorVariants(constructorNode, constructorDeclaration, accumulatedMethodDeclarations,
                        compilationResult, isEnum);
            } else {
                accumulatedMethodDeclarations.add(constructorDeclaration);
            }
        }

        if (earlyTransforms) {
            executeEarlyTransforms_ConstructorRelated(ctorName, classNode, accumulatedMethodDeclarations);
        }

    }

    /**
     * Augment set of constructors based on annotations. If the annotations are going to trigger additional constructors later, add
     * them here.
     */
    private void executeEarlyTransforms_ConstructorRelated(char[] ctorName, ClassNode classNode,
            List<AbstractMethodDeclaration> accumulatedMethodDeclarations) {
        List<AnnotationNode> annos = classNode.getAnnotations();
        boolean hasImmutableAnnotation = false;
        if (annos != null) {
            for (AnnotationNode anno : annos) {
                if (anno.getClassNode() != null && anno.getClassNode().getName().equals("Immutable")) {
                    hasImmutableAnnotation = true;
                }
            }
        }
        // TODO probably ought to check if clashing import rather than assuming it is groovy-eclipse Immutable (even though that is
        // very likely)
        if (hasImmutableAnnotation) {
            // @Immutable action: new constructor

            // TODO Should check against existing ones before creating a duplicate but quite ugly, and
            // groovy will be checking anyway...
            List<FieldNode> fields = classNode.getFields();
            Argument[] arguments = new Argument[fields.size()];
            for (int i = 0; i < fields.size(); i++) {
                FieldNode field = fields.get(i);
                TypeReference parameterTypeReference = createTypeReferenceForClassNode(field.getType());
                // TODO should set type reference position
                arguments[i] = new Argument(fields.get(i).getName().toCharArray(),
                        toPos(field.getStart(), field.getEnd() - 1), parameterTypeReference,
                        ClassFileConstants.AccPublic);
                arguments[i].declarationSourceStart = fields.get(i).getStart();
            }

            ConstructorDeclaration constructor = new ConstructorDeclaration(compilationResult);
            constructor.selector = ctorName;
            constructor.modifiers = ClassFileConstants.AccPublic;
            constructor.arguments = arguments;
            accumulatedMethodDeclarations.add(constructor);
        }
    }

    /**
     * Create JDT Argument representations of Groovy parameters
     */
    private Argument[] createArguments(Parameter[] ps, boolean isMain) {
        if (ps == null || ps.length == 0) {
            return null;
        }
        Argument[] arguments = new Argument[ps.length];
        for (int i = 0; i < ps.length; i++) {
            Parameter parameter = ps[i];
            TypeReference parameterTypeReference = createTypeReferenceForClassNode(parameter.getType());
            // not doing this for now:
            // if (isMain) {
            // parameterTypeReference = new ArrayTypeReference("String".toCharArray(), 1,
            // (parameterTypeReference.sourceStart << 32) | parameterTypeReference.sourceEnd);
            // }

            arguments[i] = new Argument(parameter.getName().toCharArray(),
                    toPos(parameter.getStart(), parameter.getEnd() - 1), parameterTypeReference,
                    ClassFileConstants.AccPublic);
            arguments[i].declarationSourceStart = parameter.getStart();
        }
        if (isVargs(ps) /* && !isMain */) {
            arguments[ps.length - 1].type.bits |= ASTNode.IsVarArgs;
        }
        return arguments;
    }

    /**
     * Build JDT representations of all the methods on the groovy type
     */
    private void createMethodDeclarations(ClassNode classNode, boolean isEnum,
            List<AbstractMethodDeclaration> accumulatedDeclarations) {
        List<MethodNode> methods = classNode.getMethods();

        for (MethodNode methodNode : methods) {
            if (isEnum && methodNode.isSynthetic()) {
                // skip values() method and valueOf(String)
                String name = methodNode.getName();
                Parameter[] params = methodNode.getParameters();
                if (name.equals("values") && params.length == 0) {
                    continue;
                }
                if (name.equals("valueOf") && params.length == 1
                        && params[0].getType().equals(ClassHelper.STRING_TYPE)) {
                    continue;
                }
            }
            MethodDeclaration methodDeclaration = createMethodDeclaration(classNode, methodNode, isEnum,
                    compilationResult);
            // methodDeclaration.javadoc = new Javadoc(0, 20);
            if (methodNode.hasDefaultValue()) {
                createMethodVariants(methodNode, methodDeclaration, accumulatedDeclarations, compilationResult);
            } else {
                accumulatedDeclarations.add(methodDeclaration);
            }
        }
    }

    /**
     * Called if a method has some 'defaulting' arguments and will compute all the variants (including the one with all parameters).
     */
    private void createMethodVariants(MethodNode method, MethodDeclaration methodDecl,
            List<AbstractMethodDeclaration> accumulatedDeclarations, CompilationResult compilationResult) {
        List<Argument[]> variants = getVariantsAllowingForDefaulting(method.getParameters(), methodDecl.arguments);
        for (Argument[] variant : variants) {
            MethodDeclaration variantMethodDeclaration = genMethodDeclarationVariant(method, variant,
                    methodDecl.returnType, compilationResult);
            addUnlessDuplicate(accumulatedDeclarations, variantMethodDeclaration);
        }
    }

    /**
     * In the given list of groovy parameters, some are defined as defaulting to an initial value. This method computes all the
     * variants of defaulting parameters allowed and returns a List of Argument arrays. Each argument array represents a variation.
     */
    private List<Argument[]> getVariantsAllowingForDefaulting(Parameter[] groovyParams, Argument[] jdtArguments) {
        List<Argument[]> variants = new ArrayList<Argument[]>();

        int psCount = groovyParams.length;
        Parameter[] wipableParameters = new Parameter[psCount];
        System.arraycopy(groovyParams, 0, wipableParameters, 0, psCount);

        // Algorithm: wipableParameters is the 'full list' of parameters at the start. As the loop is repeated, all the non-null
        // values in the list indicate a parameter variation. On each repeat we null the last one in the list that
        // has an initial expression. This is repeated until there are no more left to null.

        List<Argument> oneVariation = new ArrayList<Argument>();
        int nextToLetDefault = -1;
        do {
            oneVariation.clear();
            nextToLetDefault = -1;
            // Create a variation based on the non null entries left in th elist
            for (int p = 0; p < psCount; p++) {
                if (wipableParameters[p] != null) {
                    oneVariation.add(jdtArguments[p]);
                    if (wipableParameters[p].hasInitialExpression()) {
                        nextToLetDefault = p;
                    }
                }
            }
            if (nextToLetDefault != -1) {
                wipableParameters[nextToLetDefault] = null;
            }
            Argument[] argumentsVariant = (oneVariation.size() == 0 ? null
                    : oneVariation.toArray(new Argument[oneVariation.size()]));
            variants.add(argumentsVariant);
        } while (nextToLetDefault != -1);

        return variants;
    }

    /**
     * Add the new declaration to the list of those already built unless it clashes with an existing one. This can happen where the
     * default parameter mechanism causes creation of a variant that collides with an existing declaration. I'm not sure if Groovy
     * should be reporting an error when this occurs, but Grails does actually do it and gets no error.
     */
    private void addUnlessDuplicate(List<AbstractMethodDeclaration> accumulatedDeclarations,
            AbstractMethodDeclaration newDeclaration) {
        boolean isDuplicate = false;

        for (AbstractMethodDeclaration aMethodDecl : accumulatedDeclarations) {
            if (CharOperation.equals(aMethodDecl.selector, newDeclaration.selector)) {
                Argument[] mdArgs = aMethodDecl.arguments;
                Argument[] vmdArgs = newDeclaration.arguments;
                int mdArgsLen = mdArgs == null ? 0 : mdArgs.length;
                int vmdArgsLen = vmdArgs == null ? 0 : vmdArgs.length;
                if (mdArgsLen == vmdArgsLen) {
                    boolean argsTheSame = true;
                    for (int i = 0; i < mdArgsLen; i++) {
                        // FIXASC this comparison can fail if some are fully qualified and some not - in fact it
                        // suggests that default param variants should be built by augmentMethod() in a similar way to
                        // the GroovyObject methods, rather than during type declaration construction - but not super urgent right
                        // now
                        if (!CharOperation.equals(mdArgs[i].type.getTypeName(), vmdArgs[i].type.getTypeName())) {
                            argsTheSame = false;
                            break;
                        }
                    }
                    if (argsTheSame) {
                        isDuplicate = true;
                        break;
                    }
                }
            }
        }

        if (!isDuplicate) {
            accumulatedDeclarations.add(newDeclaration);
        }
    }

    /**
     * Called if a constructor has some 'defaulting' arguments and will compute all the variants (including the one with all
     * parameters).
     */
    private void createConstructorVariants(ConstructorNode constructorNode, ConstructorDeclaration constructorDecl,
            List<AbstractMethodDeclaration> accumulatedDeclarations, CompilationResult compilationResult,
            boolean isEnum) {

        List<Argument[]> variants = getVariantsAllowingForDefaulting(constructorNode.getParameters(),
                constructorDecl.arguments);

        for (Argument[] variant : variants) {
            ConstructorDeclaration constructorDeclaration = new ConstructorDeclaration(compilationResult);
            constructorDeclaration.annotations = transformAnnotations(constructorNode.getAnnotations());
            constructorDeclaration.modifiers = isEnum ? ClassFileConstants.AccPrivate
                    : ClassFileConstants.AccPublic;
            constructorDeclaration.selector = constructorDecl.selector;
            constructorDeclaration.arguments = variant;
            fixupSourceLocationsForConstructorDeclaration(constructorDeclaration, constructorNode);
            addUnlessDuplicate(accumulatedDeclarations, constructorDeclaration);
        }
    }

    /**
     * Create a JDT MethodDeclaration that represents a groovy MethodNode
     */
    private MethodDeclaration createMethodDeclaration(ClassNode classNode, MethodNode methodNode, boolean isEnum,
            CompilationResult compilationResult) {
        if (classNode.isAnnotationDefinition()) {
            AnnotationMethodDeclaration methodDeclaration = new AnnotationMethodDeclaration(compilationResult);
            int modifiers = methodNode.getModifiers();
            modifiers &= ~(ClassFileConstants.AccSynthetic | ClassFileConstants.AccTransient);
            methodDeclaration.annotations = transformAnnotations(methodNode.getAnnotations());
            methodDeclaration.modifiers = modifiers;
            if (methodNode.hasAnnotationDefault()) {
                methodDeclaration.modifiers |= ClassFileConstants.AccAnnotationDefault;
            }
            methodDeclaration.selector = methodNode.getName().toCharArray();
            fixupSourceLocationsForMethodDeclaration(methodDeclaration, methodNode);
            ClassNode returnType = methodNode.getReturnType();
            methodDeclaration.returnType = createTypeReferenceForClassNode(returnType);
            return methodDeclaration;
        } else {
            MethodDeclaration methodDeclaration = new MethodDeclaration(compilationResult);

            // TODO refactor - extract method
            GenericsType[] generics = methodNode.getGenericsTypes();
            // generic method
            if (generics != null && generics.length != 0) {
                methodDeclaration.typeParameters = new TypeParameter[generics.length];
                for (int tp = 0; tp < generics.length; tp++) {
                    TypeParameter typeParameter = new TypeParameter();
                    typeParameter.name = generics[tp].getName().toCharArray();
                    ClassNode[] upperBounds = generics[tp].getUpperBounds();
                    if (upperBounds != null) {
                        // FIXASC Positional info for these references?
                        typeParameter.type = createTypeReferenceForClassNode(upperBounds[0]);
                        typeParameter.bounds = (upperBounds.length > 1 ? new TypeReference[upperBounds.length - 1]
                                : null);
                        for (int b = 1, max = upperBounds.length; b < max; b++) {
                            typeParameter.bounds[b - 1] = createTypeReferenceForClassNode(upperBounds[b]);
                            typeParameter.bounds[b - 1].bits |= ASTNode.IsSuperType;
                        }
                    }
                    methodDeclaration.typeParameters[tp] = typeParameter;
                }
            }

            boolean isMain = false;
            // Note: modifiers for the MethodBinding constructed for this declaration will be created marked with
            // AccVarArgs if the bitset for the type reference in the final argument is marked IsVarArgs
            int modifiers = methodNode.getModifiers();
            modifiers &= ~(ClassFileConstants.AccSynthetic | ClassFileConstants.AccTransient);
            methodDeclaration.annotations = transformAnnotations(methodNode.getAnnotations());
            methodDeclaration.modifiers = modifiers;
            methodDeclaration.selector = methodNode.getName().toCharArray();
            // Need to capture the rule in Verifier.adjustTypesIfStaticMainMethod(MethodNode node)
            // if (node.getName().equals("main") && node.isStatic()) {
            // Parameter[] params = node.getParameters();
            // if (params.length == 1) {
            // Parameter param = params[0];
            // if (param.getType() == null || param.getType()==ClassHelper.OBJECT_TYPE) {
            // param.setType(ClassHelper.STRING_TYPE.makeArray());
            // ClassNode returnType = node.getReturnType();
            // if(returnType == ClassHelper.OBJECT_TYPE) {
            // node.setReturnType(ClassHelper.VOID_TYPE);
            // }
            // }
            // }
            Parameter[] params = methodNode.getParameters();
            ClassNode returnType = methodNode.getReturnType();

            // source of 'static main(args)' would become 'static Object main(Object args)' - so transform here
            if ((modifiers & ClassFileConstants.AccStatic) != 0 && params != null && params.length == 1
                    && methodNode.getName().equals("main")) {
                Parameter p = params[0];
                if (p.getType() == null || p.getType().getName().equals(ClassHelper.OBJECT)) {
                    String name = p.getName();
                    params = new Parameter[1];
                    params[0] = new Parameter(ClassHelper.STRING_TYPE.makeArray(), name);
                    if (returnType.getName().equals(ClassHelper.OBJECT)) {
                        returnType = ClassHelper.VOID_TYPE;
                    }
                }
            }

            methodDeclaration.arguments = createArguments(params, isMain);
            methodDeclaration.returnType = createTypeReferenceForClassNode(returnType);
            methodDeclaration.thrownExceptions = createTypeReferencesForClassNodes(methodNode.getExceptions());
            fixupSourceLocationsForMethodDeclaration(methodDeclaration, methodNode);
            return methodDeclaration;
        }
    }

    /**
     * Create a JDT representation of a groovy MethodNode - but with some parameters defaulting
     */
    private MethodDeclaration genMethodDeclarationVariant(MethodNode methodNode, Argument[] alternativeArguments,
            TypeReference returnType, CompilationResult compilationResult) {
        MethodDeclaration methodDeclaration = new MethodDeclaration(compilationResult);
        int modifiers = methodNode.getModifiers();
        modifiers &= ~(ClassFileConstants.AccSynthetic | ClassFileConstants.AccTransient);
        methodDeclaration.annotations = transformAnnotations(methodNode.getAnnotations());
        methodDeclaration.modifiers = modifiers;
        methodDeclaration.selector = methodNode.getName().toCharArray();
        methodDeclaration.arguments = alternativeArguments;
        methodDeclaration.returnType = returnType;
        fixupSourceLocationsForMethodDeclaration(methodDeclaration, methodNode);
        return methodDeclaration;
    }

    private void configureSuperInterfaces(TypeDeclaration typeDeclaration, ClassNode classNode) {
        ClassNode[] interfaces = classNode.getInterfaces();
        if (interfaces != null && interfaces.length > 0) {
            typeDeclaration.superInterfaces = new TypeReference[interfaces.length];
            for (int i = 0; i < interfaces.length; i++) {
                typeDeclaration.superInterfaces[i] = createTypeReferenceForClassNode(interfaces[i]);
            }
        } else {
            typeDeclaration.superInterfaces = new TypeReference[0];
        }
    }

    private void configureSuperClass(TypeDeclaration typeDeclaration, ClassNode superclass, boolean isEnum) {
        if (isEnum && superclass.getName().equals("java.lang.Enum")) {
            // Don't wire it in, JDT will do it
            typeDeclaration.superclass = null;
        } else {
            // If the start position is 0 the superclass wasn't actually declared, it was added by Groovy
            if (!(superclass.getStart() == 0 && superclass.equals(ClassHelper.OBJECT_TYPE))) {
                typeDeclaration.superclass = createTypeReferenceForClassNode(superclass);
            }
        }
    }

    // --- helper code

    private final static Map<Character, Integer> charToTypeId = new HashMap<Character, Integer>();

    private final static Map<String, Integer> nameToPrimitiveTypeId = new HashMap<String, Integer>();

    static {
        charToTypeId.put('D', TypeIds.T_double);
        charToTypeId.put('I', TypeIds.T_int);
        charToTypeId.put('F', TypeIds.T_float);
        charToTypeId.put('J', TypeIds.T_long);
        charToTypeId.put('Z', TypeIds.T_boolean);
        charToTypeId.put('B', TypeIds.T_byte);
        charToTypeId.put('C', TypeIds.T_char);
        charToTypeId.put('S', TypeIds.T_short);
        nameToPrimitiveTypeId.put("double", TypeIds.T_double);
        nameToPrimitiveTypeId.put("int", TypeIds.T_int);
        nameToPrimitiveTypeId.put("float", TypeIds.T_float);
        nameToPrimitiveTypeId.put("long", TypeIds.T_long);
        nameToPrimitiveTypeId.put("boolean", TypeIds.T_boolean);
        nameToPrimitiveTypeId.put("byte", TypeIds.T_byte);
        nameToPrimitiveTypeId.put("char", TypeIds.T_char);
        nameToPrimitiveTypeId.put("short", TypeIds.T_short);
        nameToPrimitiveTypeId.put("void", TypeIds.T_void);
        try {
            String value = System.getProperty("   earlyTransforms");
            if (value != null) {
                if (value.equalsIgnoreCase("true")) {
                    System.out.println("groovyeclipse.earlyTransforms = true");
                    earlyTransforms = true;
                } else if (value.equalsIgnoreCase("false")) {
                    System.out.println("groovyeclipse.earlyTransforms = false");
                    earlyTransforms = false;
                }
            }
        } catch (Throwable t) {
            // --
        }
    }

    /**
     * For some input array (usually representing a reference), work out the offset positions, assuming they are dotted. <br>
     * Currently this uses the size of each component to move from start towards end. For the very last one it makes the end
     * position 'end' because in some cases just adding 1+length of previous reference isn't enough. For example in java.util.List[]
     * the end will be the end of [] but reference will only contain 'java' 'util' 'List'
     * <p>
     * Because the 'end' is quite often wrong right now (for example on a return type 'java.util.List[]' the end can be two
     * characters off the end (set to the start of the method name...) - we are just computing the positional information from the
     * start.
     */
    // FIXASC seems that sometimes, especially for types that are defined as 'def', but are converted to java.lang.Object, end
    // < start. This causes no end of problems. I don't think it is so much the 'declaration' as the fact that is no reference and
    // really what is computed here is the reference for something actually specified in the source code. Coming up with fake
    // positions for something not specified is not entirely unreasonable we should check
    // if the reference in particular needed creating at all in the first place...
    private long[] positionsFor(char[][] reference, long start, long end) {
        long[] result = new long[reference.length];
        if (start < end) {
            // Do the right thing
            long pos = start;
            for (int i = 0, max = result.length; i < max; i++) {
                long s = pos;
                pos = pos + reference[i].length - 1; // jump to the last char of the name
                result[i] = ((s << 32) | pos);
                pos += 2; // jump onto the following '.' then off it
            }
        } else {
            // FIXASC this case shouldn't happen (end<start) - uncomment following if to collect diagnostics
            long pos = (start << 32) | start;
            for (int i = 0, max = result.length; i < max; i++) {
                result[i] = pos;
            }
        }
        return result;
    }

    /**
     * Convert from an array ClassNode into a TypeReference. Name of the node is expected to be something like java.lang.String[][]
     * - primitives should be getting handled by the other create method (and have a sig like '[[I')
     */
    private TypeReference createTypeReferenceForArrayNameTrailingBrackets(ClassNode node, int start, int end) {
        String name = node.getName();
        int dim = 0;
        int pos = name.length() - 2;
        ClassNode componentType = node;
        // jump back counting dimensions
        while (pos > 0 && name.charAt(pos) == '[') {
            dim++;
            pos -= 2;
            componentType = componentType.getComponentType();
        }
        if (componentType.isPrimitive()) {
            Integer ii = charToTypeId.get(name.charAt(dim));
            if (ii == null) {
                throw new IllegalStateException(
                        "node " + node + " reported it had a primitive component type, but it does not...");
            } else {
                TypeReference baseTypeReference = TypeReference.baseTypeReference(ii, dim);
                baseTypeReference.sourceStart = start;
                baseTypeReference.sourceEnd = start + componentType.getName().length();
                return baseTypeReference;
            }
        }
        if (dim == 0) {
            throw new IllegalStateException("Array classnode with dimensions 0?? node:" + node.getName());
        }
        // array component is something like La.b.c; ... or sometimes just [[Z (where Z is a type, not primitive)
        String arrayComponentTypename = name.substring(0, pos + 2);
        if (arrayComponentTypename.indexOf(".") == -1) {
            return createJDTArrayTypeReference(arrayComponentTypename, dim, start, end);
        } else {
            return createJDTArrayQualifiedTypeReference(arrayComponentTypename, dim, start, end);
        }
    }

    /**
     * Format will be [[I or [[Ljava.lang.String; - this latter form is really not right but groovy can produce it so we need to
     * cope with it.
     */
    private TypeReference createTypeReferenceForArrayNameLeadingBrackets(ClassNode node, int start, int end) {
        String name = node.getName();
        int dim = 0;
        ClassNode componentType = node;
        while (name.charAt(dim) == '[') {
            dim++;
            componentType = componentType.getComponentType();
        }
        if (componentType.isPrimitive()) {
            Integer ii = charToTypeId.get(name.charAt(dim));
            if (ii == null) {
                throw new IllegalStateException(
                        "node " + node + " reported it had a primitive component type, but it does not...");
            } else {
                TypeReference baseTypeReference = TypeReference.baseTypeReference(ii, dim);
                baseTypeReference.sourceStart = start;
                baseTypeReference.sourceEnd = start + componentType.getName().length();
                return baseTypeReference;
            }
        } else {
            String arrayComponentTypename = name.substring(dim);
            if (arrayComponentTypename.charAt(arrayComponentTypename.length() - 1) == ';') {
                arrayComponentTypename = name.substring(dim + 1, name.length() - 1); // chop off '['s 'L' and ';'
            }
            if (arrayComponentTypename.indexOf(".") == -1) {
                return createJDTArrayTypeReference(arrayComponentTypename, dim, start, end);
            } else {
                return createJDTArrayQualifiedTypeReference(arrayComponentTypename, dim, start, end);
            }
        }
    }

    // because 'length' is computed as 'end-start+1' and start==-1 indicates it does not exist, then
    // to have a length of 0 the end must be -2.
    private static long NON_EXISTENT_POSITION = toPos(-1, -2);

    /**
     * Pack start and end positions into a long - no adjustments are made to the values passed in, the caller must make any required
     * adjustments.
     */
    private static long toPos(long start, long end) {
        if (start == 0 && end <= 0) {
            return NON_EXISTENT_POSITION;
        }
        return ((start << 32) | end);
    }

    private TypeReference createTypeReferenceForClassNode(GenericsType genericsType) {
        if (genericsType.isWildcard()) {
            ClassNode[] bounds = genericsType.getUpperBounds();
            if (bounds != null) {
                // FIXASC other bounds?
                // positions example: (29>31)Set<(33>54)? extends (43>54)Serializable>
                TypeReference boundReference = createTypeReferenceForClassNode(bounds[0]);
                Wildcard wildcard = new Wildcard(Wildcard.EXTENDS);
                wildcard.sourceStart = genericsType.getStart();
                wildcard.sourceEnd = boundReference.sourceEnd();
                wildcard.bound = boundReference;
                return wildcard;
            } else if (genericsType.getLowerBound() != null) {
                // positions example: (67>69)Set<(71>84)? super (79>84)Number>
                TypeReference boundReference = createTypeReferenceForClassNode(genericsType.getLowerBound());
                Wildcard wildcard = new Wildcard(Wildcard.SUPER);
                wildcard.sourceStart = genericsType.getStart();
                wildcard.sourceEnd = boundReference.sourceEnd();
                wildcard.bound = boundReference;
                return wildcard;
            } else {
                Wildcard w = new Wildcard(Wildcard.UNBOUND);
                w.sourceStart = genericsType.getStart();
                w.sourceEnd = genericsType.getStart();
                return w;
            }
            // FIXASC what does the check on this next really line mean?
        } else if (!genericsType.getType().isGenericsPlaceHolder()) {
            TypeReference typeReference = createTypeReferenceForClassNode(genericsType.getType());
            return typeReference;
        } else {
            // this means it is a placeholder. As an example, if the reference is to 'List'
            // then the genericsType info may include a placeholder for the type variable (as the user
            // didn't fill it in as anything) and so for this example the genericsType is 'E extends java.lang.Object'
            // I don't think we need a type reference for this as the type references we are constructed
            // here are representative of what the user did in the source, not the resolved result of that.
            // throw new GroovyEclipseBug();
            return null;
        }
    }

    private TypeReference[] createTypeReferencesForClassNodes(ClassNode[] classNodes) {
        if (classNodes == null || classNodes.length == 0) {
            return null;
        }
        TypeReference[] refs = new TypeReference[classNodes.length];
        for (int i = 0; i < classNodes.length; i++) {
            refs[i] = createTypeReferenceForClassNode(classNodes[i]);
        }
        return refs;
    }

    private TypeReference createTypeReferenceForClassNode(ClassNode classNode) {
        int start = startOffset(classNode);
        int end = endOffset(classNode);

        List<TypeReference> typeArguments = null;

        // need to distinguish between raw usage of a type 'List' and generics
        // usage 'List<T>' -
        // it basically depends upon whether the type variable reference can be
        // resolved within the current 'scope' - if it cannot then this is probably a raw
        // reference (yes?)

        if (classNode.isUsingGenerics()) {
            GenericsType[] genericsInfo = classNode.getGenericsTypes();
            if (genericsInfo != null) {
                for (int g = 0; g < genericsInfo.length; g++) {
                    // ClassNode typeArgumentClassNode = genericsInfo[g].getType();
                    TypeReference tr = createTypeReferenceForClassNode(genericsInfo[g]);
                    if (tr != null) {
                        if (typeArguments == null) {
                            typeArguments = new ArrayList<TypeReference>();
                        }
                        typeArguments.add(tr);
                    }
                    // if (!typeArgumentClassNode.isGenericsPlaceHolder()) {
                    // typeArguments.add(createTypeReferenceForClassNode(typeArgumentClassNode));
                    // }
                }
            }
        }

        String name = classNode.getName();

        if (name.length() == 1 && name.charAt(0) == '?') {
            return new Wildcard(Wildcard.UNBOUND);
        }

        int arrayLoc = name.indexOf("[");
        if (arrayLoc == 0) {
            return createTypeReferenceForArrayNameLeadingBrackets(classNode, start, end);
        } else if (arrayLoc > 0) {
            return createTypeReferenceForArrayNameTrailingBrackets(classNode, start, end);
        }

        if (nameToPrimitiveTypeId.containsKey(name)) {
            return TypeReference.baseTypeReference(nameToPrimitiveTypeId.get(name), 0);
        }

        if (name.indexOf(".") == -1) {
            if (typeArguments == null) {
                TypeReference tr = verify(new SingleTypeReference(name.toCharArray(), toPos(start, end - 1)));

                if (!checkGenerics) {
                    tr.bits |= TypeReference.IgnoreRawTypeCheck;
                }
                return tr;
            } else {
                // FIXASC determine when array dimension used in this case,
                // is it 'A<T[]> or some silliness?
                long l = toPos(start, end - 1);
                return new ParameterizedSingleTypeReference(name.toCharArray(),
                        typeArguments.toArray(new TypeReference[typeArguments.size()]), 0, l);
            }
        } else {
            char[][] compoundName = CharOperation.splitOn('.', name.toCharArray());
            if (typeArguments == null) {
                TypeReference tr = new QualifiedTypeReference(compoundName, positionsFor(compoundName, start, end));
                if (!checkGenerics) {
                    tr.bits |= TypeReference.IgnoreRawTypeCheck;
                }
                return tr;
            } else {
                // FIXASC support individual parameterization of component
                // references A<String>.B<Wibble>
                TypeReference[][] typeReferences = new TypeReference[compoundName.length][];
                typeReferences[compoundName.length - 1] = typeArguments
                        .toArray(new TypeReference[typeArguments.size()]);
                return new ParameterizedQualifiedTypeReference(compoundName, typeReferences, 0,
                        positionsFor(compoundName, start, end));
            }
        }
    }

    private final static boolean DEBUG = false;

    // FIXASC this is useless - use proper positions
    private long[] getPositionsFor(char[][] compoundName) {
        long[] ls = new long[compoundName.length];
        for (int i = 0; i < compoundName.length; i++) {
            ls[i] = 0;
        }
        return ls;
    }

    // FIXASC are costly regens being done for all the classes???

    @SuppressWarnings("unchecked")
    @Override
    public void generateCode() {
        boolean successful = processToPhase(Phases.ALL);
        if (successful) {

            // At the end of this method we want to make this call for each of the classes generated during processing
            //
            // compilationResult.record(classname.toCharArray(), new GroovyClassFile(classname, classbytes, foundBinding, path));
            //
            // For each generated class (in groovyCompilationUnit.getClasses()) we know:
            // String classname = groovyClass.getName(); = this is the name of the generated type (doesn't matter where the
            // declaration was)
            // byte[] classbytes = groovyClass.getBytes(); = duh
            // String path = groovyClass.getName().replace('.', '/'); = where to put it on disk

            // The only tricky piece of information is discovering the binding that gave rise to the type. This is complicated
            // in groovy because it is not policing that the package name matches the directory structure.
            // Effectively the connection between the TypeDeclaration (which points to the binding) and the
            // groovy created component is lost - if that were maintained we would not have to go hunting for it.
            // On finishing processing we have access to the generated classes but GroovyClassFile objects have no idea what
            // their originating ClassNode was.

            // Under eclipse I've extended GroovyClassFile objects to remember their sourceUnit and ClassNode - this means
            // we have to do very little hunting for the binding and don't have to mess around with strings (chopping off
            // packages, etc).

            // This returns all of them, for all source files
            List<GroovyClass> classes = groovyCompilationUnit.getClasses();

            if (DEBUG) {
                log("Processing sourceUnit " + groovySourceUnit.getName());
            }

            for (GroovyClass clazz : classes) {
                ClassNode classnode = clazz.getClassNode();
                if (DEBUG) {
                    log("Looking at class " + clazz.getName());
                    log("ClassNode where it came from " + classnode);
                }
                // Only care about those coming about because of this groovySourceUnit
                if (clazz.getSourceUnit() == groovySourceUnit) {
                    if (DEBUG) {
                        log("It is from this source unit");
                    }
                    // Worth continuing
                    String classname = clazz.getName();
                    SourceTypeBinding binding = null;
                    if (types != null && types.length != 0) {
                        binding = findBinding(types, clazz.getClassNode());
                    }
                    if (DEBUG) {
                        log("Binding located?" + (binding != null));
                    }
                    if (binding == null) {
                        // closures will be represented as InnerClassNodes
                        ClassNode current = classnode;
                        while (current instanceof InnerClassNode && binding == null) {
                            current = ((InnerClassNode) current).getOuterClass();
                            binding = findBinding(types, current);
                            if (DEBUG) {
                                log("Had another look because it is in an InnerClassNode, found binding? "
                                        + (binding != null));
                            }
                        }
                    }
                    if (binding == null) {
                        RuntimeException rEx = new RuntimeException("Couldn't find binding for '" + classname
                                + "': do you maybe have a duplicate type around?");
                        rEx.printStackTrace();
                        Util.log(rEx, "Couldn't find binding for '" + classname
                                + "': do you maybe have a duplicate type around?");
                    } else {
                        // Suppress class file output if it is a script
                        boolean isScript = false;
                        if (binding.scope != null && (binding.scope.parent instanceof GroovyCompilationUnitScope)) {
                            GroovyCompilationUnitScope gcuScope = (GroovyCompilationUnitScope) binding.scope.parent;
                            if (gcuScope.isScript()) {
                                isScript = true;
                            }
                        }
                        if (!isScript) {
                            byte[] classbytes = clazz.getBytes();
                            String path = clazz.getName().replace('.', '/');
                            compilationResult.record(classname.toCharArray(),
                                    new GroovyClassFile(classname, classbytes, binding, path));
                        }
                    }
                }
            }
        }
    }

    private void log(String message) {
        System.out.println(message);
    }

    private SourceTypeBinding findBinding(TypeDeclaration[] typedeclarations, ClassNode cnode) {
        for (TypeDeclaration typedeclaration : typedeclarations) {
            GroovyTypeDeclaration groovyTypeDeclaration = (GroovyTypeDeclaration) typedeclaration;
            if (groovyTypeDeclaration.getClassNode().equals(cnode)) {
                return groovyTypeDeclaration.binding;
            }
            if (typedeclaration.memberTypes != null) {
                SourceTypeBinding binding = findBinding(typedeclaration.memberTypes, cnode);
                if (binding != null) {
                    return binding;
                }
            }
        }
        return null;
    }

    // ---

    private int startOffset(org.codehaus.groovy.ast.ASTNode astnode) {
        // int l = fromLineColumnToOffset(astnode.getLineNumber(),
        // astnode.getColumnNumber()) - 1;
        // return l;
        return (Math.max(astnode.getStart(), 0));
    }

    private int endOffset(org.codehaus.groovy.ast.ASTNode astnode) {
        // starts from 0 and dont want the char after it, i want the last char
        // return fromLineColumnToOffset(astnode.getLineNumber(),
        // astnode.getLastColumnNumber()) - 2;
        // return astnode.getEnd();
        return (Math.max(astnode.getEnd(), 0));
    }

    // here be dragons
    private void recordProblems(List<?> errors) {
        // FIXASC look at this error situation (described below), surely we need to do it?
        // Due to the nature of driving all groovy entities through compilation together, we can accumulate messages for other
        // compilation units whilst processing the one we wanted to. Per GRE396 this can manifest as recording the wrong thing
        // against the wrong type. That is the only case I have seen of it, so I'm not putting in the general mechanism for all
        // errors yet, I'm just dealing with RuntimeParserExceptions. The general strategy would be to compare the ModuleNode
        // for each message with the ModuleNode currently being processed - if they differ then this isn't a message for this
        // unit and so we ignore it. If we do deal with it then we remember that we did (in errorsRecorded) and remove it from
        // the list of those to process.

        List errorsRecorded = new ArrayList();
        // FIXASC poor way to get the errors attached to the files
        // FIXASC does groovy ever produce warnings? How are they treated here?
        for (Iterator<?> iterator = errors.iterator(); iterator.hasNext();) {
            SyntaxException syntaxException = null;
            Message message = (Message) iterator.next();
            StringWriter sw = new StringWriter();
            message.write(new PrintWriter(sw));
            String msg = sw.toString();
            CategorizedProblem p = null;
            int line = 0;
            int sev = 0;
            int scol = 0;
            int ecol = 0;
            // LocatedMessage instances are produced sometimes, e.g. by grails ast transforms, use the context for position
            if (message instanceof LocatedMessage) {
                CSTNode context = ((LocatedMessage) message).getContext();
                if (context instanceof Token) {
                    line = context.getStartLine();
                    scol = context.getStartColumn();
                    String text = ((Token) context).getText();
                    ecol = scol + (text == null ? 1 : (text.length() - 1));
                }
            }
            if (message instanceof SimpleMessage) {
                SimpleMessage simpleMessage = (SimpleMessage) message;
                sev |= ProblemSeverities.Error;
                String simpleText = simpleMessage.getMessage();
                if (simpleText.length() > 1 && simpleText.charAt(0) == '\n') {
                    simpleText = simpleText.substring(1);
                }
                msg = "Groovy:" + simpleText;
                if (msg.indexOf("\n") != -1) {
                    msg = msg.substring(0, msg.indexOf("\n"));
                }
            }
            if (message instanceof SyntaxErrorMessage) {
                SyntaxErrorMessage errorMessage = (SyntaxErrorMessage) message;
                syntaxException = errorMessage.getCause();
                sev |= ProblemSeverities.Error;
                // FIXASC in the short term, prefixed groovy to indicate
                // where it came from
                String actualMessage = syntaxException.getMessage();
                if (actualMessage.length() > 1 && actualMessage.charAt(0) == '\n') {
                    actualMessage = actualMessage.substring(1);
                }
                msg = "Groovy:" + actualMessage;
                if (msg.indexOf("\n") != -1) {
                    msg = msg.substring(0, msg.indexOf("\n"));
                }
                line = syntaxException.getLine();
                scol = errorMessage.getCause().getStartColumn();
                ecol = errorMessage.getCause().getEndColumn() - 1;
            }
            int soffset = -1;
            int eoffset = -1;
            if (message instanceof ExceptionMessage) {
                ExceptionMessage em = (ExceptionMessage) message;
                sev |= ProblemSeverities.Error;
                if (em.getCause() instanceof RuntimeParserException) {
                    RuntimeParserException rpe = (RuntimeParserException) em.getCause();
                    sev |= ProblemSeverities.Error;
                    msg = "Groovy:" + rpe.getMessage();
                    if (msg.indexOf("\n") != -1) {
                        msg = msg.substring(0, msg.indexOf("\n"));
                    }
                    ModuleNode errorModuleNode = rpe.getModule();
                    ModuleNode thisModuleNode = this.getModuleNode();
                    if (!errorModuleNode.equals(thisModuleNode)) {
                        continue;
                    }
                    soffset = rpe.getNode().getStart();
                    eoffset = rpe.getNode().getEnd() - 1;
                    // need to work out the line again as it may be wrong
                    line = 0;
                    while (compilationResult.lineSeparatorPositions[line] < soffset
                            && line < compilationResult.lineSeparatorPositions.length) {
                        line++;
                    }

                    line++; // from an array index to a real 'line number'
                }
            }
            if (syntaxException instanceof PreciseSyntaxException) {
                soffset = ((PreciseSyntaxException) syntaxException).getStartOffset();
                eoffset = ((PreciseSyntaxException) syntaxException).getEndOffset();
                // need to work out the line again as it may be wrong
                line = 0;
                while (line < compilationResult.lineSeparatorPositions.length
                        && compilationResult.lineSeparatorPositions[line] < soffset) {
                    line++;
                }
                ;
                line++; // from an array index to a real 'line number'
            } else {
                if (soffset == -1) {
                    soffset = getOffset(compilationResult.lineSeparatorPositions, line, scol);
                }
                if (eoffset == -1) {
                    eoffset = getOffset(compilationResult.lineSeparatorPositions, line, ecol);
                }
            }
            if (soffset > eoffset) {
                eoffset = soffset;
            }
            if (soffset > sourceEnd) {
                soffset = sourceEnd;
                eoffset = sourceEnd;
            }

            char[] filename = getFileName();
            p = new DefaultProblemFactory().createProblem(filename, 0, new String[] { msg }, 0,
                    new String[] { msg }, sev, soffset, eoffset, line, scol);
            this.problemReporter.record(p, compilationResult, this, false);
            errorsRecorded.add(message);
            System.err.println(new String(compilationResult.getFileName()) + ": " + line + " " + msg);
        }
        errors.removeAll(errorsRecorded);
    }

    private int getOffset(int[] lineSeparatorPositions, int line, int col) {
        if (lineSeparatorPositions.length > (line - 2) && line > 1) {
            return lineSeparatorPositions[line - 2] + col;
        } else {
            return col;
        }
    }

    @Override
    public CompilationUnitScope buildCompilationUnitScope(LookupEnvironment lookupEnvironment) {
        GroovyCompilationUnitScope gcus = new GroovyCompilationUnitScope(this, lookupEnvironment);
        gcus.setIsScript(isScript);
        return gcus;
    }

    public ModuleNode getModuleNode() {
        return groovySourceUnit == null ? null : groovySourceUnit.getAST();
    }

    public SourceUnit getSourceUnit() {
        return groovySourceUnit;
    }

    // TODO find a better home for this?
    @Override
    public org.eclipse.jdt.core.dom.CompilationUnit getSpecialDomCompilationUnit(org.eclipse.jdt.core.dom.AST ast) {
        return new org.codehaus.jdt.groovy.core.dom.GroovyCompilationUnit(ast);
    }

    /**
     * Try to get the source locations for type declarations to be as correct as possible
     */
    private void fixupSourceLocationsForTypeDeclaration(GroovyTypeDeclaration typeDeclaration,
            ClassNode classNode) {
        // start and end of the name of class
        // scripts do not have a name, so use start instead
        typeDeclaration.sourceStart = Math.max(classNode.getNameStart(), classNode.getStart());
        typeDeclaration.sourceEnd = Math.max(classNode.getNameEnd(), classNode.getStart());

        // start and end of the entire declaration including Javadoc and ending at the last close bracket
        int line = classNode.getLineNumber();
        Javadoc doc = findJavadoc(line);
        if (doc != null) {
            if (imports != null && imports.length > 0) {
                if (doc.sourceStart < imports[imports.length - 1].sourceStart) {
                    // ignore the doc if it should be associated with and import statement
                    doc = null;
                }
            } else if (currentPackage != null) {
                if (doc.sourceStart < currentPackage.sourceStart) {
                    // ignore the doc if it should be associated with the package statement
                    doc = null;
                }
            }
        }

        typeDeclaration.javadoc = doc;
        typeDeclaration.declarationSourceStart = doc == null ? classNode.getStart() : doc.sourceStart;
        // Without the -1 we can hit AIOOBE in org.eclipse.jdt.internal.core.Member.getJavadocRange where it calls getText()
        // because the source range length causes us to ask for more data than is in the buffer. What does this mean?
        // For hovers, the AIOOBE is swallowed and you just see no hover box.
        typeDeclaration.declarationSourceEnd = classNode.getEnd() - 1;

        // * start at the opening brace and end at the closing brace
        // except that scripts do not have a name, use the start instead
        // FIXADE this is not exactly right since getNameEnd() comes before extends and implements clauses
        typeDeclaration.bodyStart = Math.max(classNode.getNameEnd(), classNode.getStart());

        // seems to be the same as declarationSourceEnd
        typeDeclaration.bodyEnd = classNode.getEnd() - 1;

        // start of the modifiers after the javadoc
        typeDeclaration.modifiersSourceStart = classNode.getStart();

    }

    /**
     * Try to get the source locations for constructor declarations to be as correct as possible
     */
    private void fixupSourceLocationsForConstructorDeclaration(ConstructorDeclaration ctorDeclaration,
            ConstructorNode ctorNode) {
        ctorDeclaration.sourceStart = ctorNode.getNameStart();
        ctorDeclaration.sourceEnd = ctorNode.getNameEnd();

        // start and end of method declaration including JavaDoc
        // ending with closing '}' or ';' if abstract
        int line = ctorNode.getLineNumber();
        Javadoc doc = findJavadoc(line);
        ctorDeclaration.javadoc = doc;
        ctorDeclaration.declarationSourceStart = doc == null ? ctorNode.getStart() : doc.sourceStart;
        ctorDeclaration.declarationSourceEnd = ctorNode.getEnd() - 1;

        // start of method's modifier list (after Javadoc is ended)
        ctorDeclaration.modifiersSourceStart = ctorNode.getStart();

        // opening bracket
        ctorDeclaration.bodyStart =
                // try for opening bracket
                ctorNode.getCode() != null ? ctorNode.getCode().getStart() :
                // handle abstract constructor. not sure if this can ever happen, but you never know with Groovy
                        ctorNode.getNameEnd();

        // closing bracket or ';' same as declarationSourceEnd
        ctorDeclaration.bodyEnd = ctorNode.getEnd() - 1;
    }

    /**
     * Try to get the source locations for method declarations to be as correct as possible
     */
    private void fixupSourceLocationsForMethodDeclaration(MethodDeclaration methodDeclaration,
            MethodNode methodNode) {
        // run() method for scripts has no name, so use the start of the method instead
        methodDeclaration.sourceStart = Math.max(methodNode.getNameStart(), methodNode.getStart());
        methodDeclaration.sourceEnd = Math.max(methodNode.getNameEnd(), methodNode.getStart());

        // start and end of method declaration including JavaDoc
        // ending with closing '}' or ';' if abstract
        int line = methodNode.getLineNumber();
        Javadoc doc = findJavadoc(line);
        methodDeclaration.javadoc = doc;
        methodDeclaration.declarationSourceStart = doc == null ? methodNode.getStart() : doc.sourceStart;
        methodDeclaration.declarationSourceEnd = methodNode.getEnd() - 1;

        // start of method's modifier list (after Javadoc is ended)
        methodDeclaration.modifiersSourceStart = methodNode.getStart();

        // opening bracket
        methodDeclaration.bodyStart =
                // try for opening bracket
                methodNode.getCode() != null ? methodNode.getCode().getStart() :
                // run() method for script has no opening bracket
                // also need to handle abstract methods
                        Math.max(methodNode.getNameEnd(), methodNode.getStart());

        // closing bracket or ';' same as declarationSourceEnd
        methodDeclaration.bodyEnd = methodNode.getEnd() - 1;
    }

    /**
     * Try to get the source locations for field declarations to be as correct as possible
     */
    private void fixupSourceLocationsForFieldDeclaration(FieldDeclaration fieldDeclaration, FieldNode fieldNode,
            boolean isEnumField) {
        // TODO (groovy) each area marked with a '*' is only approximate
        // and can be revisited to make more precise

        // Here, we distinguish between the declaration and the fragment
        // e.g.- def x = 9, y = "l"
        // 'x = 9,' and 'y = "l"' are the fragments and 'def x = 9, y = "l"' is the declaration

        // the start and end of the fragment name
        fieldDeclaration.sourceStart = fieldNode.getNameStart();
        fieldDeclaration.sourceEnd = fieldNode.getNameEnd();

        // start of the declaration (including javadoc?)
        int line = fieldNode.getLineNumber();
        Javadoc doc = findJavadoc(line);
        fieldDeclaration.javadoc = doc;

        if (isEnumField) {
            // they have no 'leading' type declaration or modifiers
            fieldDeclaration.declarationSourceStart = doc == null ? fieldNode.getNameStart() : doc.sourceStart;// fieldNode.getNameStart();
            fieldDeclaration.declarationSourceEnd = fieldNode.getNameEnd() - 1;
        } else {
            fieldDeclaration.declarationSourceStart = doc == null ? fieldNode.getStart() : doc.sourceStart;
            // the end of the fragment including initializer (and trailing ',')
            fieldDeclaration.declarationSourceEnd = fieldNode.getEnd() - 1;
        }
        // * first character of the declaration's modifier
        fieldDeclaration.modifiersSourceStart = fieldNode.getStart();

        // end of the entire Field declaration (after all fragments and including ';' if exists)
        fieldDeclaration.declarationEnd = fieldNode.getEnd();

        // * end of the type declaration part of the declaration (the same for each fragment)
        // eg- int x, y corresponds to the location after 'int'
        fieldDeclaration.endPart1Position = fieldNode.getNameStart();

        // * just before the start of the next fragment
        // (or the end of the entire declaration if it is the last one)
        // (how is this different from declarationSourceEnd?)
        fieldDeclaration.endPart2Position = fieldNode.getEnd() - 1;
    }

    /**
     * @return true if this is varargs, using the same definition as in AsmClassGenerator.isVargs(Parameter[])
     */
    private boolean isVargs(Parameter[] parameters) {
        if (parameters.length == 0) {
            return false;
        }
        ClassNode clazz = parameters[parameters.length - 1].getType();
        return (clazz.isArray());
    }

    // for testing
    public String print() {
        return toString();
    }

    public GroovyCompilationUnitScope getScope() {
        return (GroovyCompilationUnitScope) scope;
    }

    // -- overridden behaviour from the supertype

    @Override
    public void resolve() {
        processToPhase(Phases.SEMANTIC_ANALYSIS);
        checkForTags();
        setComments();
    }

    /**
     * Check any comments from the source file for task tag markers.
     */
    private void checkForTags() {
        if (this.compilerOptions == null) {
            return;
        }
        List<Comment> comments = groovySourceUnit.getComments();
        if (comments == null) {
            return;
        }
        char[][] taskTags = this.compilerOptions.taskTags;
        char[][] taskPriorities = this.compilerOptions.taskPriorities;
        boolean caseSensitiveTags = this.compilerOptions.isTaskCaseSensitive;
        try {
            if (taskTags != null) {
                // For each comment find all task tags within it and cope with
                for (Comment comment : comments) {
                    List<TaskEntry> allTasksInComment = new ArrayList<TaskEntry>();
                    for (int t = 0; t < taskTags.length; t++) {
                        String taskTag = new String(taskTags[t]);
                        String taskPriority = null;
                        if (taskPriorities != null) {
                            taskPriority = new String(taskPriorities[t]);
                        }
                        allTasksInComment.addAll(comment.getPositionsOf(taskTag, taskPriority,
                                compilationResult.lineSeparatorPositions, caseSensitiveTags));
                    }
                    if (!allTasksInComment.isEmpty()) {
                        // Need to check quickly for clashes
                        for (int t1 = 0; t1 < allTasksInComment.size(); t1++) {
                            for (int t2 = 0; t2 < allTasksInComment.size(); t2++) {
                                if (t1 == t2)
                                    continue;
                                TaskEntry taskOne = allTasksInComment.get(t1);
                                TaskEntry taskTwo = allTasksInComment.get(t2);
                                if (DEBUG_TASK_TAGS) {
                                    System.out.println(
                                            "Comparing " + taskOne.toString() + " and " + taskTwo.toString());
                                }
                                if ((taskOne.start + taskOne.taskTag.length() + 1) == taskTwo.start) {
                                    // Adjacent tags
                                    taskOne.isAdjacentTo = taskTwo;
                                } else {
                                    if ((taskOne.getEnd() > taskTwo.start) && (taskOne.start < taskTwo.start)) {
                                        taskOne.setEnd(taskTwo.start - 1);
                                        if (DEBUG_TASK_TAGS) {
                                            System.out.println(
                                                    "trim " + taskOne.toString() + " and " + taskTwo.toString());
                                        }
                                    } else if (taskTwo.getEnd() > taskOne.start && taskTwo.start < taskOne.start) {
                                        taskTwo.setEnd(taskOne.start - 1);
                                        if (DEBUG_TASK_TAGS) {
                                            System.out.println(
                                                    "trim " + taskOne.toString() + " and " + taskTwo.toString());
                                        }
                                    }
                                }
                            }
                        }
                        for (TaskEntry taskEntry : allTasksInComment) {
                            this.problemReporter.referenceContext = this;
                            if (DEBUG_TASK_TAGS) {
                                System.out.println("Adding task " + taskEntry.toString());
                            }
                            problemReporter.task(taskEntry.taskTag, taskEntry.getText(), taskEntry.taskPriority,
                                    taskEntry.start, taskEntry.getEnd());
                        }
                    }
                }
            }
        } catch (AbortCompilation ac) {
            // that is ok... probably cancelled
        } catch (Throwable t) {
            Util.log(t, "Unexpected problem processing task tags in " + groovySourceUnit.getName());
            new RuntimeException("Unexpected problem processing task tags in " + groovySourceUnit.getName(), t)
                    .printStackTrace();
        }
    }

    @Override
    public void analyseCode() {
        processToPhase(Phases.CANONICALIZATION);

    }

    @Override
    public void abort(int abortLevel, CategorizedProblem problem) {
        // FIXASC look at callers of this, should we be following the abort on first problem policy?
        super.abort(abortLevel, problem);
    }

    @Override
    public void checkUnusedImports() {
        super.checkUnusedImports();
    }

    @Override
    public void cleanUp() {
        // FIXASC any tidy up for us to do?
        super.cleanUp();
    }

    @Override
    public CompilationResult compilationResult() {
        return super.compilationResult();
    }

    @Override
    public TypeDeclaration declarationOfType(char[][] typeName) {
        return super.declarationOfType(typeName);
    }

    @Override
    public void finalizeProblems() {
        super.finalizeProblems();
    }

    @Override
    public char[] getFileName() {
        return super.getFileName();
    }

    @Override
    public char[] getMainTypeName() {
        // FIXASC necessary to return something for groovy?
        return super.getMainTypeName();
    }

    @Override
    public boolean hasErrors() {
        return super.hasErrors();
    }

    @Override
    public boolean isEmpty() {
        return super.isEmpty();
    }

    @Override
    public boolean isPackageInfo() {
        return super.isPackageInfo();
    }

    @Override
    public StringBuffer print(int indent, StringBuffer output) {
        // FIXASC additional stuff to print?
        return super.print(indent, output);
    }

    @Override
    public void propagateInnerEmulationForAllLocalTypes() {
        // FIXASC anything to do here for groovy inner types?
        super.propagateInnerEmulationForAllLocalTypes();
    }

    @Override
    public void record(LocalTypeBinding localType) {
        super.record(localType);
    }

    @Override
    public void recordStringLiteral(StringLiteral literal, boolean fromRecovery) {
        // FIXASC assert not called for groovy, surely
        super.recordStringLiteral(literal, fromRecovery);
    }

    @Override
    public void recordSuppressWarnings(IrritantSet irritants, Annotation annotation, int scopeStart, int scopeEnd) {
        super.recordSuppressWarnings(irritants, annotation, scopeStart, scopeEnd);
    }

    @Override
    public void tagAsHavingErrors() {
        super.tagAsHavingErrors();
    }

    @Override
    public void traverse(ASTVisitor visitor, CompilationUnitScope unitScope) {
        // FIXASC are we well formed enough for this?
        super.traverse(visitor, unitScope);
    }

    @Override
    public ASTNode concreteStatement() {
        // FIXASC assert not called for groovy, surely
        return super.concreteStatement();
    }

    @Override
    public boolean isImplicitThis() {
        // FIXASC assert not called for groovy, surely
        return super.isImplicitThis();
    }

    @Override
    public boolean isSuper() {
        // FIXASC assert not called for groovy, surely
        return super.isSuper();
    }

    @Override
    public boolean isThis() {
        // FIXASC assert not called for groovy, surely
        return super.isThis();
    }

    @Override
    public int sourceEnd() {
        return super.sourceEnd();
    }

    @Override
    public int sourceStart() {
        return super.sourceStart();
    }

    @Override
    public String toString() {
        // FIXASC anything to add?
        return super.toString();
    }

    @Override
    public void traverse(ASTVisitor visitor, BlockScope scope) {
        // FIXASC in a good state for traversal? what would cause this to trigger?
        super.traverse(visitor, scope);
    }

    // -- builders for JDT TypeReference subclasses

    /**
     * Create a JDT ArrayTypeReference.<br>
     * Positional information:
     * <p>
     * For a single array reference, for example 'String[]' start will be 'S' and end will be the char after ']'. When the
     * ArrayTypeReference is built we need these positions for the result: sourceStart - the 'S'; sourceEnd - the ']';
     * originalSourceEnd - the 'g'
     */
    private ArrayTypeReference createJDTArrayTypeReference(String arrayComponentTypename, int dimensions, int start,
            int end) {
        ArrayTypeReference atr = new ArrayTypeReference(arrayComponentTypename.toCharArray(), dimensions,
                toPos(start, end - 1));
        atr.originalSourceEnd = atr.sourceStart + arrayComponentTypename.length() - 1;
        return atr;
    }

    /**
     * Create a JDT ArrayQualifiedTypeReference.<br>
     * Positional information:
     * <p>
     * For a qualified array reference, for example 'java.lang.Number[][]' start will be 'j' and end will be the char after ']'.
     * When the ArrayQualifiedTypeReference is built we need these positions for the result: sourceStart - the 'j'; sourceEnd - the
     * final ']'; the positions computed for the reference components would be j..a l..g and N..r
     */
    private ArrayQualifiedTypeReference createJDTArrayQualifiedTypeReference(String arrayComponentTypename,
            int dimensions, int start, int end) {
        char[][] compoundName = CharOperation.splitOn('.', arrayComponentTypename.toCharArray());
        ArrayQualifiedTypeReference aqtr = new ArrayQualifiedTypeReference(compoundName, dimensions,
                positionsFor(compoundName, start, end - dimensions * 2));
        aqtr.sourceEnd = end - 1;
        return aqtr;
    }

    /**
     * Check the supplied TypeReference. If there are problems with the construction of a TypeReference then these may not surface
     * until it is used later, perhaps when reconciling. The easiest way to check there will not be problems later is to check it at
     * construction time.
     * 
     * @param toVerify the type reference to check
     * @param does the type reference really exist in the source or is it conjured up based on the source
     * @return the verified type reference
     * @throws IllegalStateException if the type reference is malformed
     */
    private TypeReference verify(TypeReference toVerify) {
        if (GroovyCheckingControl.checkTypeReferences) {
            if (toVerify.getClass().equals(SingleTypeReference.class)) {
                SingleTypeReference str = (SingleTypeReference) toVerify;
                if (str.sourceStart == -1) {
                    if (str.sourceEnd != -2) {
                        throw new IllegalStateException(
                                "TypeReference '" + new String(str.token) + " should end at -2");
                    }
                } else {
                    if (str.sourceEnd < str.sourceStart) {
                        throw new IllegalStateException("TypeReference '" + new String(str.token)
                                + " should end at " + str.sourceStart + " or later");
                    }
                }
            } else {
                throw new IllegalStateException(
                        "Cannot verify type reference of this class " + toVerify.getClass());
            }
        }
        return toVerify;
    }

    public void tagAsScript() {
        this.isScript = true;
    }

}