e0377d79de59ff31374ccbe2877757acdfedb817
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / internal / compiler / Compiler.java
1 /*******************************************************************************
2  * Copyright (c) 2000, 2001, 2002 International Business Machines Corp. and others.
3  * All rights reserved. This program and the accompanying materials 
4  * are made available under the terms of the Common Public License v0.5 
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/cpl-v05.html
7  * 
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  ******************************************************************************/
11 package net.sourceforge.phpdt.internal.compiler;
12
13 import net.sourceforge.phpdt.core.compiler.*;
14 import net.sourceforge.phpdt.internal.compiler.env.*;
15 import net.sourceforge.phpdt.internal.compiler.impl.*;
16 import net.sourceforge.phpdt.internal.compiler.ast.*;
17 import net.sourceforge.phpdt.internal.compiler.lookup.*;
18 import net.sourceforge.phpdt.internal.compiler.parser.*;
19 import net.sourceforge.phpdt.internal.compiler.problem.*;
20 import net.sourceforge.phpdt.internal.compiler.util.*;
21
22 import java.io.*;
23 import java.util.*;
24
25 public class Compiler implements ITypeRequestor, ProblemSeverities {
26         public Parser parser;
27         ICompilerRequestor requestor;
28         public CompilerOptions options;
29         public ProblemReporter problemReporter;
30
31         // management of unit to be processed
32         //public CompilationUnitResult currentCompilationUnitResult;
33         CompilationUnitDeclaration[] unitsToProcess;
34         int totalUnits; // (totalUnits-1) gives the last unit in unitToProcess
35
36         // name lookup
37         public LookupEnvironment lookupEnvironment;
38
39         // ONCE STABILIZED, THESE SHOULD RETURN TO A FINAL FIELD
40         public static boolean DEBUG = false;
41         public int parseThreshold = -1;
42         // number of initial units parsed at once (-1: none)
43
44         /*
45          * Static requestor reserved to listening compilation results in debug mode,
46          * so as for example to monitor compiler activity independantly from a particular
47          * builder implementation. It is reset at the end of compilation, and should not 
48          * persist any information after having been reset.
49          */
50         public static IDebugRequestor DebugRequestor = null;
51
52         /**
53          * Answer a new compiler using the given name environment and compiler options.
54          * The environment and options will be in effect for the lifetime of the compiler.
55          * When the compiler is run, compilation results are sent to the given requestor.
56          *
57          *  @param environment org.eclipse.jdt.internal.compiler.api.env.INameEnvironment
58          *      Environment used by the compiler in order to resolve type and package
59          *      names. The name environment implements the actual connection of the compiler
60          *      to the outside world (e.g. in batch mode the name environment is performing
61          *      pure file accesses, reuse previous build state or connection to repositories).
62          *      Note: the name environment is responsible for implementing the actual classpath
63          *            rules.
64          *
65          *  @param policy org.eclipse.jdt.internal.compiler.api.problem.IErrorHandlingPolicy
66          *      Configurable part for problem handling, allowing the compiler client to
67          *      specify the rules for handling problems (stop on first error or accumulate
68          *      them all) and at the same time perform some actions such as opening a dialog
69          *      in UI when compiling interactively.
70          *      @see org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies
71          *      
72          *  @param requestor org.eclipse.jdt.internal.compiler.api.ICompilerRequestor
73          *      Component which will receive and persist all compilation results and is intended
74          *      to consume them as they are produced. Typically, in a batch compiler, it is 
75          *      responsible for writing out the actual .class files to the file system.
76          *      @see org.eclipse.jdt.internal.compiler.CompilationResult
77          *
78          *  @param problemFactory org.eclipse.jdt.internal.compiler.api.problem.IProblemFactory
79          *      Factory used inside the compiler to create problem descriptors. It allows the
80          *      compiler client to supply its own representation of compilation problems in
81          *      order to avoid object conversions. Note that the factory is not supposed
82          *      to accumulate the created problems, the compiler will gather them all and hand
83          *      them back as part of the compilation unit result.
84          */
85         public Compiler(
86                 INameEnvironment environment,
87                 IErrorHandlingPolicy policy,
88                 Map settings,
89                 final ICompilerRequestor requestor,
90                 IProblemFactory problemFactory) {
91
92                 // create a problem handler given a handling policy
93                 this.options = new CompilerOptions(settings);
94                 
95                 // wrap requestor in DebugRequestor if one is specified
96                 if(DebugRequestor == null) {
97                         this.requestor = requestor;
98                 } else {
99                         this.requestor = new ICompilerRequestor(){
100                                 public void acceptResult(CompilationResult result){
101                                         if (DebugRequestor.isActive()){
102                                                 DebugRequestor.acceptDebugResult(result);
103                                         }
104                                         requestor.acceptResult(result);
105                                 }
106                         };
107                 }
108                 this.problemReporter =
109                         new ProblemReporter(policy, this.options, problemFactory);
110                 this.lookupEnvironment =
111                         new LookupEnvironment(this, options, problemReporter, environment);
112                 this.parser =
113                         new Parser(
114                                 problemReporter, 
115                                 this.options.parseLiteralExpressionsAsConstants, 
116                                 this.options.assertMode);
117         }
118         
119         /**
120          * Answer a new compiler using the given name environment and compiler options.
121          * The environment and options will be in effect for the lifetime of the compiler.
122          * When the compiler is run, compilation results are sent to the given requestor.
123          *
124          *  @param environment org.eclipse.jdt.internal.compiler.api.env.INameEnvironment
125          *      Environment used by the compiler in order to resolve type and package
126          *      names. The name environment implements the actual connection of the compiler
127          *      to the outside world (e.g. in batch mode the name environment is performing
128          *      pure file accesses, reuse previous build state or connection to repositories).
129          *      Note: the name environment is responsible for implementing the actual classpath
130          *            rules.
131          *
132          *  @param policy org.eclipse.jdt.internal.compiler.api.problem.IErrorHandlingPolicy
133          *      Configurable part for problem handling, allowing the compiler client to
134          *      specify the rules for handling problems (stop on first error or accumulate
135          *      them all) and at the same time perform some actions such as opening a dialog
136          *      in UI when compiling interactively.
137          *      @see org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies
138          *      
139          *  @param requestor org.eclipse.jdt.internal.compiler.api.ICompilerRequestor
140          *      Component which will receive and persist all compilation results and is intended
141          *      to consume them as they are produced. Typically, in a batch compiler, it is 
142          *      responsible for writing out the actual .class files to the file system.
143          *      @see org.eclipse.jdt.internal.compiler.CompilationResult
144          *
145          *  @param problemFactory org.eclipse.jdt.internal.compiler.api.problem.IProblemFactory
146          *      Factory used inside the compiler to create problem descriptors. It allows the
147          *      compiler client to supply its own representation of compilation problems in
148          *      order to avoid object conversions. Note that the factory is not supposed
149          *      to accumulate the created problems, the compiler will gather them all and hand
150          *      them back as part of the compilation unit result.
151          *      @param parseLiteralExpressionsAsConstants <code>boolean</code>
152          *              This parameter is used to optimize the literals or leave them as they are in the source.
153          *              If you put true, "Hello" + " world" will be converted to "Hello world".
154          */
155         public Compiler(
156                 INameEnvironment environment,
157                 IErrorHandlingPolicy policy,
158                 Map settings,
159                 final ICompilerRequestor requestor,
160                 IProblemFactory problemFactory,
161                 boolean parseLiteralExpressionsAsConstants) {
162
163                 // create a problem handler given a handling policy
164                 this.options = new CompilerOptions(settings);
165                 
166                 // wrap requestor in DebugRequestor if one is specified
167                 if(DebugRequestor == null) {
168                         this.requestor = requestor;
169                 } else {
170                         this.requestor = new ICompilerRequestor(){
171                                 public void acceptResult(CompilationResult result){
172                                         if (DebugRequestor.isActive()){
173                                                 DebugRequestor.acceptDebugResult(result);
174                                         }
175                                         requestor.acceptResult(result);
176                                 }
177                         };
178                 }
179                 this.problemReporter =
180                         new ProblemReporter(policy, this.options, problemFactory);
181                 this.lookupEnvironment =
182                         new LookupEnvironment(this, options, problemReporter, environment);
183                 this.parser =
184                         new Parser(
185                                 problemReporter, 
186                                 parseLiteralExpressionsAsConstants, 
187                                 this.options.assertMode);
188         }
189         
190         /**
191          * Add an additional binary type
192          */
193         public void accept(IBinaryType binaryType, PackageBinding packageBinding) {
194                 lookupEnvironment.createBinaryTypeFrom(binaryType, packageBinding);
195         }
196
197         /**
198          * Add an additional compilation unit into the loop
199          *  ->  build compilation unit declarations, their bindings and record their results.
200          */
201         public void accept(ICompilationUnit sourceUnit) {
202                 // Switch the current policy and compilation result for this unit to the requested one.
203                 CompilationResult unitResult =
204                         new CompilationResult(sourceUnit, totalUnits, totalUnits, this.options.maxProblemsPerUnit);
205                 try {
206                         // diet parsing for large collection of unit
207                         CompilationUnitDeclaration parsedUnit;
208                         if (totalUnits < parseThreshold) {
209                                 parsedUnit = parser.parse(sourceUnit, unitResult);
210                         } else {
211                                 parsedUnit = parser.dietParse(sourceUnit, unitResult);
212                         }
213
214                         if (options.verbose) {
215                                 System.out.println(
216                                         Util.bind(
217                                                 "compilation.request" , //$NON-NLS-1$
218                                                 new String[] {
219                                                         String.valueOf(totalUnits + 1),
220                                                         String.valueOf(totalUnits + 1),
221                                                         new String(sourceUnit.getFileName())}));
222                         }
223
224                         // initial type binding creation
225                         lookupEnvironment.buildTypeBindings(parsedUnit);
226                         this.addCompilationUnit(sourceUnit, parsedUnit);
227
228                         // binding resolution
229                         lookupEnvironment.completeTypeBindings(parsedUnit);
230                 } catch (AbortCompilationUnit e) {
231                         // at this point, currentCompilationUnitResult may not be sourceUnit, but some other
232                         // one requested further along to resolve sourceUnit.
233                         if (unitResult.compilationUnit == sourceUnit) { // only report once
234                                 requestor.acceptResult(unitResult.tagAsAccepted());
235                         } else {
236                                 throw e; // want to abort enclosing request to compile
237                         }
238                 }
239         }
240
241         /**
242          * Add additional source types
243          */
244         public void accept(ISourceType[] sourceTypes, PackageBinding packageBinding) {
245                 problemReporter.abortDueToInternalError(
246                         Util.bind(
247                                 "abort.againstSourceModel " , //$NON-NLS-1$
248                                 String.valueOf(sourceTypes[0].getName()),
249                                 String.valueOf(sourceTypes[0].getFileName())));
250         }
251
252         protected void addCompilationUnit(
253                 ICompilationUnit sourceUnit,
254                 CompilationUnitDeclaration parsedUnit) {
255
256                 // append the unit to the list of ones to process later on
257                 int size = unitsToProcess.length;
258                 if (totalUnits == size)
259                         // when growing reposition units starting at position 0
260                         System.arraycopy(
261                                 unitsToProcess,
262                                 0,
263                                 (unitsToProcess = new CompilationUnitDeclaration[size * 2]),
264                                 0,
265                                 totalUnits);
266                 unitsToProcess[totalUnits++] = parsedUnit;
267         }
268
269         /**
270          * Add the initial set of compilation units into the loop
271          *  ->  build compilation unit declarations, their bindings and record their results.
272          */
273         protected void beginToCompile(ICompilationUnit[] sourceUnits) {
274                 int maxUnits = sourceUnits.length;
275                 totalUnits = 0;
276                 unitsToProcess = new CompilationUnitDeclaration[maxUnits];
277
278                 // Switch the current policy and compilation result for this unit to the requested one.
279                 for (int i = 0; i < maxUnits; i++) {
280                         CompilationUnitDeclaration parsedUnit;
281                         CompilationResult unitResult =
282                                 new CompilationResult(sourceUnits[i], i, maxUnits, this.options.maxProblemsPerUnit);
283                         try {
284                                 // diet parsing for large collection of units
285                                 if (totalUnits < parseThreshold) {
286                                         parsedUnit = parser.parse(sourceUnits[i], unitResult);
287                                 } else {
288                                         parsedUnit = parser.dietParse(sourceUnits[i], unitResult);
289                                 }
290                                 if (options.verbose) {
291                                         System.out.println(
292                                                 Util.bind(
293                                                         "compilation.request" , //$NON-NLS-1$
294                                                         new String[] {
295                                                                 String.valueOf(i + 1),
296                                                                 String.valueOf(maxUnits),
297                                                                 new String(sourceUnits[i].getFileName())}));
298                                 }
299                                 // initial type binding creation
300                                 lookupEnvironment.buildTypeBindings(parsedUnit);
301                                 this.addCompilationUnit(sourceUnits[i], parsedUnit);
302                                 //} catch (AbortCompilationUnit e) {
303                                 //      requestor.acceptResult(unitResult.tagAsAccepted());
304                         } finally {
305                                 sourceUnits[i] = null; // no longer hold onto the unit
306                         }
307                 }
308                 // binding resolution
309                 lookupEnvironment.completeTypeBindings();
310         }
311
312         /**
313          * General API
314          * -> compile each of supplied files
315          * -> recompile any required types for which we have an incomplete principle structure
316          */
317         public void compile(ICompilationUnit[] sourceUnits) {
318                 CompilationUnitDeclaration unit = null;
319                 int i = 0;
320                 try {
321                         // build and record parsed units
322
323                         beginToCompile(sourceUnits);
324
325                         // process all units (some more could be injected in the loop by the lookup environment)
326                         for (; i < totalUnits; i++) {
327                                 unit = unitsToProcess[i];
328                                 try {
329                                         if (options.verbose)
330                                                 System.out.println(
331                                                         Util.bind(
332                                                                 "compilation.process" , //$NON-NLS-1$
333                                                                 new String[] {
334                                                                         String.valueOf(i + 1),
335                                                                         String.valueOf(totalUnits),
336                                                                         new String(unitsToProcess[i].getFileName())}));
337                                         process(unit, i);
338                                 } finally {
339                                         // cleanup compilation unit result
340                                         unit.cleanUp();
341                                         if (options.verbose)
342                                                 System.out.println(Util.bind("compilation.done", //$NON-NLS-1$
343                                         new String[] {
344                                                 String.valueOf(i + 1),
345                                                 String.valueOf(totalUnits),
346                                                 new String(unitsToProcess[i].getFileName())}));
347                                 }
348                                 unitsToProcess[i] = null; // release reference to processed unit declaration
349                                 requestor.acceptResult(unit.compilationResult.tagAsAccepted());
350                         }
351                 } catch (AbortCompilation e) {
352                         this.handleInternalException(e, unit);
353                 } catch (Error e) {
354                         this.handleInternalException(e, unit, null);
355                         throw e; // rethrow
356                 } catch (RuntimeException e) {
357                         this.handleInternalException(e, unit, null);
358                         throw e; // rethrow
359                 } finally {
360                         this.reset();
361                 }
362                 if (options.verbose) {
363                         if (totalUnits > 1) {
364                                 System.out.println(
365                                         Util.bind("compilation.units" , String.valueOf(totalUnits))); //$NON-NLS-1$
366                         } else {
367                                 System.out.println(
368                                         Util.bind("compilation.unit" , String.valueOf(totalUnits))); //$NON-NLS-1$
369                         }
370                 }
371         }
372
373         protected void getMethodBodies(CompilationUnitDeclaration unit, int place) {
374                 //fill the methods bodies in order for the code to be generated
375
376                 if (unit.ignoreMethodBodies) {
377                         unit.ignoreFurtherInvestigation = true;
378                         return;
379                         // if initial diet parse did not work, no need to dig into method bodies.
380                 }
381
382                 if (place < parseThreshold)
383                         return; //work already done ...
384
385                 //real parse of the method....
386                 parser.scanner.setSource(
387                         unit.compilationResult.compilationUnit.getContents());
388                 if (unit.types != null) {
389                         for (int i = unit.types.length; --i >= 0;)
390                                 unit.types[i].parseMethod(parser, unit);
391                 }
392         }
393
394         /*
395          * Compiler crash recovery in case of unexpected runtime exceptions
396          */
397         protected void handleInternalException(
398                 Throwable internalException,
399                 CompilationUnitDeclaration unit,
400                 CompilationResult result) {
401
402                 /* dump a stack trace to the console */
403                 internalException.printStackTrace();
404
405                 /* find a compilation result */
406                 if ((unit != null)) // basing result upon the current unit if available
407                         result = unit.compilationResult; // current unit being processed ?
408                 if ((result == null) && (unitsToProcess != null) && (totalUnits > 0))
409                         result = unitsToProcess[totalUnits - 1].compilationResult;
410                 // last unit in beginToCompile ?
411
412                 if (result != null) {
413                         /* create and record a compilation problem */
414                         StringWriter stringWriter = new StringWriter();
415                         PrintWriter writer = new PrintWriter(stringWriter);
416                         internalException.printStackTrace(writer);
417                         StringBuffer buffer = stringWriter.getBuffer();
418
419                         result
420                                 .record(
421                                         problemReporter
422                                         .createProblem(
423                                                 result.getFileName(),
424                                                 IProblem.Unclassified,
425                                                 new String[] {
426                                                         Util.bind("compilation.internalError" ) //$NON-NLS-1$
427                                                                 + "\n"  //$NON-NLS-1$
428                                                                 + buffer.toString()},
429                                                 Error, // severity
430                                                 0, // source start
431                                                 0, // source end
432                                                 0, // line number               
433                                                 unit,
434                                                 result),
435                                         unit);
436
437                         /* hand back the compilation result */
438                         if (!result.hasBeenAccepted) {
439                                 requestor.acceptResult(result.tagAsAccepted());
440                         }
441                 }
442         }
443
444         /*
445          * Compiler recovery in case of internal AbortCompilation event
446          */
447         protected void handleInternalException(
448                 AbortCompilation abortException,
449                 CompilationUnitDeclaration unit) {
450
451                 /* special treatment for SilentAbort: silently cancelling the compilation process */
452                 if (abortException.isSilent) {
453                         if (abortException.silentException == null) {
454                                 return;
455                         } else {
456                                 throw abortException.silentException;
457                         }
458                 }
459
460                 /* uncomment following line to see where the abort came from */
461                 // abortException.printStackTrace(); 
462
463                 // Exception may tell which compilation result it is related, and which problem caused it
464                 CompilationResult result = abortException.compilationResult;
465                 if ((result == null) && (unit != null))
466                         result = unit.compilationResult; // current unit being processed ?
467                 if ((result == null) && (unitsToProcess != null) && (totalUnits > 0))
468                         result = unitsToProcess[totalUnits - 1].compilationResult;
469                 // last unit in beginToCompile ?
470                 if (result != null && !result.hasBeenAccepted) {
471                         /* distant problem which could not be reported back there */
472                         if (abortException.problemId != 0) {
473                                 result
474                                         .record(
475                                                 problemReporter
476                                                 .createProblem(
477                                                         result.getFileName(),
478                                                         abortException.problemId,
479                                                         abortException.problemArguments,
480                                                         Error, // severity
481                                                         0, // source start
482                                                         0, // source end
483                                                         0, // line number               
484                                                         unit,
485                                                         result),
486                                                 unit);                          
487                         } else {
488                                 /* distant internal exception which could not be reported back there */
489                                 if (abortException.exception != null) {
490                                         this.handleInternalException(abortException.exception, null, result);
491                                         return;
492                                 }
493                         }
494                         /* hand back the compilation result */
495                         if (!result.hasBeenAccepted) {
496                                 requestor.acceptResult(result.tagAsAccepted());
497                         }
498                 } else {
499                         /*
500                         if (abortException.problemId != 0){ 
501                                 IProblem problem =
502                                         problemReporter.createProblem(
503                                                 "???".toCharArray(),
504                                                 abortException.problemId, 
505                                                 abortException.problemArguments, 
506                                                 Error, // severity
507                                                 0, // source start
508                                                 0, // source end
509                                                 0); // line number
510                                 System.out.println(problem.getMessage());
511                         }
512                         */
513                         abortException.printStackTrace();
514                 }
515         }
516
517         /**
518          * Process a compilation unit already parsed and build.
519          */
520         private void process(CompilationUnitDeclaration unit, int i) {
521
522                 getMethodBodies(unit, i);
523
524                 // fault in fields & methods
525                 if (unit.scope != null)
526                         unit.scope.faultInTypes();
527
528                 // verify inherited methods
529                 if (unit.scope != null)
530                         unit.scope.verifyMethods(lookupEnvironment.methodVerifier());
531
532                 // type checking
533                 unit.resolve();
534
535                 // flow analysis
536                 unit.analyseCode();
537
538                 // code generation
539                 unit.generateCode();
540
541                 // reference info
542                 if (options.produceReferenceInfo && unit.scope != null)
543                         unit.scope.storeDependencyInfo();
544
545                 // refresh the total number of units known at this stage
546                 unit.compilationResult.totalUnitsKnown = totalUnits;
547         }
548         public void reset() {
549                 lookupEnvironment.reset();
550                 parser.scanner.source = null;
551                 unitsToProcess = null;
552                 if (DebugRequestor != null) DebugRequestor.reset();
553         }
554
555         /**
556          * Internal API used to resolve a compilation unit minimally for code assist engine
557          */
558         public CompilationUnitDeclaration resolve(ICompilationUnit sourceUnit) {
559                 CompilationUnitDeclaration unit = null;
560                 try {
561                         // build and record parsed units
562                         parseThreshold = 0; // will request a full parse
563                         beginToCompile(new ICompilationUnit[] { sourceUnit });
564                         // process all units (some more could be injected in the loop by the lookup environment)
565                         unit = unitsToProcess[0];
566                         getMethodBodies(unit, 0);
567                         if (unit.scope != null) {
568                                 // fault in fields & methods
569                                 unit.scope.faultInTypes();
570                                 // type checking
571                                 unit.resolve();
572                         }
573                         unitsToProcess[0] = null; // release reference to processed unit declaration
574                         requestor.acceptResult(unit.compilationResult.tagAsAccepted());
575                         return unit;
576                 } catch (AbortCompilation e) {
577                         this.handleInternalException(e, unit);
578                         return unit == null ? unitsToProcess[0] : unit;
579                 } catch (Error e) {
580                         this.handleInternalException(e, unit, null);
581                         throw e; // rethrow
582                 } catch (RuntimeException e) {
583                         this.handleInternalException(e, unit, null);
584                         throw e; // rethrow
585                 } finally {
586                         // No reset is performed there anymore since,
587                         // within the CodeAssist (or related tools),
588                         // the compiler may be called *after* a call
589                         // to this resolve(...) method. And such a call
590                         // needs to have a compiler with a non-empty
591                         // environment.
592                         // this.reset();
593                 }
594         }
595 }