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
 
   9  *     IBM Corporation - initial API and implementation
 
  10  *******************************************************************************/
 
  11 package net.sourceforge.phpdt.internal.core.builder;
 
  13 import java.util.ArrayList;
 
  14 import java.util.Locale;
 
  16 import net.sourceforge.phpdt.core.IJavaModelMarker;
 
  17 import net.sourceforge.phpdt.core.JavaModelException;
 
  18 import net.sourceforge.phpdt.core.JavaCore;
 
  19 import net.sourceforge.phpdt.core.compiler.IProblem;
 
  20 import net.sourceforge.phpdt.internal.compiler.Compiler;
 
  21 import net.sourceforge.phpdt.internal.compiler.CompilationResult;
 
  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.core.util.Util;
 
  27 import org.eclipse.core.resources.IContainer;
 
  28 import org.eclipse.core.resources.IFolder;
 
  29 import org.eclipse.core.resources.IMarker;
 
  30 import org.eclipse.core.resources.IResource;
 
  31 import org.eclipse.core.runtime.CoreException;
 
  32 import org.eclipse.core.runtime.IPath;
 
  35  * The abstract superclass of Java builders.
 
  36  * Provides the building and compilation mechanism
 
  37  * in common with the batch and incremental builders.
 
  39 public abstract class AbstractImageBuilder implements ICompilerRequestor {
 
  41 protected PHPBuilder javaBuilder;
 
  42 protected State newState;
 
  45 protected NameEnvironment nameEnvironment;
 
  46 protected ClasspathMultiDirectory[] sourceLocations;
 
  47 protected BuildNotifier notifier; 
 
  49 protected String encoding;
 
  50 protected Compiler compiler;
 
  51 protected WorkQueue workQueue;
 
  52 protected ArrayList problemSourceFiles;
 
  53 protected boolean compiledAllAtOnce;
 
  55 private boolean inCompiler;
 
  57 public static int MAX_AT_ONCE = 1000;
 
  59 protected AbstractImageBuilder(PHPBuilder javaBuilder) {
 
  60         this.javaBuilder = javaBuilder;
 
  61         this.newState = new State(javaBuilder);
 
  64         this.nameEnvironment = javaBuilder.nameEnvironment;
 
  65         this.sourceLocations = this.nameEnvironment.sourceLocations;
 
  66         this.notifier = javaBuilder.notifier;
 
  68         this.encoding = javaBuilder.javaProject.getOption(JavaCore.CORE_ENCODING, true);
 
  69         this.compiler = newCompiler();
 
  70         this.workQueue = new WorkQueue();
 
  71         this.problemSourceFiles = new ArrayList(3);
 
  74 public void acceptResult(CompilationResult result) {
 
  75         // In Batch mode, we write out the class files, hold onto the dependency info
 
  76         // & additional types and report problems.
 
  78         // In Incremental mode, when writing out a class file we need to compare it
 
  79         // against the previous file, remembering if structural changes occured.
 
  80         // Before reporting the new problems, we need to update the problem count &
 
  81         // remove the old problems. Plus delete additional class files that no longer exist.
 
  83         SourceFile compilationUnit = (SourceFile) result.getCompilationUnit(); // go directly back to the sourceFile
 
  84         if (!workQueue.isCompiled(compilationUnit)) {
 
  86                         workQueue.finished(compilationUnit);
 
  87                         updateProblemsFor(compilationUnit, result); // record compilation problems before potentially adding duplicate errors
 
  88                         updateTasksFor(compilationUnit, result); // record tasks
 
  90                         String typeLocator = compilationUnit.typeLocator();
 
  91 //                      ClassFile[] classFiles = result.getClassFiles();
 
  92 //                      int length = classFiles.length;
 
  93 //                      ArrayList duplicateTypeNames = null;
 
  94 //                      ArrayList definedTypeNames = new ArrayList(length);
 
  95 //                      for (int i = 0; i < length; i++) {
 
  96 //                              ClassFile classFile = classFiles[i];
 
  97 //                              char[][] compoundName = classFile.getCompoundName();
 
  98 //                              char[] typeName = compoundName[compoundName.length - 1];
 
  99 //                              boolean isNestedType = CharOperation.contains('$', typeName);
 
 101 //                              // Look for a possible collision, if one exists, report an error but do not write the class file
 
 102 //                              if (isNestedType) {
 
 103 //                                      String qualifiedTypeName = new String(classFile.outerMostEnclosingClassFile().fileName());
 
 104 //                                      if (newState.isDuplicateLocator(qualifiedTypeName, typeLocator))
 
 107 //                                      String qualifiedTypeName = new String(classFile.fileName()); // the qualified type name "p1/p2/A"
 
 108 //                                      if (newState.isDuplicateLocator(qualifiedTypeName, typeLocator)) {
 
 109 //                                              if (duplicateTypeNames == null)
 
 110 //                                                      duplicateTypeNames = new ArrayList();
 
 111 //                                              duplicateTypeNames.add(compoundName);
 
 112 //                                              createErrorFor(compilationUnit.resource, Util.bind("build.duplicateClassFile", new String(typeName))); //$NON-NLS-1$
 
 115 //                                      newState.recordLocatorForType(qualifiedTypeName, typeLocator);
 
 117 //                              definedTypeNames.add(writeClassFile(classFile, compilationUnit.sourceLocation.binaryFolder, !isNestedType));
 
 120 //                      finishedWith(typeLocator, result, compilationUnit.getMainTypeName(), definedTypeNames, duplicateTypeNames);
 
 121                         notifier.compiled(compilationUnit);
 
 122                 } catch (CoreException e) {
 
 123                         Util.log(e, "JavaBuilder handling CoreException"); //$NON-NLS-1$
 
 124                         createErrorFor(compilationUnit.resource, Util.bind("build.inconsistentClassFile")); //$NON-NLS-1$
 
 129 protected void cleanUp() {
 
 130         this.nameEnvironment.cleanup();
 
 132         this.javaBuilder = null;
 
 133         this.nameEnvironment = null;
 
 134         this.sourceLocations = null;
 
 135         this.notifier = null;
 
 136         this.compiler = null;
 
 137         this.workQueue = null;
 
 138         this.problemSourceFiles = null;
 
 141 /* Compile the given elements, adding more elements to the work queue 
 
 142 * if they are affected by the changes.
 
 144 protected void compile(SourceFile[] units) {
 
 145         int toDo = units.length;
 
 146         if (this.compiledAllAtOnce = toDo <= MAX_AT_ONCE) {
 
 148                 if (PHPBuilder.DEBUG)
 
 149                         for (int i = 0; i < toDo; i++)
 
 150                                 System.out.println("About to compile " + units[i].typeLocator()); //$NON-NLS-1$
 
 151                 compile(units, null);
 
 154                 boolean compilingFirstGroup = true;
 
 156                         int doNow = toDo < MAX_AT_ONCE ? toDo : MAX_AT_ONCE;
 
 158                         SourceFile[] toCompile = new SourceFile[doNow];
 
 159                         while (i < toDo && index < doNow) {
 
 160                                 // Although it needed compiling when this method was called, it may have
 
 161                                 // already been compiled when it was referenced by another unit.
 
 162                                 SourceFile unit = units[i++];
 
 163                                 if (compilingFirstGroup || workQueue.isWaiting(unit)) {
 
 164                                         if (PHPBuilder.DEBUG)
 
 165                                                 System.out.println("About to compile " + unit.typeLocator()); //$NON-NLS-1$
 
 166                                         toCompile[index++] = unit;
 
 170                                 System.arraycopy(toCompile, 0, toCompile = new SourceFile[index], 0, index);
 
 171                         SourceFile[] additionalUnits = new SourceFile[toDo - i];
 
 172                         System.arraycopy(units, i, additionalUnits, 0, additionalUnits.length);
 
 173                         compilingFirstGroup = false;
 
 174                         compile(toCompile, additionalUnits);
 
 179 void compile(SourceFile[] units, SourceFile[] additionalUnits) {
 
 180         if (units.length == 0) return;
 
 181         notifier.aboutToCompile(units[0]); // just to change the message
 
 183         // extend additionalFilenames with all hierarchical problem types found during this entire build
 
 184         if (!problemSourceFiles.isEmpty()) {
 
 185                 int toAdd = problemSourceFiles.size();
 
 186                 int length = additionalUnits == null ? 0 : additionalUnits.length;
 
 188                         additionalUnits = new SourceFile[toAdd];
 
 190                         System.arraycopy(additionalUnits, 0, additionalUnits = new SourceFile[length + toAdd], 0, length);
 
 191                 for (int i = 0; i < toAdd; i++)
 
 192                         additionalUnits[length + i] = (SourceFile) problemSourceFiles.get(i);
 
 194         String[] initialTypeNames = new String[units.length];
 
 195         for (int i = 0, l = units.length; i < l; i++)
 
 196                 initialTypeNames[i] = units[i].initialTypeName;
 
 197         nameEnvironment.setNames(initialTypeNames, additionalUnits);
 
 198         notifier.checkCancel();
 
 201                 compiler.compile(units);
 
 202         } catch (AbortCompilation ignored) {
 
 203                 // ignore the AbortCompilcation coming from BuildNotifier.checkCancelWithinCompiler()
 
 204                 // the Compiler failed after the user has chose to cancel... likely due to an OutOfMemory error
 
 208         // Check for cancel immediately after a compile, because the compiler may
 
 209         // have been cancelled but without propagating the correct exception
 
 210         notifier.checkCancel();
 
 213 protected void createErrorFor(IResource resource, String message) {
 
 215                 IMarker marker = resource.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER);
 
 216                 int severity = IMarker.SEVERITY_ERROR;
 
 217                 if (message.equals(Util.bind("build.duplicateResource"))) //$NON-NLS-1$
 
 218                         if (JavaCore.WARNING.equals(javaBuilder.javaProject.getOption(JavaCore.CORE_JAVA_BUILD_DUPLICATE_RESOURCE, true)))
 
 219                                 severity = IMarker.SEVERITY_WARNING;
 
 220                 marker.setAttributes(
 
 221                         new String[] {IMarker.MESSAGE, IMarker.SEVERITY, IMarker.CHAR_START, IMarker.CHAR_END},
 
 222                         new Object[] {message, new Integer(severity), new Integer(0), new Integer(1)});
 
 223         } catch (CoreException e) {
 
 224                 throw internalException(e);
 
 228 //protected void finishedWith(String sourceLocator, CompilationResult result, char[] mainTypeName) throws CoreException {//, ArrayList definedTypeNames, ArrayList duplicateTypeNames) throws CoreException {
 
 229 //      if (duplicateTypeNames == null) {
 
 230 //              newState.record(sourceLocator, result.qualifiedReferences, result.simpleNameReferences, mainTypeName, definedTypeNames);
 
 234 //      char[][][] qualifiedRefs = result.qualifiedReferences;
 
 235 //      char[][] simpleRefs = result.simpleNameReferences;
 
 236 //      // for each duplicate type p1.p2.A, add the type name A (package was already added)
 
 237 //      next : for (int i = 0, l = duplicateTypeNames.size(); i < l; i++) {
 
 238 //              char[][] compoundName = (char[][]) duplicateTypeNames.get(i);
 
 239 //              char[] typeName = compoundName[compoundName.length - 1];
 
 240 //              int sLength = simpleRefs.length;
 
 241 //              for (int j = 0; j < sLength; j++)
 
 242 //                      if (CharOperation.equals(simpleRefs[j], typeName))
 
 244 //              System.arraycopy(simpleRefs, 0, simpleRefs = new char[sLength + 1][], 0, sLength);
 
 245 //              simpleRefs[sLength] = typeName;
 
 247 //      newState.record(sourceLocator, qualifiedRefs, simpleRefs, mainTypeName, definedTypeNames);
 
 250 protected IContainer createFolder(IPath packagePath, IContainer outputFolder) throws CoreException {
 
 251         if (packagePath.isEmpty()) return outputFolder;
 
 252         IFolder folder = outputFolder.getFolder(packagePath);
 
 253         if (!folder.exists()) {
 
 254                 createFolder(packagePath.removeLastSegments(1), outputFolder);
 
 255                 folder.create(true, true, null);
 
 256                 folder.setDerived(true);
 
 261 protected RuntimeException internalException(CoreException t) {
 
 262         ImageBuilderInternalException imageBuilderException = new ImageBuilderInternalException(t);
 
 264                 return new AbortCompilation(true, imageBuilderException);
 
 265         return imageBuilderException;
 
 268 protected Compiler newCompiler() {
 
 269         // called once when the builder is initialized... can override if needed
 
 272                 DefaultErrorHandlingPolicies.proceedWithAllProblems(),
 
 273                 javaBuilder.javaProject.getOptions(true),
 
 275                 ProblemFactory.getProblemFactory(Locale.getDefault()));
 
 278 protected boolean isExcludedFromProject(IPath childPath) throws JavaModelException {
 
 279         // answer whether the folder should be ignored when walking the project as a source folder
 
 280         if (childPath.segmentCount() > 2) return false; // is a subfolder of a package
 
 282         for (int j = 0, k = sourceLocations.length; j < k; j++) {
 
 283                 if (childPath.equals(sourceLocations[j].binaryFolder.getFullPath())) return true;
 
 284                 if (childPath.equals(sourceLocations[j].sourceFolder.getFullPath())) return true;
 
 286         // skip default output folder which may not be used by any source folder
 
 287         return childPath.equals(javaBuilder.javaProject.getOutputLocation());
 
 291  * Creates a marker from each problem and adds it to the resource.
 
 292  * The marker is as follows:
 
 293  *   - its type is T_PROBLEM
 
 294  *   - its plugin ID is the JavaBuilder's plugin ID
 
 295  *       - its message is the problem's message
 
 296  *       - its priority reflects the severity of the problem
 
 297  *       - its range is the problem's range
 
 298  *       - it has an extra attribute "ID" which holds the problem's id
 
 300 protected void storeProblemsFor(SourceFile sourceFile, IProblem[] problems) throws CoreException {
 
 301         if (sourceFile == null || problems == null || problems.length == 0) return;
 
 303 //      String missingClassFile = null;
 
 304         IResource resource = sourceFile.resource;
 
 305         for (int i = 0, l = problems.length; i < l; i++) {
 
 306                 IProblem problem = problems[i];
 
 307                 int id = problem.getID();
 
 309                         case IProblem.IsClassPathCorrect :
 
 310 //                              PHPBuilder.removeProblemsAndTasksFor(javaBuilder.currentProject); // make this the only problem for this project
 
 311 //                              String[] args = problem.getArguments();
 
 312 //                              missingClassFile = args[0];
 
 314                         case IProblem.SuperclassMustBeAClass :
 
 315                         case IProblem.SuperInterfaceMustBeAnInterface :
 
 316                         case IProblem.HierarchyCircularitySelfReference :
 
 317                         case IProblem.HierarchyCircularity :
 
 318                         case IProblem.HierarchyHasProblems :
 
 319                         case IProblem.SuperclassNotFound :
 
 320                         case IProblem.SuperclassNotVisible :
 
 321                         case IProblem.SuperclassAmbiguous :
 
 322                         case IProblem.SuperclassInternalNameProvided :
 
 323                         case IProblem.SuperclassInheritedNameHidesEnclosingName :
 
 324                         case IProblem.InterfaceNotFound :
 
 325                         case IProblem.InterfaceNotVisible :
 
 326                         case IProblem.InterfaceAmbiguous :
 
 327                         case IProblem.InterfaceInternalNameProvided :
 
 328                         case IProblem.InterfaceInheritedNameHidesEnclosingName :
 
 329                                 // ensure that this file is always retrieved from source for the rest of the build
 
 330                                 if (!problemSourceFiles.contains(sourceFile))
 
 331                                         problemSourceFiles.add(sourceFile);
 
 335                 if (id != IProblem.Task) {
 
 336                         IMarker marker = resource.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER);
 
 337                         marker.setAttributes(
 
 345                                         IJavaModelMarker.ARGUMENTS},
 
 347                                         problem.getMessage(),
 
 348                                         new Integer(problem.isError() ? IMarker.SEVERITY_ERROR : IMarker.SEVERITY_WARNING), 
 
 350                                         new Integer(problem.getSourceStart()),
 
 351                                         new Integer(problem.getSourceEnd() + 1),
 
 352                                         new Integer(problem.getSourceLineNumber()),
 
 353                                         Util.getProblemArgumentsForMarker(problem.getArguments())
 
 357 /* Do NOT want to populate the Java Model just to find the matching Java element.
 
 358  * Also cannot query compilation units located in folders with invalid package
 
 359  * names such as 'a/b.c.d/e'.
 
 361                 // compute a user-friendly location
 
 362                 IJavaElement element = JavaCore.create(resource);
 
 363                 if (element instanceof org.eclipse.jdt.core.ICompilationUnit) { // try to find a finer grain element
 
 364                         org.eclipse.jdt.core.ICompilationUnit unit = (org.eclipse.jdt.core.ICompilationUnit) element;
 
 365                         IJavaElement fragment = unit.getElementAt(problem.getSourceStart());
 
 366                         if (fragment != null) element = fragment;
 
 368                 String location = null;
 
 369                 if (element instanceof JavaElement)
 
 370                         location = ((JavaElement) element).readableName();
 
 371                 if (location != null)
 
 372                         marker.setAttribute(IMarker.LOCATION, location);
 
 375 //              if (missingClassFile != null)
 
 376 //                      throw new MissingClassFileException(missingClassFile);
 
 380 protected void storeTasksFor(SourceFile sourceFile, IProblem[] tasks) throws CoreException {
 
 381         if (sourceFile == null || tasks == null || tasks.length == 0) return;
 
 383         IResource resource = sourceFile.resource;
 
 384         for (int i = 0, l = tasks.length; i < l; i++) {
 
 385                 IProblem task = tasks[i];
 
 386                 if (task.getID() == IProblem.Task) {
 
 387                         IMarker marker = resource.createMarker(IJavaModelMarker.TASK_MARKER);
 
 388                         int priority = IMarker.PRIORITY_NORMAL;
 
 389                         String compilerPriority = task.getArguments()[2];
 
 390                         if (JavaCore.COMPILER_TASK_PRIORITY_HIGH.equals(compilerPriority))
 
 391                                 priority = IMarker.PRIORITY_HIGH;
 
 392                         else if (JavaCore.COMPILER_TASK_PRIORITY_LOW.equals(compilerPriority))
 
 393                                 priority = IMarker.PRIORITY_LOW;
 
 394                         marker.setAttributes(
 
 402                                         IMarker.USER_EDITABLE, 
 
 406                                         new Integer(priority),
 
 408                                         new Integer(task.getSourceStart()),
 
 409                                         new Integer(task.getSourceEnd() + 1),
 
 410                                         new Integer(task.getSourceLineNumber()),
 
 417 protected void updateProblemsFor(SourceFile sourceFile, CompilationResult result) throws CoreException {
 
 418         IProblem[] problems = result.getProblems();
 
 419         if (problems == null || problems.length == 0) return;
 
 421         notifier.updateProblemCounts(problems);
 
 422         storeProblemsFor(sourceFile, problems);
 
 425 protected void updateTasksFor(SourceFile sourceFile, CompilationResult result) throws CoreException {
 
 426         IProblem[] tasks = result.getTasks();
 
 427         if (tasks == null || tasks.length == 0) return;
 
 429         storeTasksFor(sourceFile, tasks);
 
 432 //protected char[] writeClassFile(ClassFile classFile, IContainer outputFolder, boolean isSecondaryType) throws CoreException {
 
 433 //      String fileName = new String(classFile.fileName()); // the qualified type name "p1/p2/A"
 
 434 //      IPath filePath = new Path(fileName);
 
 435 //      IContainer container = outputFolder;
 
 436 //      if (filePath.segmentCount() > 1) {
 
 437 //              container = createFolder(filePath.removeLastSegments(1), outputFolder);
 
 438 //              filePath = new Path(filePath.lastSegment());
 
 441 //      IFile file = container.getFile(filePath.addFileExtension(JavaBuilder.CLASS_EXTENSION));
 
 442 //      writeClassFileBytes(classFile.getBytes(), file, fileName, isSecondaryType);
 
 443 //      // answer the name of the class file as in Y or Y$M
 
 444 //      return filePath.lastSegment().toCharArray();
 
 447 //protected void writeClassFileBytes(byte[] bytes, IFile file, String qualifiedFileName, boolean isSecondaryType) throws CoreException {
 
 448 //      if (file.exists()) {
 
 449 //              // Deal with shared output folders... last one wins... no collision cases detected
 
 450 //              if (JavaBuilder.DEBUG)
 
 451 //                      System.out.println("Writing changed class file " + file.getName());//$NON-NLS-1$
 
 452 //              file.setContents(new ByteArrayInputStream(bytes), true, false, null);
 
 453 //              if (!file.isDerived())
 
 454 //                      file.setDerived(true);
 
 456 //              // Default implementation just writes out the bytes for the new class file...
 
 457 //              if (JavaBuilder.DEBUG)
 
 458 //                      System.out.println("Writing new class file " + file.getName());//$NON-NLS-1$
 
 459 //              file.create(new ByteArrayInputStream(bytes), IResource.FORCE, null);
 
 460 //              file.setDerived(true);