Show line numbers (other than 1) in problems view for errors and warnings
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / internal / core / builder / AbstractImageBuilder.java
1 /*******************************************************************************
2  * Copyright (c) 2000, 2003 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials 
4  * are made available under the terms of the Common Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/cpl-v10.html
7  * 
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  *******************************************************************************/
11 package net.sourceforge.phpdt.internal.core.builder;
12
13 import java.util.ArrayList;
14 import java.util.Locale;
15
16 import net.sourceforge.phpdt.core.IJavaModelMarker;
17 import net.sourceforge.phpdt.core.JavaCore;
18 import net.sourceforge.phpdt.core.JavaModelException;
19 import net.sourceforge.phpdt.core.compiler.IProblem;
20 import net.sourceforge.phpdt.internal.compiler.CompilationResult;
21 import net.sourceforge.phpdt.internal.compiler.Compiler;
22 import net.sourceforge.phpdt.internal.compiler.DefaultErrorHandlingPolicies;
23 import net.sourceforge.phpdt.internal.compiler.ICompilerRequestor;
24 import net.sourceforge.phpdt.internal.compiler.problem.AbortCompilation;
25 import net.sourceforge.phpdt.internal.compiler.problem.ProblemHandler;
26 import net.sourceforge.phpdt.internal.core.util.Util;
27
28 import org.eclipse.core.resources.IMarker;
29 import org.eclipse.core.resources.IResource;
30 import org.eclipse.core.runtime.CoreException;
31 import org.eclipse.core.runtime.IPath;
32
33 /**
34  * The abstract superclass of Java builders. Provides the building and compilation mechanism in common with the batch and
35  * incremental builders.
36  */
37 public abstract class AbstractImageBuilder implements ICompilerRequestor {
38
39   protected PHPBuilder javaBuilder;
40
41   protected State newState;
42
43   // local copies
44   protected NameEnvironment nameEnvironment;
45
46   protected ClasspathMultiDirectory[] sourceLocations;
47
48   protected BuildNotifier notifier;
49
50   protected String encoding;
51
52   protected Compiler compiler;
53
54   protected WorkQueue workQueue;
55
56   protected ArrayList problemSourceFiles;
57
58   protected boolean compiledAllAtOnce;
59
60   private boolean inCompiler;
61
62   public static int MAX_AT_ONCE = 1000;
63
64   protected AbstractImageBuilder(PHPBuilder javaBuilder) {
65     this.javaBuilder = javaBuilder;
66     this.newState = new State(javaBuilder);
67
68     // local copies
69     this.nameEnvironment = javaBuilder.nameEnvironment;
70     this.sourceLocations = this.nameEnvironment.sourceLocations;
71     this.notifier = javaBuilder.notifier;
72
73     this.encoding = javaBuilder.javaProject.getOption(JavaCore.CORE_ENCODING, true);
74     this.compiler = newCompiler();
75     this.workQueue = new WorkQueue();
76     this.problemSourceFiles = new ArrayList(3);
77   }
78
79   public void acceptResult(CompilationResult result) {
80     // In Batch mode, we write out the class files, hold onto the dependency info
81     // & additional types and report problems.
82
83     // In Incremental mode, when writing out a class file we need to compare it
84     // against the previous file, remembering if structural changes occured.
85     // Before reporting the new problems, we need to update the problem count &
86     // remove the old problems. Plus delete additional class files that no longer exist.
87
88     SourceFile compilationUnit = (SourceFile) result.getCompilationUnit(); // go directly back to the sourceFile
89     if (!workQueue.isCompiled(compilationUnit)) {
90 //      try {
91         workQueue.finished(compilationUnit);
92         try {
93                         updateProblemsFor(compilationUnit, result); // record compilation problems before potentially adding duplicate errors
94                         updateTasksFor(compilationUnit, result); // record tasks 
95                 } catch (CoreException e) {
96                         throw internalException(e);
97                 } 
98                 
99         // String typeLocator = compilationUnit.typeLocator();
100         //                      ClassFile[] classFiles = result.getClassFiles();
101         //                      int length = classFiles.length;
102         //                      ArrayList duplicateTypeNames = null;
103         //                      ArrayList definedTypeNames = new ArrayList(length);
104         //                      for (int i = 0; i < length; i++) {
105         //                              ClassFile classFile = classFiles[i];
106         //                              char[][] compoundName = classFile.getCompoundName();
107         //                              char[] typeName = compoundName[compoundName.length - 1];
108         //                              boolean isNestedType = CharOperation.contains('$', typeName);
109         //
110         //                              // Look for a possible collision, if one exists, report an error but do not write the class file
111         //                              if (isNestedType) {
112         //                                      String qualifiedTypeName = new String(classFile.outerMostEnclosingClassFile().fileName());
113         //                                      if (newState.isDuplicateLocator(qualifiedTypeName, typeLocator))
114         //                                              continue;
115         //                              } else {
116         //                                      String qualifiedTypeName = new String(classFile.fileName()); // the qualified type name "p1/p2/A"
117         //                                      if (newState.isDuplicateLocator(qualifiedTypeName, typeLocator)) {
118         //                                              if (duplicateTypeNames == null)
119         //                                                      duplicateTypeNames = new ArrayList();
120         //                                              duplicateTypeNames.add(compoundName);
121         //                                              createErrorFor(compilationUnit.resource, ProjectPrefUtil.bind("build.duplicateClassFile", new String(typeName)));
122         // //$NON-NLS-1$
123         //                                              continue;
124         //                                      }
125         //                                      newState.recordLocatorForType(qualifiedTypeName, typeLocator);
126         //                              }
127         //                              definedTypeNames.add(writeClassFile(classFile, compilationUnit.sourceLocation.binaryFolder, !isNestedType));
128         //                      }
129
130         //                      finishedWith(typeLocator, result, compilationUnit.getMainTypeName(), definedTypeNames, duplicateTypeNames);
131         notifier.compiled(compilationUnit);
132 //      } catch (CoreException e) {
133 //        Util.log(e, "JavaBuilder handling CoreException"); //$NON-NLS-1$
134 //        createErrorFor(compilationUnit.resource, Util.bind("build.inconsistentClassFile")); //$NON-NLS-1$
135 //      }
136     }
137   }
138
139   protected void cleanUp() {
140     this.nameEnvironment.cleanup();
141
142     this.javaBuilder = null;
143     this.nameEnvironment = null;
144     this.sourceLocations = null;
145     this.notifier = null;
146     this.compiler = null;
147     this.workQueue = null;
148     this.problemSourceFiles = null;
149   }
150
151   /*
152    * Compile the given elements, adding more elements to the work queue if they are affected by the changes.
153    */
154   protected void compile(SourceFile[] units) {
155     int toDo = units.length;
156     if (this.compiledAllAtOnce = toDo <= MAX_AT_ONCE) {
157       // do them all now
158       if (PHPBuilder.DEBUG)
159         for (int i = 0; i < toDo; i++)
160           System.out.println("About to compile " + units[i].typeLocator()); //$NON-NLS-1$
161       compile(units, null);
162     } else {
163       int i = 0;
164       boolean compilingFirstGroup = true;
165       while (i < toDo) {
166         int doNow = toDo < MAX_AT_ONCE ? toDo : MAX_AT_ONCE;
167         int index = 0;
168         SourceFile[] toCompile = new SourceFile[doNow];
169         while (i < toDo && index < doNow) {
170           // Although it needed compiling when this method was called, it may have
171           // already been compiled when it was referenced by another unit.
172           SourceFile unit = units[i++];
173           if (compilingFirstGroup || workQueue.isWaiting(unit)) {
174             if (PHPBuilder.DEBUG)
175               System.out.println("About to compile " + unit.typeLocator()); //$NON-NLS-1$
176             toCompile[index++] = unit;
177           }
178         }
179         if (index < doNow)
180           System.arraycopy(toCompile, 0, toCompile = new SourceFile[index], 0, index);
181         SourceFile[] additionalUnits = new SourceFile[toDo - i];
182         System.arraycopy(units, i, additionalUnits, 0, additionalUnits.length);
183         compilingFirstGroup = false;
184         compile(toCompile, additionalUnits);
185       }
186     }
187   }
188
189   void compile(SourceFile[] units, SourceFile[] additionalUnits) {
190     if (units.length == 0)
191       return;
192     notifier.aboutToCompile(units[0]); // just to change the message
193
194     // extend additionalFilenames with all hierarchical problem types found during this entire build
195     if (!problemSourceFiles.isEmpty()) {
196       int toAdd = problemSourceFiles.size();
197       int length = additionalUnits == null ? 0 : additionalUnits.length;
198       if (length == 0)
199         additionalUnits = new SourceFile[toAdd];
200       else
201         System.arraycopy(additionalUnits, 0, additionalUnits = new SourceFile[length + toAdd], 0, length);
202       for (int i = 0; i < toAdd; i++)
203         additionalUnits[length + i] = (SourceFile) problemSourceFiles.get(i);
204     }
205     String[] initialTypeNames = new String[units.length];
206     for (int i = 0, l = units.length; i < l; i++)
207       initialTypeNames[i] = units[i].initialTypeName;
208     nameEnvironment.setNames(initialTypeNames, additionalUnits);
209     notifier.checkCancel();
210     try {
211       inCompiler = true;
212       compiler.compile(units);
213     } catch (AbortCompilation ignored) {
214       // ignore the AbortCompilcation coming from BuildNotifier.checkCancelWithinCompiler()
215       // the Compiler failed after the user has chose to cancel... likely due to an OutOfMemory error
216     } finally {
217       inCompiler = false;
218     }
219     // Check for cancel immediately after a compile, because the compiler may
220     // have been cancelled but without propagating the correct exception
221     notifier.checkCancel();
222   }
223
224   protected void createErrorFor(IResource resource, String message) {
225     try {
226       IMarker marker = resource.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER);
227       int severity = IMarker.SEVERITY_ERROR;
228       if (message.equals(Util.bind("build.duplicateResource"))) //$NON-NLS-1$
229         if (JavaCore.WARNING.equals(javaBuilder.javaProject.getOption(JavaCore.CORE_JAVA_BUILD_DUPLICATE_RESOURCE, true)))
230           severity = IMarker.SEVERITY_WARNING;
231       marker.setAttributes(new String[] { IMarker.MESSAGE, IMarker.SEVERITY, IMarker.CHAR_START, IMarker.CHAR_END }, new Object[] {
232           message,
233           new Integer(severity),
234           new Integer(0),
235           new Integer(1) });
236     } catch (CoreException e) {
237       throw internalException(e);
238     }
239   }
240
241   //protected void finishedWith(String sourceLocator, CompilationResult result, char[] mainTypeName) throws CoreException {//,
242   // ArrayList definedTypeNames, ArrayList duplicateTypeNames) throws CoreException {
243   //    if (duplicateTypeNames == null) {
244   //            newState.record(sourceLocator, result.qualifiedReferences, result.simpleNameReferences, mainTypeName, definedTypeNames);
245   //            return;
246   //    }
247   //
248   //    char[][][] qualifiedRefs = result.qualifiedReferences;
249   //    char[][] simpleRefs = result.simpleNameReferences;
250   //    // for each duplicate type p1.p2.A, add the type name A (package was already added)
251   //    next : for (int i = 0, l = duplicateTypeNames.size(); i < l; i++) {
252   //            char[][] compoundName = (char[][]) duplicateTypeNames.get(i);
253   //            char[] typeName = compoundName[compoundName.length - 1];
254   //            int sLength = simpleRefs.length;
255   //            for (int j = 0; j < sLength; j++)
256   //                    if (CharOperation.equals(simpleRefs[j], typeName))
257   //                            continue next;
258   //            System.arraycopy(simpleRefs, 0, simpleRefs = new char[sLength + 1][], 0, sLength);
259   //            simpleRefs[sLength] = typeName;
260   //    }
261   //    newState.record(sourceLocator, qualifiedRefs, simpleRefs, mainTypeName, definedTypeNames);
262   //}
263
264   //protected IContainer createFolder(IPath packagePath, IContainer outputFolder) throws CoreException {
265   //    if (packagePath.isEmpty()) return outputFolder;
266   //    IFolder folder = outputFolder.getFolder(packagePath);
267   //    if (!folder.exists()) {
268   //            createFolder(packagePath.removeLastSegments(1), outputFolder);
269   //            folder.create(true, true, null);
270   //            folder.setDerived(true);
271   //    }
272   //    return folder;
273   //}
274
275   protected RuntimeException internalException(CoreException t) {
276     ImageBuilderInternalException imageBuilderException = new ImageBuilderInternalException(t);
277     if (inCompiler)
278       return new AbortCompilation(true, imageBuilderException);
279     return imageBuilderException;
280   }
281
282   protected Compiler newCompiler() {
283     // called once when the builder is initialized... can override if needed
284     return new Compiler(nameEnvironment, DefaultErrorHandlingPolicies.proceedWithAllProblems(), javaBuilder.javaProject
285         .getOptions(true), this, ProblemFactory.getProblemFactory(Locale.getDefault()));
286   }
287
288   protected boolean isExcludedFromProject(IPath childPath) throws JavaModelException {
289     // answer whether the folder should be ignored when walking the project as a source folder
290     if (childPath.segmentCount() > 2)
291       return false; // is a subfolder of a package
292
293     for (int j = 0, k = sourceLocations.length; j < k; j++) {
294       //                if (childPath.equals(sourceLocations[j].binaryFolder.getFullPath())) return true;
295       if (childPath.equals(sourceLocations[j].sourceFolder.getFullPath()))
296         return true;
297     }
298     // skip default output folder which may not be used by any source folder
299     return false; //childPath.equals(javaBuilder.javaProject.getOutputLocation());
300   }
301
302   /**
303    * Creates a marker from each problem and adds it to the resource. The marker is as follows: - its type is T_PROBLEM - its plugin
304    * ID is the JavaBuilder's plugin ID - its message is the problem's message - its priority reflects the severity of the problem -
305    * its range is the problem's range - it has an extra attribute "ID" which holds the problem's id
306    */
307   protected void storeProblemsFor(SourceFile sourceFile, IProblem[] problems) throws CoreException {
308     if (sourceFile == null || problems == null || problems.length == 0)
309       return;
310
311     //  String missingClassFile = null;
312     IResource resource = sourceFile.resource;
313     for (int i = 0, l = problems.length; i < l; i++) {
314       IProblem problem = problems[i];
315       int id = problem.getID();
316       switch (id) {
317       case IProblem.IsClassPathCorrect:
318         //                              PHPBuilder.removeProblemsAndTasksFor(javaBuilder.currentProject); // make this the only problem for this project
319         //                              String[] args = problem.getArguments();
320         //                              missingClassFile = args[0];
321         break;
322       case IProblem.SuperclassMustBeAClass:
323       case IProblem.SuperInterfaceMustBeAnInterface:
324       case IProblem.HierarchyCircularitySelfReference:
325       case IProblem.HierarchyCircularity:
326       case IProblem.HierarchyHasProblems:
327       case IProblem.SuperclassNotFound:
328       case IProblem.SuperclassNotVisible:
329       case IProblem.SuperclassAmbiguous:
330       case IProblem.SuperclassInternalNameProvided:
331       case IProblem.SuperclassInheritedNameHidesEnclosingName:
332       case IProblem.InterfaceNotFound:
333       case IProblem.InterfaceNotVisible:
334       case IProblem.InterfaceAmbiguous:
335       case IProblem.InterfaceInternalNameProvided:
336       case IProblem.InterfaceInheritedNameHidesEnclosingName:
337         // ensure that this file is always retrieved from source for the rest of the build
338         if (!problemSourceFiles.contains(sourceFile))
339           problemSourceFiles.add(sourceFile);
340         break;
341       }
342
343       if (id != IProblem.Task) {
344         IMarker marker = resource.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER);
345         marker.setAttributes(new String[] {
346             IMarker.MESSAGE,
347             IMarker.SEVERITY,
348             IJavaModelMarker.ID,
349             IMarker.CHAR_START,
350             IMarker.CHAR_END,
351             IMarker.LINE_NUMBER,
352             IJavaModelMarker.ARGUMENTS }, new Object[] {
353             problem.getMessage(),
354             new Integer(problem.isError() ? IMarker.SEVERITY_ERROR : IMarker.SEVERITY_WARNING),
355             new Integer(id),
356             new Integer(problem.getSourceStart()),
357             new Integer(problem.getSourceEnd() + 1),
358             new Integer(problem.getSourceLineNumber()),
359             Util.getProblemArgumentsForMarker(problem.getArguments()) });
360       }
361
362       /*
363        * Do NOT want to populate the Java Model just to find the matching Java element. Also cannot query compilation units located
364        * in folders with invalid package names such as 'a/b.c.d/e'.
365        *  // compute a user-friendly location IJavaElement element = JavaCore.create(resource); if (element instanceof
366        * net.sourceforge.phpdt.core.ICompilationUnit) { // try to find a finer grain element
367        * net.sourceforge.phpdt.core.ICompilationUnit unit = (net.sourceforge.phpdt.core.ICompilationUnit) element; IJavaElement
368        * fragment = unit.getElementAt(problem.getSourceStart()); if (fragment != null) element = fragment; } String location = null;
369        * if (element instanceof JavaElement) location = ((JavaElement) element).readableName(); if (location != null)
370        * marker.setAttribute(IMarker.LOCATION, location);
371        */
372
373       //                if (missingClassFile != null)
374       //                        throw new MissingClassFileException(missingClassFile);
375     }
376   }
377
378   protected void storeTasksFor(SourceFile sourceFile, IProblem[] tasks) throws CoreException {
379     if (sourceFile == null || tasks == null || tasks.length == 0)
380       return;
381
382     IResource resource = sourceFile.resource;
383     for (int i = 0, l = tasks.length; i < l; i++) {
384       IProblem task = tasks[i];
385       if (task.getID() == IProblem.Task) {
386         IMarker marker = resource.createMarker(IJavaModelMarker.TASK_MARKER);
387         int priority = IMarker.PRIORITY_NORMAL;
388         String compilerPriority = task.getArguments()[2];
389         if (JavaCore.COMPILER_TASK_PRIORITY_HIGH.equals(compilerPriority))
390           priority = IMarker.PRIORITY_HIGH;
391         else if (JavaCore.COMPILER_TASK_PRIORITY_LOW.equals(compilerPriority))
392           priority = IMarker.PRIORITY_LOW;
393         marker.setAttributes(new String[] {
394             IMarker.MESSAGE,
395             IMarker.PRIORITY,
396             IMarker.DONE,
397             IMarker.CHAR_START,
398             IMarker.CHAR_END,
399             IMarker.LINE_NUMBER,
400             IMarker.USER_EDITABLE, }, new Object[] {
401             task.getMessage(),
402             new Integer(priority),
403             new Boolean(false),
404             new Integer(task.getSourceStart()),
405             new Integer(task.getSourceEnd() + 1),
406             new Integer(task.getSourceLineNumber()),
407             new Boolean(false), });
408       }
409     }
410   }
411
412   protected void updateProblemsFor(SourceFile sourceFile, CompilationResult result) throws CoreException {
413     IProblem[] problems = result.getProblems();
414     if (problems == null || problems.length == 0)
415       return;
416     //axelcl start insert - calculate line numbers
417     for (int i = 0; i < problems.length; i++) {
418       if (problems[i].getSourceLineNumber() == 1) {
419         problems[i].setSourceLineNumber(ProblemHandler
420             .searchLineNumber(result.lineSeparatorPositions, problems[i].getSourceStart()));
421       }
422     }
423     //axelcl end insert
424     notifier.updateProblemCounts(problems);
425     storeProblemsFor(sourceFile, problems);
426   }
427
428   protected void updateTasksFor(SourceFile sourceFile, CompilationResult result) throws CoreException {
429     IProblem[] tasks = result.getTasks();
430     if (tasks == null || tasks.length == 0)
431       return;
432
433     storeTasksFor(sourceFile, tasks);
434   }
435
436   //protected char[] writeClassFile(ClassFile classFile, IContainer outputFolder, boolean isSecondaryType) throws CoreException {
437   //    String fileName = new String(classFile.fileName()); // the qualified type name "p1/p2/A"
438   //    IPath filePath = new Path(fileName);
439   //    IContainer container = outputFolder;
440   //    if (filePath.segmentCount() > 1) {
441   //            container = createFolder(filePath.removeLastSegments(1), outputFolder);
442   //            filePath = new Path(filePath.lastSegment());
443   //    }
444   //
445   //    IFile file = container.getFile(filePath.addFileExtension(JavaBuilder.CLASS_EXTENSION));
446   //    writeClassFileBytes(classFile.getBytes(), file, fileName, isSecondaryType);
447   //    // answer the name of the class file as in Y or Y$M
448   //    return filePath.lastSegment().toCharArray();
449   //}
450   //
451   //protected void writeClassFileBytes(byte[] bytes, IFile file, String qualifiedFileName, boolean isSecondaryType) throws
452   // CoreException {
453   //    if (file.exists()) {
454   //            // Deal with shared output folders... last one wins... no collision cases detected
455   //            if (JavaBuilder.DEBUG)
456   //                    System.out.println("Writing changed class file " + file.getName());//$NON-NLS-1$
457   //            file.setContents(new ByteArrayInputStream(bytes), true, false, null);
458   //            if (!file.isDerived())
459   //                    file.setDerived(true);
460   //    } else {
461   //            // Default implementation just writes out the bytes for the new class file...
462   //            if (JavaBuilder.DEBUG)
463   //                    System.out.println("Writing new class file " + file.getName());//$NON-NLS-1$
464   //            file.create(new ByteArrayInputStream(bytes), IResource.FORCE, null);
465   //            file.setDerived(true);
466   //    }
467   //}
468 }