/*******************************************************************************
 * Copyright (c) 2000, 2017 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Stephan Herrmann - contributions for
 *     							bug 337868 - [compiler][model] incomplete support for package-info.java when using SearchableEnvironment
 *     							bug 186342 - [compiler][null] Using annotations for null checking
 *******************************************************************************/
package org.eclipse.jdt.internal.compiler;

import org.eclipse.jdt.core.compiler.*;
import org.eclipse.jdt.internal.compiler.env.*;
import org.eclipse.jdt.internal.compiler.impl.*;
import org.eclipse.jdt.internal.compiler.ast.*;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.lookup.*;
import org.eclipse.jdt.internal.compiler.parser.*;
import org.eclipse.jdt.internal.compiler.problem.*;
import org.eclipse.jdt.internal.compiler.util.*;

import java.io.*;
import java.util.*;

@SuppressWarnings("rawtypes")
public class Compiler implements ITypeRequestor, ProblemSeverities {
    public Parser parser;
    public ICompilerRequestor requestor;
    public CompilerOptions options;
    public ProblemReporter problemReporter;
    protected PrintWriter out; // output for messages that are not sent to problemReporter
    public CompilerStats stats;
    public CompilationProgress progress;
    public int remainingIterations = 1;

    // management of unit to be processed
    //public CompilationUnitResult currentCompilationUnitResult;
    public CompilationUnitDeclaration[] unitsToProcess;
    public int totalUnits; // (totalUnits-1) gives the last unit in unitToProcess

    private Map<String, APTProblem[]> aptProblems;

    // name lookup
    public LookupEnvironment lookupEnvironment;

    // ONCE STABILIZED, THESE SHOULD RETURN TO A FINAL FIELD
    public static boolean DEBUG = false;
    public int parseThreshold = -1;

    public AbstractAnnotationProcessorManager annotationProcessorManager;
    public int annotationProcessorStartIndex = 0;
    public ReferenceBinding[] referenceBindings;
    public boolean useSingleThread = true; // by default the compiler will not use worker threads to read/process/write

    // number of initial units parsed at once (-1: none)

    /*
     * Static requestor reserved to listening compilation results in debug mode,
     * so as for example to monitor compiler activity independantly from a particular
     * builder implementation. It is reset at the end of compilation, and should not
     * persist any information after having been reset.
     */
    public static IDebugRequestor DebugRequestor = null;

    /**
     * Answer a new compiler using the given name environment and compiler options.
     * The environment and options will be in effect for the lifetime of the compiler.
     * When the compiler is run, compilation results are sent to the given requestor.
     *
     * @param environment    org.eclipse.jdt.internal.compiler.api.env.INameEnvironment
     *                       Environment used by the compiler in order to resolve type and package
     *                       names. The name environment implements the actual connection of the compiler
     *                       to the outside world (e.g. in batch mode the name environment is performing
     *                       pure file accesses, reuse previous build state or connection to repositories).
     *                       Note: the name environment is responsible for implementing the actual classpath
     *                       rules.
     * @param policy         org.eclipse.jdt.internal.compiler.api.problem.IErrorHandlingPolicy
     *                       Configurable part for problem handling, allowing the compiler client to
     *                       specify the rules for handling problems (stop on first error or accumulate
     *                       them all) and at the same time perform some actions such as opening a dialog
     *                       in UI when compiling interactively.
     * @param settings       java.util.Map
     *                       The settings that control the compiler behavior.
     * @param requestor      org.eclipse.jdt.internal.compiler.api.ICompilerRequestor
     *                       Component which will receive and persist all compilation results and is intended
     *                       to consume them as they are produced. Typically, in a batch compiler, it is
     *                       responsible for writing out the actual .class files to the file system.
     * @param problemFactory org.eclipse.jdt.internal.compiler.api.problem.IProblemFactory
     *                       Factory used inside the compiler to create problem descriptors. It allows the
     *                       compiler client to supply its own representation of compilation problems in
     *                       order to avoid object conversions. Note that the factory is not supposed
     *                       to accumulate the created problems, the compiler will gather them all and hand
     *                       them back as part of the compilation unit result.
     * @see org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies
     * @see org.eclipse.jdt.internal.compiler.CompilationResult
     * @deprecated this constructor is kept to preserve 3.1 and 3.2M4 compatibility
     */
    public Compiler(
            INameEnvironment environment,
            IErrorHandlingPolicy policy,
            Map<String, String> settings,
            final ICompilerRequestor requestor,
            IProblemFactory problemFactory) {
        this(environment, policy, new CompilerOptions(settings), requestor, problemFactory, null /* printwriter */, null /* progress */);
    }

