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