/******************************************************************************* | |
* Copyright (c) 2000, 2004 IBM Corporation and others. | |
* All rights reserved. This program and the accompanying materials | |
* are made available under the terms of the Common Public License v1.0 | |
* which accompanies this distribution, and is available at | |
* http://www.eclipse.org/legal/cpl-v10.html | |
* | |
* Contributors: | |
* IBM Corporation - initial API and implementation | |
*******************************************************************************/ | |
package org.eclipse.wst.jsdt.internal.compiler; | |
import org.eclipse.wst.jsdt.core.compiler.*; | |
import org.eclipse.wst.jsdt.internal.compiler.env.*; | |
import org.eclipse.wst.jsdt.internal.compiler.impl.*; | |
import org.eclipse.wst.jsdt.internal.compiler.ast.*; | |
import org.eclipse.wst.jsdt.internal.compiler.lookup.*; | |
import org.eclipse.wst.jsdt.internal.compiler.parser.*; | |
import org.eclipse.wst.jsdt.internal.compiler.problem.*; | |
import org.eclipse.wst.jsdt.internal.compiler.util.*; | |
import java.io.*; | |
import java.util.*; | |
public class Compiler implements ITypeRequestor, ProblemSeverities { | |
public Parser parser; | |
public ICompilerRequestor requestor; | |
public CompilerOptions options; | |
public ProblemReporter problemReporter; | |
// management of unit to be processed | |
//public CompilationUnitResult currentCompilationUnitResult; | |
public CompilationUnitDeclaration[] unitsToProcess; | |
public int totalUnits; // (totalUnits-1) gives the last unit in unitToProcess | |
// name lookup | |
public LookupEnvironment lookupEnvironment; | |
// ONCE STABILIZED, THESE SHOULD RETURN TO A FINAL FIELD | |
public static boolean DEBUG = false; | |
public int parseThreshold = -1; | |
// 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.wst.jsdt.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.wst.jsdt.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. | |
* @see org.eclipse.wst.jsdt.internal.compiler.DefaultErrorHandlingPolicies | |
* | |
* @param requestor org.eclipse.wst.jsdt.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. | |
* @see org.eclipse.wst.jsdt.internal.compiler.CompilationResult | |
* | |
* @param problemFactory org.eclipse.wst.jsdt.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. | |
*/ | |
public Compiler( | |
INameEnvironment environment, | |
IErrorHandlingPolicy policy, | |
Map settings, | |
final ICompilerRequestor requestor, | |
IProblemFactory problemFactory) { | |
// create a problem handler given a handling policy | |
this.options = new CompilerOptions(settings); | |
// wrap requestor in DebugRequestor if one is specified | |
if(DebugRequestor == null) { | |
this.requestor = requestor; | |
} else { | |
this.requestor = new ICompilerRequestor(){ | |
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, options, problemReporter, environment); | |
initializeParser(); | |
} | |
/** | |
* 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.wst.jsdt.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.wst.jsdt.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. | |
* @see org.eclipse.wst.jsdt.internal.compiler.DefaultErrorHandlingPolicies | |
* | |
* @param requestor org.eclipse.wst.jsdt.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. | |
* @see org.eclipse.wst.jsdt.internal.compiler.CompilationResult | |
* | |
* @param problemFactory org.eclipse.wst.jsdt.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". | |
*/ | |
public Compiler( | |
INameEnvironment environment, | |
IErrorHandlingPolicy policy, | |
Map settings, | |
final ICompilerRequestor requestor, | |
IProblemFactory problemFactory, | |
boolean parseLiteralExpressionsAsConstants) { | |
// create a problem handler given a handling policy | |
this.options = new CompilerOptions(settings); | |
// wrap requestor in DebugRequestor if one is specified | |
if(DebugRequestor == null) { | |
this.requestor = requestor; | |
} else { | |
this.requestor = new ICompilerRequestor(){ | |
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, options, problemReporter, environment); | |
initializeParser(); | |
} | |
/** | |
* Add an additional binary type | |
*/ | |
public void accept(IBinaryType binaryType, PackageBinding packageBinding) { | |
if (options.verbose) { | |
System.out.println( | |
Util.bind( | |
"compilation.loadBinary" , //$NON-NLS-1$ | |
new String[] { | |
new String(binaryType.getName())})); | |
// new Exception("TRACE BINARY").printStackTrace(System.out); | |
// System.out.println(); | |
} | |
lookupEnvironment.createBinaryTypeFrom(binaryType, packageBinding); | |
} | |
/** | |
* Add an additional compilation unit into the loop | |
* -> build compilation unit declarations, their bindings and record their results. | |
*/ | |
public void accept(ICompilationUnit sourceUnit) { | |
// Switch the current policy and compilation result for this unit to the requested one. | |
CompilationResult unitResult = | |
new CompilationResult(sourceUnit, totalUnits, totalUnits, this.options.maxProblemsPerUnit); | |
try { | |
if (options.verbose) { | |
String count = String.valueOf(totalUnits + 1); | |
System.out.println( | |
Util.bind( | |
"compilation.request" , //$NON-NLS-1$ | |
new String[] { | |
count, | |
count, | |
new String(sourceUnit.getFileName())})); | |
} | |
// diet parsing for large collection of unit | |
CompilationUnitDeclaration parsedUnit; | |
if (totalUnits < parseThreshold) { | |
parsedUnit = parser.parse(sourceUnit, unitResult); | |
} else { | |
parsedUnit = parser.dietParse(sourceUnit, unitResult); | |
} | |
// initial type binding creation | |
lookupEnvironment.buildTypeBindings(parsedUnit); | |
this.addCompilationUnit(sourceUnit, parsedUnit); | |
// binding resolution | |
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 | |
requestor.acceptResult(unitResult.tagAsAccepted()); | |
} else { | |
throw e; // want to abort enclosing request to compile | |
} | |
} | |
} | |
/** | |
* Add additional source types | |
*/ | |
public void accept(ISourceType[] sourceTypes, PackageBinding packageBinding) { | |
problemReporter.abortDueToInternalError( | |
Util.bind( | |
"abort.againstSourceModel" , //$NON-NLS-1$ | |
String.valueOf(sourceTypes[0].getName()), | |
String.valueOf(sourceTypes[0].getFileName()))); | |
} | |
protected void addCompilationUnit( | |
ICompilationUnit sourceUnit, | |
CompilationUnitDeclaration parsedUnit) { | |
// append the unit to the list of ones to process later on | |
int size = unitsToProcess.length; | |
if (totalUnits == size) | |
// when growing reposition units starting at position 0 | |
System.arraycopy( | |
unitsToProcess, | |
0, | |
(unitsToProcess = new CompilationUnitDeclaration[size * 2]), | |
0, | |
totalUnits); | |
unitsToProcess[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; | |
totalUnits = 0; | |
unitsToProcess = new CompilationUnitDeclaration[maxUnits]; | |
// Switch the current policy and compilation result for this unit to the requested one. | |
for (int i = 0; i < maxUnits; i++) { | |
CompilationUnitDeclaration parsedUnit; | |
CompilationResult unitResult = | |
new CompilationResult(sourceUnits[i], i, maxUnits, this.options.maxProblemsPerUnit); | |
try { | |
if (options.verbose) { | |
System.out.println( | |
Util.bind( | |
"compilation.request" , //$NON-NLS-1$ | |
new String[] { | |
String.valueOf(i + 1), | |
String.valueOf(maxUnits), | |
new String(sourceUnits[i].getFileName())})); | |
} | |
// diet parsing for large collection of units | |
if (totalUnits < parseThreshold) { | |
parsedUnit = parser.parse(sourceUnits[i], unitResult); | |
} else { | |
parsedUnit = parser.dietParse(sourceUnits[i], unitResult); | |
} | |
// initial type binding creation | |
lookupEnvironment.buildTypeBindings(parsedUnit); | |
this.addCompilationUnit(sourceUnits[i], parsedUnit); | |
//} catch (AbortCompilationUnit e) { | |
// requestor.acceptResult(unitResult.tagAsAccepted()); | |
} finally { | |
sourceUnits[i] = null; // no longer hold onto the unit | |
} | |
} | |
// binding resolution | |
lookupEnvironment.completeTypeBindings(); | |
} | |
/** | |
* General API | |
* -> compile each of supplied files | |
* -> recompile any required types for which we have an incomplete principle structure | |
*/ | |
public void compile(ICompilationUnit[] sourceUnits) { | |
CompilationUnitDeclaration unit = null; | |
int i = 0; | |
try { | |
// build and record parsed units | |
beginToCompile(sourceUnits); | |
// process all units (some more could be injected in the loop by the lookup environment) | |
for (; i < totalUnits; i++) { | |
unit = unitsToProcess[i]; | |
try { | |
if (options.verbose) | |
System.out.println( | |
Util.bind( | |
"compilation.process" , //$NON-NLS-1$ | |
new String[] { | |
String.valueOf(i + 1), | |
String.valueOf(totalUnits), | |
new String(unitsToProcess[i].getFileName())})); | |
process(unit, i); | |
} finally { | |
// cleanup compilation unit result | |
unit.cleanUp(); | |
} | |
unitsToProcess[i] = null; // release reference to processed unit declaration | |
requestor.acceptResult(unit.compilationResult.tagAsAccepted()); | |
if (options.verbose) | |
System.out.println(Util.bind("compilation.done", //$NON-NLS-1$ | |
new String[] { | |
String.valueOf(i + 1), | |
String.valueOf(totalUnits), | |
new String(unit.getFileName())})); | |
} | |
} 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 { | |
this.reset(); | |
} | |
if (options.verbose) { | |
if (totalUnits > 1) { | |
System.out.println( | |
Util.bind("compilation.units" , String.valueOf(totalUnits))); //$NON-NLS-1$ | |
} else { | |
System.out.println( | |
Util.bind("compilation.unit" , String.valueOf(totalUnits))); //$NON-NLS-1$ | |
} | |
} | |
} | |
/* | |
* Compiler crash recovery in case of unexpected runtime exceptions | |
*/ | |
protected void handleInternalException( | |
Throwable internalException, | |
CompilationUnitDeclaration unit, | |
CompilationResult result) { | |
/* find a compilation result */ | |
if ((unit != null)) // basing result upon the current unit if available | |
result = unit.compilationResult; // current unit being processed ? | |
if ((result == null) && (unitsToProcess != null) && (totalUnits > 0)) | |
result = unitsToProcess[totalUnits - 1].compilationResult; | |
// last unit in beginToCompile ? | |
boolean needToPrint = true; | |
if (result != null) { | |
/* create and record a compilation problem */ | |
StringWriter stringWriter = new StringWriter(); | |
PrintWriter writer = new PrintWriter(stringWriter); | |
internalException.printStackTrace(writer); | |
StringBuffer buffer = stringWriter.getBuffer(); | |
String[] pbArguments = new String[] { | |
Util.bind("compilation.internalError" ) //$NON-NLS-1$ | |
+ "\n" //$NON-NLS-1$ | |
+ buffer.toString()}; | |
result | |
.record( | |
problemReporter | |
.createProblem( | |
result.getFileName(), | |
IProblem.Unclassified, | |
pbArguments, | |
pbArguments, | |
Error, // severity | |
0, // source start | |
0, // source end | |
0), // line number | |
unit); | |
/* hand back the compilation result */ | |
if (!result.hasBeenAccepted) { | |
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) && lookupEnvironment.unitBeingCompleted != null) { | |
result = lookupEnvironment.unitBeingCompleted.compilationResult; | |
} | |
if ((result == null) && (unitsToProcess != null) && (totalUnits > 0)) | |
result = unitsToProcess[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: { | |
IProblem distantProblem = abortException.problem; | |
IProblem[] 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); | |
} | |
} 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) { | |
requestor.acceptResult(result.tagAsAccepted()); | |
} | |
} else { | |
abortException.printStackTrace(); | |
} | |
} | |
public void initializeParser() { | |
this.parser = new Parser(this.problemReporter, this.options.parseLiteralExpressionsAsConstants); | |
} | |
/** | |
* Process a compilation unit already parsed and build. | |
*/ | |
public void process(CompilationUnitDeclaration unit, int i) { | |
this.parser.getMethodBodies(unit); | |
// fault in fields & methods | |
if (unit.scope != null) | |
unit.scope.faultInTypes(); | |
// verify inherited methods | |
if (unit.scope != null) | |
unit.scope.verifyMethods(lookupEnvironment.methodVerifier()); | |
// type checking | |
unit.resolve(); | |
// flow analysis | |
unit.analyseCode(); | |
// code generation | |
unit.generateCode(); | |
// reference info | |
if (options.produceReferenceInfo && unit.scope != null) | |
unit.scope.storeDependencyInfo(); | |
// refresh the total number of units known at this stage | |
unit.compilationResult.totalUnitsKnown = totalUnits; | |
} | |
public void reset() { | |
lookupEnvironment.reset(); | |
parser.scanner.source = null; | |
unitsToProcess = null; | |
if (DebugRequestor != null) DebugRequestor.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 | |
parseThreshold = 0; // will request a full parse | |
beginToCompile(new ICompilationUnit[] { sourceUnit }); | |
// process all units (some more could be injected in the loop by the lookup environment) | |
unit = unitsToProcess[0]; | |
} else { | |
// initial type binding creation | |
lookupEnvironment.buildTypeBindings(unit); | |
// binding resolution | |
lookupEnvironment.completeTypeBindings(); | |
} | |
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(lookupEnvironment.methodVerifier()); | |
} | |
// type checking | |
unit.resolve(); | |
// flow analysis | |
if (analyzeCode) unit.analyseCode(); | |
// code generation | |
if (generateCode) unit.generateCode(); | |
} | |
if (unitsToProcess != null) unitsToProcess[0] = null; // release reference to processed unit declaration | |
requestor.acceptResult(unit.compilationResult.tagAsAccepted()); | |
return unit; | |
} catch (AbortCompilation e) { | |
this.handleInternalException(e, unit); | |
return unit == null ? 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 { | |
// 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); | |
} | |
} |