    /**
     * Answer a new compiler using the given name environment and compiler options.
     * The environment and options will be in effect for the lifetime of the compiler.
     * When the compiler is run, compilation results are sent to the given requestor.
     *
     * @param environment                        org.eclipse.jdt.internal.compiler.api.env.INameEnvironment
     *                                           Environment used by the compiler in order to resolve type and package
     *                                           names. The name environment implements the actual connection of the compiler
     *                                           to the outside world (e.g. in batch mode the name environment is performing
     *                                           pure file accesses, reuse previous build state or connection to repositories).
     *                                           Note: the name environment is responsible for implementing the actual classpath
     *                                           rules.
     * @param policy                             org.eclipse.jdt.internal.compiler.api.problem.IErrorHandlingPolicy
     *                                           Configurable part for problem handling, allowing the compiler client to
     *                                           specify the rules for handling problems (stop on first error or accumulate
     *                                           them all) and at the same time perform some actions such as opening a dialog
     *                                           in UI when compiling interactively.
     * @param settings                           java.util.Map
     *                                           The settings that control the compiler behavior.
     * @param requestor                          org.eclipse.jdt.internal.compiler.api.ICompilerRequestor
     *                                           Component which will receive and persist all compilation results and is intended
     *                                           to consume them as they are produced. Typically, in a batch compiler, it is
     *                                           responsible for writing out the actual .class files to the file system.
     * @param problemFactory                     org.eclipse.jdt.internal.compiler.api.problem.IProblemFactory
     *                                           Factory used inside the compiler to create problem descriptors. It allows the
     *                                           compiler client to supply its own representation of compilation problems in
     *                                           order to avoid object conversions. Note that the factory is not supposed
     *                                           to accumulate the created problems, the compiler will gather them all and hand
     *                                           them back as part of the compilation unit result.
     * @param parseLiteralExpressionsAsConstants <code>boolean</code>
     *                                           This parameter is used to optimize the literals or leave them as they are in the source.
     *                                           If you put true, "Hello" + " world" will be converted to "Hello world".
     * @see org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies
     * @see org.eclipse.jdt.internal.compiler.CompilationResult
     * @deprecated this constructor is kept to preserve 3.1 and 3.2M4 compatibility
     */
    public Compiler(
            INameEnvironment environment,
            IErrorHandlingPolicy policy,
            Map settings,
            final ICompilerRequestor requestor,
            IProblemFactory problemFactory,
            boolean parseLiteralExpressionsAsConstants) {
        this(environment, policy, new CompilerOptions(settings, parseLiteralExpressionsAsConstants), requestor, problemFactory, null /* printwriter */, null /* progress */);
    }

    /**
     * Answer a new compiler using the given name environment and compiler options.
     * The environment and options will be in effect for the lifetime of the compiler.
     * When the compiler is run, compilation results are sent to the given requestor.
     *
     * @param environment    org.eclipse.jdt.internal.compiler.api.env.INameEnvironment
     *                       Environment used by the compiler in order to resolve type and package
     *                       names. The name environment implements the actual connection of the compiler
     *                       to the outside world (e.g. in batch mode the name environment is performing
     *                       pure file accesses, reuse previous build state or connection to repositories).
     *                       Note: the name environment is responsible for implementing the actual classpath
     *                       rules.
     * @param policy         org.eclipse.jdt.internal.compiler.api.problem.IErrorHandlingPolicy
     *                       Configurable part for problem handling, allowing the compiler client to
     *                       specify the rules for handling problems (stop on first error or accumulate
     *                       them all) and at the same time perform some actions such as opening a dialog
     *                       in UI when compiling interactively.
     * @param options        org.eclipse.jdt.internal.compiler.impl.CompilerOptions
     *                       The options that control the compiler behavior.
     * @param requestor      org.eclipse.jdt.internal.compiler.api.ICompilerRequestor
     *                       Component which will receive and persist all compilation results and is intended
     *                       to consume them as they are produced. Typically, in a batch compiler, it is
     *                       responsible for writing out the actual .class files to the file system.
     * @param problemFactory org.eclipse.jdt.internal.compiler.api.problem.IProblemFactory
     *                       Factory used inside the compiler to create problem descriptors. It allows the
     *                       compiler client to supply its own representation of compilation problems in
     *                       order to avoid object conversions. Note that the factory is not supposed
     *                       to accumulate the created problems, the compiler will gather them all and hand
     *                       them back as part of the compilation unit result.
     * @see org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies
     * @see org.eclipse.jdt.internal.compiler.CompilationResult
     */
    public Compiler(
            INameEnvironment environment,
            IErrorHandlingPolicy policy,
            CompilerOptions options,
            final ICompilerRequestor requestor,
            IProblemFactory problemFactory) {
        this(environment, policy, options, requestor, problemFactory, null /* printwriter */, null /* progress */);
    }

    /**
     * Answer a new compiler using the given name environment and compiler options.
     * The environment and options will be in effect for the lifetime of the compiler.
     * When the compiler is run, compilation results are sent to the given requestor.
     *
     * @param environment    org.eclipse.jdt.internal.compiler.api.env.INameEnvironment
     *                       Environment used by the compiler in order to resolve type and package
     *                       names. The name environment implements the actual connection of the compiler
     *                       to the outside world (e.g. in batch mode the name environment is performing
     *                       pure file accesses, reuse previous build state or connection to repositories).
     *                       Note: the name environment is responsible for implementing the actual classpath
     *                       rules.
     * @param policy         org.eclipse.jdt.internal.compiler.api.problem.IErrorHandlingPolicy
     *                       Configurable part for problem handling, allowing the compiler client to
     *                       specify the rules for handling problems (stop on first error or accumulate
     *                       them all) and at the same time perform some actions such as opening a dialog
     *                       in UI when compiling interactively.
     * @param options        org.eclipse.jdt.internal.compiler.impl.CompilerOptions
     *                       The options that control the compiler behavior.
     * @param requestor      org.eclipse.jdt.internal.compiler.api.ICompilerRequestor
     *                       Component which will receive and persist all compilation results and is intended
     *                       to consume them as they are produced. Typically, in a batch compiler, it is
     *                       responsible for writing out the actual .class files to the file system.
     * @param problemFactory org.eclipse.jdt.internal.compiler.api.problem.IProblemFactory
     *                       Factory used inside the compiler to create problem descriptors. It allows the
     *                       compiler client to supply its own representation of compilation problems in
     *                       order to avoid object conversions. Note that the factory is not supposed
     *                       to accumulate the created problems, the compiler will gather them all and hand
     *                       them back as part of the compilation unit result.
     * @see org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies
     * @see org.eclipse.jdt.internal.compiler.CompilationResult
     * @deprecated
     */
    public Compiler(
            INameEnvironment environment,
            IErrorHandlingPolicy policy,
            CompilerOptions options,
            final ICompilerRequestor requestor,
            IProblemFactory problemFactory,
            PrintWriter out) {
        this(environment, policy, options, requestor, problemFactory, out, null /* progress */);
    }

    public Compiler(
            INameEnvironment environment,
            IErrorHandlingPolicy policy,
            CompilerOptions options,
            final ICompilerRequestor requestor,
            IProblemFactory problemFactory,
            PrintWriter out,
            CompilationProgress progress) {

        this.options = options;
        this.progress = progress;

        // wrap requestor in DebugRequestor if one is specified
        if (DebugRequestor == null) {
            this.requestor = requestor;
        } else {
            this.requestor = new ICompilerRequestor() {
                @Override
                public void acceptResult(CompilationResult result) {
                    if (DebugRequestor.isActive()) {
                        DebugRequestor.acceptDebugResult(result);
                    }
                    requestor.acceptResult(result);
                }
            };
        }
        this.problemReporter = new ProblemReporter(policy, this.options, problemFactory);
        this.lookupEnvironment = new LookupEnvironment(this, this.options, this.problemReporter, environment);

        /*
         * START WINDUP ADDITION
         *
         *   This line is added to prevent JDT from aborting in cases
         *   where SOME type information is missing. This makes the parser much
         *   more forgiving of minor errors, improving performance and reliability.
         *   Addition to https://github.com/eclipse/eclipse.jdt.core/blob/master/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/Compiler.java
         */
        this.lookupEnvironment.mayTolerateMissingType = true;
        /*
         * END WINDUP ADDITION
         */

        this.out = out == null ? new PrintWriter(System.out, true) : out;
        this.stats = new CompilerStats();
        initializeParser();
    }

    /**
     * Add an additional binary type
     */
    @Override
    public void accept(IBinaryType binaryType, PackageBinding packageBinding, AccessRestriction accessRestriction) {
        if (this.options.verbose) {
            this.out.println(
                    Messages.bind(Messages.compilation_loadBinary, new String(binaryType.getName())));
//			new Exception("TRACE BINARY").printStackTrace(System.out);
//		    System.out.println();
        }
        LookupEnvironment env = packageBinding.environment;
        env.createBinaryTypeFrom(binaryType, packageBinding, accessRestriction);
    }

    /**
     * Add an additional compilation unit into the loop
     * ->  build compilation unit declarations, their bindings and record their results.
     */
    @Override
    public void accept(ICompilationUnit sourceUnit, AccessRestriction accessRestriction) {
        // Switch the current policy and compilation result for this unit to the requested one.
        CompilationResult unitResult =
                new CompilationResult(sourceUnit, this.totalUnits, this.totalUnits, this.options.maxProblemsPerUnit);
        unitResult.checkSecondaryTypes = true;
        try {
            if (this.options.verbose) {
                String count = String.valueOf(this.totalUnits + 1);
                this.out.println(
                        Messages.bind(Messages.compilation_request,
                                new String[]{
                                        count,
                                        count,
                                        new String(sourceUnit.getFileName())
                                }));
            }
            // diet parsing for large collection of unit
            CompilationUnitDeclaration parsedUnit;
            if (this.totalUnits < this.parseThreshold) {
                parsedUnit = this.parser.parse(sourceUnit, unitResult);
            } else {
                parsedUnit = this.parser.dietParse(sourceUnit, unitResult);
            }
            // initial type binding creation
            this.lookupEnvironment.buildTypeBindings(parsedUnit, accessRestriction);
            addCompilationUnit(sourceUnit, parsedUnit);

            // binding resolution
            this.lookupEnvironment.completeTypeBindings(parsedUnit);
        } catch (AbortCompilationUnit e) {
            // at this point, currentCompilationUnitResult may not be sourceUnit, but some other
            // one requested further along to resolve sourceUnit.
            if (unitResult.compilationUnit == sourceUnit) { // only report once
                this.requestor.acceptResult(unitResult.tagAsAccepted());
            } else {
                throw e; // want to abort enclosing request to compile
            }
        }
    }

    /**
     * Add additional source types
     */
    @Override
    public void accept(ISourceType[] sourceTypes, PackageBinding packageBinding, AccessRestriction accessRestriction) {
        this.problemReporter.abortDueToInternalError(
                Messages.bind(Messages.abort_againstSourceModel, new String[]{String.valueOf(sourceTypes[0].getName()), String.valueOf(sourceTypes[0].getFileName())}));
    }

    protected synchronized void addCompilationUnit(
            ICompilationUnit sourceUnit,
            CompilationUnitDeclaration parsedUnit) {

        if (this.unitsToProcess == null)
            return; // not collecting units

        // append the unit to the list of ones to process later on
        int size = this.unitsToProcess.length;
        if (this.totalUnits == size)
            // when growing reposition units starting at position 0
            System.arraycopy(
                    this.unitsToProcess,
                    0,
                    (this.unitsToProcess = new CompilationUnitDeclaration[size * 2]),
                    0,
                    this.totalUnits);
        this.unitsToProcess[this.totalUnits++] = parsedUnit;
    }

    /**
     * Add the initial set of compilation units into the loop
     * ->  build compilation unit declarations, their bindings and record their results.
     */
    protected void beginToCompile(ICompilationUnit[] sourceUnits) {
        int maxUnits = sourceUnits.length;
        this.totalUnits = 0;
        this.unitsToProcess = new CompilationUnitDeclaration[maxUnits];

        internalBeginToCompile(sourceUnits, maxUnits);
    }

    /**
     * Checks whether the compilation has been canceled and reports the given progress to the compiler progress.
     */
    protected void reportProgress(String taskDecription) {
        if (this.progress != null) {
            if (this.progress.isCanceled()) {
                // Only AbortCompilation can stop the compiler cleanly.
                // We check cancellation again following the call to compile.
                throw new AbortCompilation(true, null);
            }
            this.progress.setTaskName(taskDecription);
        }
    }

    /**
     * Checks whether the compilation has been canceled and reports the given work increment to the compiler progress.
     */
    protected void reportWorked(int workIncrement, int currentUnitIndex) {
        if (this.progress != null) {
            if (this.progress.isCanceled()) {
                // Only AbortCompilation can stop the compiler cleanly.
                // We check cancellation again following the call to compile.
                throw new AbortCompilation(true, null);
            }
            this.progress.worked(workIncrement, (this.totalUnits * this.remainingIterations) - currentUnitIndex - 1);
        }
    }

    public void compile(ICompilationUnit[] sourceUnits) {
        compile(sourceUnits, false);
    }

    /**
     * General API
     * -> compile each of supplied files
     * -> recompile any required types for which we have an incomplete principle structure
     */
    private void compile(ICompilationUnit[] sourceUnits, boolean lastRound) {
        this.stats.startTime = System.currentTimeMillis();
        try {
            // build and record parsed units
            reportProgress(Messages.compilation_beginningToCompile);

            if (this.options.complianceLevel >= ClassFileConstants.JDK9) {
                // in Java 9 the compiler must never ask the oracle for a module that is contained in the input units:
                sortModuleDeclarationsFirst(sourceUnits);
            }
            if (this.annotationProcessorManager == null) {
                beginToCompile(sourceUnits);
            } else {
                ICompilationUnit[] originalUnits = sourceUnits.clone(); // remember source units in case a source type collision occurs
                try {
                    beginToCompile(sourceUnits);
                    if (!lastRound) {
                        processAnnotations();
                    }
                    if (!this.options.generateClassFiles) {
                        // -proc:only was set on the command line
                        return;
                    }
                } catch (SourceTypeCollisionException e) {
                    backupAptProblems();
                    reset();
                    // a generated type was referenced before it was created
                    // the compiler either created a MissingType or found a BinaryType for it
                    // so add the processor's generated files & start over,
                    // but remember to only pass the generated files to the annotation processor
                    int originalLength = originalUnits.length;
                    int newProcessedLength = e.newAnnotationProcessorUnits.length;
                    ICompilationUnit[] combinedUnits = new ICompilationUnit[originalLength + newProcessedLength];
                    System.arraycopy(originalUnits, 0, combinedUnits, 0, originalLength);
                    System.arraycopy(e.newAnnotationProcessorUnits, 0, combinedUnits, originalLength, newProcessedLength);
                    this.annotationProcessorStartIndex = originalLength;
                    compile(combinedUnits, e.isLastRound);
                    return;
                }
            }
            // Restore the problems before the results are processed and cleaned up.
            restoreAptProblems();
            processCompiledUnits(0, lastRound);
        } catch (AbortCompilation e) {
            this.handleInternalException(e, null);
        }
        if (this.options.verbose) {
            if (this.totalUnits > 1) {
                this.out.println(
                        Messages.bind(Messages.compilation_units, String.valueOf(this.totalUnits)));
            } else {
                this.out.println(
                        Messages.bind(Messages.compilation_unit, String.valueOf(this.totalUnits)));
            }
        }
    }

    private void sortModuleDeclarationsFirst(ICompilationUnit[] sourceUnits) {
        Arrays.sort(sourceUnits, (u1, u2) -> {
            char[] fn1 = u1.getFileName();
            char[] fn2 = u2.getFileName();
            boolean isMod1 = CharOperation.endsWith(fn1, TypeConstants.MODULE_INFO_FILE_NAME) || CharOperation.endsWith(fn1, TypeConstants.MODULE_INFO_CLASS_NAME);
            boolean isMod2 = CharOperation.endsWith(fn2, TypeConstants.MODULE_INFO_FILE_NAME) || CharOperation.endsWith(fn2, TypeConstants.MODULE_INFO_CLASS_NAME);
            if (isMod1 == isMod2)
                return 0;
            return isMod1 ? -1 : 1;
        });
    }

    class APTProblem {
        CategorizedProblem problem;
        ReferenceContext context;

        APTProblem(CategorizedProblem problem, ReferenceContext context) {
            this.problem = problem;
            this.context = context;
        }
    }

    protected void backupAptProblems() {
        if (this.unitsToProcess == null) return;
        for (int i = 0; i < this.totalUnits; i++) {
            CompilationUnitDeclaration unitDecl = this.unitsToProcess[i];
            CompilationResult result = unitDecl.compilationResult;
            if (result != null && result.hasErrors()) {
                CategorizedProblem[] errors = result.getErrors();
                for (CategorizedProblem problem : errors) {
                    if (problem.getCategoryID() == CategorizedProblem.CAT_UNSPECIFIED) {
                        if (this.aptProblems == null) {
                            this.aptProblems = new HashMap<>();
                        }
                        APTProblem[] problems = this.aptProblems.get(new String(unitDecl.getFileName()));
                        if (problems == null) {
                            this.aptProblems.put(
                                    new String(unitDecl.getFileName()),
                                    new APTProblem[]{new APTProblem(problem, result.getContext(problem))});
                        } else {
                            APTProblem[] temp = new APTProblem[problems.length + 1];
                            System.arraycopy(problems, 0, temp, 0, problems.length);
                            temp[problems.length] = new APTProblem(problem, result.getContext(problem));
                            this.aptProblems.put(new String(unitDecl.getFileName()), temp);
                        }
                    }
                }
            }
        }
    }

    protected void restoreAptProblems() {
        if (this.unitsToProcess != null && this.aptProblems != null) {
            for (int i = 0; i < this.totalUnits; i++) {
                CompilationUnitDeclaration unitDecl = this.unitsToProcess[i];
                APTProblem[] problems = this.aptProblems.get(new String(unitDecl.getFileName()));
                if (problems != null) {
                    for (APTProblem problem : problems) {
                        unitDecl.compilationResult.record(problem.problem, problem.context);
                    }
                }
            }
        }
        this.aptProblems = null; // No need for this.
    }

    protected void processCompiledUnits(int startingIndex, boolean lastRound) throws java.lang.Error {
        CompilationUnitDeclaration unit = null;
        ProcessTaskManager processingTask = null;
        try {
            if (this.useSingleThread) {
                // process all units (some more could be injected in the loop by the lookup environment)
                for (int i = startingIndex; i < this.totalUnits; i++) {
                    unit = this.unitsToProcess[i];
                    if (unit.compilationResult != null && unit.compilationResult.hasBeenAccepted)
                        continue;
                    reportProgress(Messages.bind(Messages.compilation_processing, new String(unit.getFileName())));
                    try {
                        if (this.options.verbose)
                            this.out.println(
                                    Messages.bind(Messages.compilation_process,
                                            new String[]{
                                                    String.valueOf(i + 1),
                                                    String.valueOf(this.totalUnits),
                                                    new String(this.unitsToProcess[i].getFileName())
                                            }));
                        process(unit, i);
                    } finally {
                        // cleanup compilation unit result, but only if not annotation processed.
                        if (this.annotationProcessorManager == null || shouldCleanup(i))
                            unit.cleanUp();
                    }
                    if (this.annotationProcessorManager == null) {
                        this.unitsToProcess[i] = null; // release reference to processed unit declaration
                    }

                    reportWorked(1, i);
                    this.stats.lineCount += unit.compilationResult.lineSeparatorPositions.length;
                    long acceptStart = System.currentTimeMillis();
                    this.requestor.acceptResult(unit.compilationResult.tagAsAccepted());
                    this.stats.generateTime += System.currentTimeMillis() - acceptStart; // record accept time as part of generation
                    if (this.options.verbose)
                        this.out.println(
                                Messages.bind(Messages.compilation_done,
                                        new String[]{
                                                String.valueOf(i + 1),
                                                String.valueOf(this.totalUnits),
                                                new String(unit.getFileName())
                                        }));
                }
            } else {
                processingTask = new ProcessTaskManager(this, startingIndex);
                int acceptedCount = 0;
                // process all units (some more could be injected in the loop by the lookup environment)
                // the processTask can continue to process units until its fixed sized cache is full then it must wait
                // for this this thread to accept the units as they appear (it only waits if no units are available)
                while (true) {
                    try {
                        unit = processingTask.removeNextUnit(); // waits if no units are in the processed queue
                    } catch (Error e) {
                        unit = processingTask.unitToProcess;
                        throw e;
                    } catch (RuntimeException e) {
                        unit = processingTask.unitToProcess;
                        throw e;
                    }
                    if (unit == null) break;
                    reportWorked(1, acceptedCount++);
                    this.stats.lineCount += unit.compilationResult.lineSeparatorPositions.length;
                    this.requestor.acceptResult(unit.compilationResult.tagAsAccepted());
                    if (this.options.verbose)
                        this.out.println(
                                Messages.bind(Messages.compilation_done,
                                        new String[]{
                                                String.valueOf(acceptedCount),
                                                String.valueOf(this.totalUnits),
                                                new String(unit.getFileName())
                                        }));
                }
            }
            if (!lastRound) {
                if (this.annotationProcessorManager != null && this.totalUnits > this.annotationProcessorStartIndex) {
                    int backup = this.annotationProcessorStartIndex;
                    int prevUnits = this.totalUnits;
                    processAnnotations();
                    // Clean up the units that were left out previously for annotation processing.
                    for (int i = backup; i < prevUnits; i++) {
                        this.unitsToProcess[i].cleanUp();
                    }
                    processCompiledUnits(backup, lastRound);
                }
            }
        } catch (AbortCompilation e) {
            this.handleInternalException(e, unit);
        } catch (Error e) {
            this.handleInternalException(e, unit, null);
            throw e; // rethrow
        } catch (RuntimeException e) {
            this.handleInternalException(e, unit, null);
            throw e; // rethrow
        } finally {
            if (processingTask != null) {
                processingTask.shutdown();
                processingTask = null;
            }
            reset();
            this.annotationProcessorStartIndex = 0;
            this.stats.endTime = System.currentTimeMillis();
        }
    }

    public synchronized CompilationUnitDeclaration getUnitToProcess(int next) {
        if (next < this.totalUnits) {
            CompilationUnitDeclaration unit = this.unitsToProcess[next];
            if (this.annotationProcessorManager == null || next < this.annotationProcessorStartIndex) {
                this.unitsToProcess[next] = null; // release reference to processed unit declaration
            }
            return unit;
        }
        return null;
    }

    /*
     * Returns whether the compilation unit at the given index should be
     * cleaned up after processing. This basically means whether or not
     * the unit is still required for annotation processing.
     */
    public boolean shouldCleanup(int index) {
        return index < this.annotationProcessorStartIndex;
    }

    public void setBinaryTypes(ReferenceBinding[] binaryTypes) {
        this.referenceBindings = binaryTypes;
    }

    /*
     * Compiler crash recovery in case of unexpected runtime exceptions
     */
    protected void handleInternalException(
            Throwable internalException,
            CompilationUnitDeclaration unit,
            CompilationResult result) {

        if (result == null && unit != null) {
            result = unit.compilationResult; // current unit being processed ?
        }
        // Lookup environment may be in middle of connecting types
        if (result == null && this.lookupEnvironment.unitBeingCompleted != null) {
            result = this.lookupEnvironment.unitBeingCompleted.compilationResult;
        }
        if (result == null) {
            synchronized (this) {
                if (this.unitsToProcess != null && this.totalUnits > 0)
                    result = this.unitsToProcess[this.totalUnits - 1].compilationResult;
            }
        }
        // last unit in beginToCompile ?

        boolean needToPrint = true;
        if (result != null) {
            /* create and record a compilation problem */
            // only keep leading portion of the trace
            String[] pbArguments = new String[]{
                    Messages.bind(Messages.compilation_internalError, Util.getExceptionSummary(internalException)),
            };

            result
                    .record(
                            this.problemReporter
                                    .createProblem(
                                            result.getFileName(),
                                            IProblem.Unclassified,
                                            pbArguments,
                                            pbArguments,
                                            Error, // severity
                                            0, // source start
                                            0, // source end
                                            0, // line number
                                            0),// column number
                            unit, true);

            /* hand back the compilation result */
            if (!result.hasBeenAccepted) {
                this.requestor.acceptResult(result.tagAsAccepted());
                needToPrint = false;
            }
        }
        if (needToPrint) {
            /* dump a stack trace to the console */
            internalException.printStackTrace();
        }
    }

    /*
     * Compiler recovery in case of internal AbortCompilation event
     */
    protected void handleInternalException(
            AbortCompilation abortException,
            CompilationUnitDeclaration unit) {

        /* special treatment for SilentAbort: silently cancelling the compilation process */
        if (abortException.isSilent) {
            if (abortException.silentException == null) {
                return;
            }
            throw abortException.silentException;
        }

        /* uncomment following line to see where the abort came from */
        // abortException.printStackTrace();

        // Exception may tell which compilation result it is related, and which problem caused it
        CompilationResult result = abortException.compilationResult;
        if (result == null && unit != null) {
            result = unit.compilationResult; // current unit being processed ?
        }
        // Lookup environment may be in middle of connecting types
        if (result == null && this.lookupEnvironment.unitBeingCompleted != null) {
            result = this.lookupEnvironment.unitBeingCompleted.compilationResult;
        }
        if (result == null) {
            synchronized (this) {
                if (this.unitsToProcess != null && this.totalUnits > 0)
                    result = this.unitsToProcess[this.totalUnits - 1].compilationResult;
            }
        }
        // last unit in beginToCompile ?
        if (result != null && !result.hasBeenAccepted) {
            /* distant problem which could not be reported back there? */
            if (abortException.problem != null) {
                recordDistantProblem:
                {
                    CategorizedProblem distantProblem = abortException.problem;
                    CategorizedProblem[] knownProblems = result.problems;
                    for (int i = 0; i < result.problemCount; i++) {
                        if (knownProblems[i] == distantProblem) { // already recorded
                            break recordDistantProblem;
                        }
                    }
                    if (distantProblem instanceof DefaultProblem) { // fixup filename TODO (philippe) should improve API to make this official
                        ((DefaultProblem) distantProblem).setOriginatingFileName(result.getFileName());
                    }
                    result.record(distantProblem, unit, true);
                }
            } else {
                /* distant internal exception which could not be reported back there */
                if (abortException.exception != null) {
                    this.handleInternalException(abortException.exception, null, result);
                    return;
                }
            }
            /* hand back the compilation result */
            if (!result.hasBeenAccepted) {
                this.requestor.acceptResult(result.tagAsAccepted());
            }
        } else {
            abortException.printStackTrace();
        }
    }

    public void initializeParser() {

        this.parser = new Parser(this.problemReporter, this.options.parseLiteralExpressionsAsConstants);
    }

    /**
     * Add the initial set of compilation units into the loop
     * ->  build compilation unit declarations, their bindings and record their results.
     */
    protected void internalBeginToCompile(ICompilationUnit[] sourceUnits, int maxUnits) {
        if (!this.useSingleThread && maxUnits >= ReadManager.THRESHOLD)
            this.parser.readManager = new ReadManager(sourceUnits, maxUnits);

        // Switch the current policy and compilation result for this unit to the requested one.
        for (int i = 0; i < maxUnits; i++) {
            CompilationResult unitResult = null;
            try {
                if (this.options.verbose) {
                    this.out.println(
                            Messages.bind(Messages.compilation_request,
                                    new String[]{
                                            String.valueOf(i + 1),
                                            String.valueOf(maxUnits),
                                            new String(sourceUnits[i].getFileName())
                                    }));
                }
                // diet parsing for large collection of units
                CompilationUnitDeclaration parsedUnit;
                unitResult = new CompilationResult(sourceUnits[i], i, maxUnits, this.options.maxProblemsPerUnit);
                long parseStart = System.currentTimeMillis();
                if (this.totalUnits < this.parseThreshold) {
                    parsedUnit = this.parser.parse(sourceUnits[i], unitResult);
                } else {
                    parsedUnit = this.parser.dietParse(sourceUnits[i], unitResult);
                }
                long resolveStart = System.currentTimeMillis();
                this.stats.parseTime += resolveStart - parseStart;
                // initial type binding creation
                this.lookupEnvironment.buildTypeBindings(parsedUnit, null /*no access restriction*/);
                this.stats.resolveTime += System.currentTimeMillis() - resolveStart;
                addCompilationUnit(sourceUnits[i], parsedUnit);
                ImportReference currentPackage = parsedUnit.currentPackage;
                if (currentPackage != null) {
                    unitResult.recordPackageName(currentPackage.tokens);
                }
                //} catch (AbortCompilationUnit e) {
                //	requestor.acceptResult(unitResult.tagAsAccepted());
            } catch (AbortCompilation a) {
                // best effort to find a way for reporting this problem:
                if (a.compilationResult == null)
                    a.compilationResult = unitResult;
                throw a;
            } finally {
                sourceUnits[i] = null; // no longer hold onto the unit
            }
        }
        if (this.parser.readManager != null) {
            this.parser.readManager.shutdown();
            this.parser.readManager = null;
        }
        // binding resolution
        this.lookupEnvironment.completeTypeBindings();
    }

    /**
     * Process a compilation unit already parsed and build.
     */
    public void process(CompilationUnitDeclaration unit, int i) {
        this.lookupEnvironment.unitBeingCompleted = unit;
        long parseStart = System.currentTimeMillis();

        this.parser.getMethodBodies(unit);

        long resolveStart = System.currentTimeMillis();
        this.stats.parseTime += resolveStart - parseStart;

        // fault in fields & methods
        if (unit.scope != null)
            unit.scope.faultInTypes();

        // verify inherited methods
        if (unit.scope != null)
            unit.scope.verifyMethods(this.lookupEnvironment.methodVerifier());

        // type checking
        unit.resolve();

        long analyzeStart = System.currentTimeMillis();
        this.stats.resolveTime += analyzeStart - resolveStart;

        //No need of analysis or generation of code if statements are not required
        if (!this.options.ignoreMethodBodies) unit.analyseCode(); // flow analysis

        long generateStart = System.currentTimeMillis();
        this.stats.analyzeTime += generateStart - analyzeStart;

        if (!this.options.ignoreMethodBodies) unit.generateCode(); // code generation

        // reference info
        if (this.options.produceReferenceInfo && unit.scope != null)
            unit.scope.storeDependencyInfo();

        // finalize problems (suppressWarnings)
        unit.finalizeProblems();

        this.stats.generateTime += System.currentTimeMillis() - generateStart;

        // refresh the total number of units known at this stage
        unit.compilationResult.totalUnitsKnown = this.totalUnits;

        this.lookupEnvironment.unitBeingCompleted = null;
    }

    protected void processAnnotations() {
        int newUnitSize = 0;
        int newClassFilesSize = 0;
        int bottom = this.annotationProcessorStartIndex;
        int top = this.totalUnits;
        ReferenceBinding[] binaryTypeBindingsTemp = this.referenceBindings;
        if (top == 0 && binaryTypeBindingsTemp == null) return;
        this.referenceBindings = null;
        do {
            // extract units to process
            int length = top - bottom;
            CompilationUnitDeclaration[] currentUnits = new CompilationUnitDeclaration[length];
            int index = 0;
            for (int i = bottom; i < top; i++) {
                CompilationUnitDeclaration currentUnit = this.unitsToProcess[i];
                currentUnits[index++] = currentUnit;
            }
            if (index != length) {
                System.arraycopy(currentUnits, 0, (currentUnits = new CompilationUnitDeclaration[index]), 0, index);
            }
            this.annotationProcessorManager.processAnnotations(currentUnits, binaryTypeBindingsTemp, false);
            // https://bugs.eclipse.org/bugs/show_bug.cgi?id=407841
            // It is possible that during the #processAnnotations() call, some units in the next batch would have been
            // brought forward and compiled already. If there are any such, process them for annotations then and there.
            // This would avoid the complications of marking some units as compiled but not-annotation-processed-yet.
            if (top < this.totalUnits) {
                length = this.totalUnits - top; // NOTE: Reuse the same variable, but make sure it's not used after this point
                CompilationUnitDeclaration[] addedUnits = new CompilationUnitDeclaration[length];
                System.arraycopy(this.unitsToProcess, top, addedUnits, 0, length);
                this.annotationProcessorManager.processAnnotations(addedUnits, binaryTypeBindingsTemp, false);
            }
            this.annotationProcessorStartIndex = top;
            ICompilationUnit[] newUnits = this.annotationProcessorManager.getNewUnits();
            newUnitSize = newUnits.length;
            ReferenceBinding[] newClassFiles = this.annotationProcessorManager.getNewClassFiles();
            binaryTypeBindingsTemp = newClassFiles;
            newClassFilesSize = newClassFiles.length;
            if (newUnitSize != 0) {
                ICompilationUnit[] newProcessedUnits = newUnits.clone(); // remember new units in case a source type collision occurs
                try {
                    this.lookupEnvironment.isProcessingAnnotations = true;
                    internalBeginToCompile(newUnits, newUnitSize);
                } catch (SourceTypeCollisionException e) {
                    e.newAnnotationProcessorUnits = newProcessedUnits;
                    throw e;
                } finally {
                    this.lookupEnvironment.isProcessingAnnotations = false;
                    this.annotationProcessorManager.reset();
                }
                bottom = top;
                top = this.totalUnits; // last unit added
                this.annotationProcessorStartIndex = top;
            } else {
                bottom = top;
                this.annotationProcessorManager.reset();
            }
        } while (newUnitSize != 0 || newClassFilesSize != 0);

        this.annotationProcessorManager.processAnnotations(null, null, true);
        // process potential units added in the final round see 329156
        ICompilationUnit[] newUnits = this.annotationProcessorManager.getNewUnits();
        newUnitSize = newUnits.length;
        if (newUnitSize != 0) {
            ICompilationUnit[] newProcessedUnits = newUnits.clone(); // remember new units in case a source type collision occurs
            try {
                this.lookupEnvironment.isProcessingAnnotations = true;
                internalBeginToCompile(newUnits, newUnitSize);
            } catch (SourceTypeCollisionException e) {
                e.isLastRound = true;
                e.newAnnotationProcessorUnits = newProcessedUnits;
                throw e;
            } finally {
                this.lookupEnvironment.isProcessingAnnotations = false;
                this.annotationProcessorManager.reset();
            }
        } else {
            this.annotationProcessorManager.reset();
        }
        // Units added in final round don't get annotation processed
        this.annotationProcessorStartIndex = this.totalUnits;
    }

    public void reset() {
        this.lookupEnvironment.reset();
        this.parser.scanner.source = null;
        this.unitsToProcess = null;
        if (DebugRequestor != null) DebugRequestor.reset();
        this.problemReporter.reset();
    }

    /**
     * Internal API used to resolve a given compilation unit. Can run a subset of the compilation process
     */
    public CompilationUnitDeclaration resolve(
            CompilationUnitDeclaration unit,
            ICompilationUnit sourceUnit,
            boolean verifyMethods,
            boolean analyzeCode,
            boolean generateCode) {

        try {
            if (unit == null) {
                // build and record parsed units
                this.parseThreshold = 0; // will request a full parse
                beginToCompile(new ICompilationUnit[]{sourceUnit});
                // find the right unit from what was injected via accept(ICompilationUnit,..):
                for (int i = 0; i < this.totalUnits; i++) {
                    if (this.unitsToProcess[i] != null
                            && this.unitsToProcess[i].compilationResult.compilationUnit == sourceUnit) {
                        unit = this.unitsToProcess[i];
                        break;
                    }
                }
                if (unit == null)
                    unit = this.unitsToProcess[0]; // fall back to old behavior

            } else {
                // initial type binding creation
                this.lookupEnvironment.buildTypeBindings(unit, null /*no access restriction*/);

                // binding resolution
                this.lookupEnvironment.completeTypeBindings();
            }
            this.lookupEnvironment.unitBeingCompleted = unit;
            this.parser.getMethodBodies(unit);
            if (unit.scope != null) {
                // fault in fields & methods
                unit.scope.faultInTypes();
                if (unit.scope != null && verifyMethods) {
                    // http://dev.eclipse.org/bugs/show_bug.cgi?id=23117
                    // verify inherited methods
                    unit.scope.verifyMethods(this.lookupEnvironment.methodVerifier());
                }
                // type checking
                unit.resolve();

                // flow analysis
                if (analyzeCode) unit.analyseCode();

                // code generation
                if (generateCode) unit.generateCode();

                // finalize problems (suppressWarnings)
                unit.finalizeProblems();
            }
            if (this.unitsToProcess != null)
                this.unitsToProcess[0] = null; // release reference to processed unit declaration
            this.requestor.acceptResult(unit.compilationResult.tagAsAccepted());
            return unit;
        } catch (AbortCompilation e) {
            this.handleInternalException(e, unit);
            return unit == null ? this.unitsToProcess[0] : unit;
        } catch (Error e) {
            this.handleInternalException(e, unit, null);
            throw e; // rethrow
        } catch (RuntimeException e) {
            this.handleInternalException(e, unit, null);
            throw e; // rethrow
        } finally {
            // leave this.lookupEnvironment.unitBeingCompleted set to the unit, until another unit is resolved
            // other calls to dom can cause classpath errors to be detected, resulting in AbortCompilation exceptions

            // No reset is performed there anymore since,
            // within the CodeAssist (or related tools),
            // the compiler may be called *after* a call
            // to this resolve(...) method. And such a call
            // needs to have a compiler with a non-empty
            // environment.
            // this.reset();
        }
    }

    /**
     * Internal API used to resolve a given compilation unit. Can run a subset of the compilation process
     */
    public CompilationUnitDeclaration resolve(
            ICompilationUnit sourceUnit,
            boolean verifyMethods,
            boolean analyzeCode,
            boolean generateCode) {

        return resolve(
                null,
                sourceUnit,
                verifyMethods,
                analyzeCode,
                generateCode);
    }
}
