| /******************************************************************************* |
| * Copyright (c) 2000, 2004 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Common Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/cpl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.core.builder; |
| |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.core.resources.*; |
| |
| import org.eclipse.jdt.core.*; |
| import org.eclipse.jdt.core.compiler.*; |
| import org.eclipse.jdt.core.compiler.IProblem; |
| import org.eclipse.jdt.internal.compiler.*; |
| import org.eclipse.jdt.internal.compiler.ClassFile; |
| import org.eclipse.jdt.internal.compiler.Compiler; |
| import org.eclipse.jdt.internal.compiler.problem.*; |
| import org.eclipse.jdt.internal.compiler.util.SuffixConstants; |
| import org.eclipse.jdt.internal.core.util.Util; |
| |
| import java.io.*; |
| import java.util.*; |
| |
| /** |
| * The abstract superclass of Java builders. |
| * Provides the building and compilation mechanism |
| * in common with the batch and incremental builders. |
| */ |
| public abstract class AbstractImageBuilder implements ICompilerRequestor { |
| |
| protected JavaBuilder javaBuilder; |
| protected State newState; |
| |
| // local copies |
| protected NameEnvironment nameEnvironment; |
| protected ClasspathMultiDirectory[] sourceLocations; |
| protected BuildNotifier notifier; |
| |
| protected Compiler compiler; |
| protected WorkQueue workQueue; |
| protected ArrayList problemSourceFiles; |
| protected boolean compiledAllAtOnce; |
| |
| private boolean inCompiler; |
| |
| public static int MAX_AT_ONCE = 1000; |
| |
| protected AbstractImageBuilder(JavaBuilder javaBuilder) { |
| this.javaBuilder = javaBuilder; |
| this.newState = new State(javaBuilder); |
| |
| // local copies |
| this.nameEnvironment = javaBuilder.nameEnvironment; |
| this.sourceLocations = this.nameEnvironment.sourceLocations; |
| this.notifier = javaBuilder.notifier; |
| |
| this.compiler = newCompiler(); |
| this.workQueue = new WorkQueue(); |
| this.problemSourceFiles = new ArrayList(3); |
| } |
| |
| public void acceptResult(CompilationResult result) { |
| // In Batch mode, we write out the class files, hold onto the dependency info |
| // & additional types and report problems. |
| |
| // In Incremental mode, when writing out a class file we need to compare it |
| // against the previous file, remembering if structural changes occured. |
| // Before reporting the new problems, we need to update the problem count & |
| // remove the old problems. Plus delete additional class files that no longer exist. |
| |
| SourceFile compilationUnit = (SourceFile) result.getCompilationUnit(); // go directly back to the sourceFile |
| if (!workQueue.isCompiled(compilationUnit)) { |
| workQueue.finished(compilationUnit); |
| |
| try { |
| updateProblemsFor(compilationUnit, result); // record compilation problems before potentially adding duplicate errors |
| updateTasksFor(compilationUnit, result); // record tasks |
| } catch (CoreException e) { |
| throw internalException(e); |
| } |
| |
| if (result.hasInconsistentToplevelHierarchies) |
| // ensure that this file is always retrieved from source for the rest of the build |
| if (!problemSourceFiles.contains(compilationUnit)) |
| problemSourceFiles.add(compilationUnit); |
| |
| String typeLocator = compilationUnit.typeLocator(); |
| ClassFile[] classFiles = result.getClassFiles(); |
| int length = classFiles.length; |
| ArrayList duplicateTypeNames = null; |
| ArrayList definedTypeNames = new ArrayList(length); |
| for (int i = 0; i < length; i++) { |
| ClassFile classFile = classFiles[i]; |
| |
| char[][] compoundName = classFile.getCompoundName(); |
| char[] typeName = compoundName[compoundName.length - 1]; |
| boolean isNestedType = classFile.enclosingClassFile != null; |
| |
| // Look for a possible collision, if one exists, report an error but do not write the class file |
| if (isNestedType) { |
| String qualifiedTypeName = new String(classFile.outerMostEnclosingClassFile().fileName()); |
| if (newState.isDuplicateLocator(qualifiedTypeName, typeLocator)) |
| continue; |
| } else { |
| String qualifiedTypeName = new String(classFile.fileName()); // the qualified type name "p1/p2/A" |
| if (newState.isDuplicateLocator(qualifiedTypeName, typeLocator)) { |
| if (duplicateTypeNames == null) |
| duplicateTypeNames = new ArrayList(); |
| duplicateTypeNames.add(compoundName); |
| IType type = null; |
| try { |
| type = javaBuilder.javaProject.findType(qualifiedTypeName.replace('/', '.')); |
| } catch (JavaModelException e) { |
| // ignore |
| } |
| createProblemFor(compilationUnit.resource, type, Util.bind("build.duplicateClassFile", new String(typeName)), JavaCore.ERROR); //$NON-NLS-1$ |
| continue; |
| } |
| newState.recordLocatorForType(qualifiedTypeName, typeLocator); |
| } |
| try { |
| definedTypeNames.add(writeClassFile(classFile, compilationUnit, !isNestedType)); |
| } catch (CoreException e) { |
| Util.log(e, "JavaBuilder handling CoreException"); //$NON-NLS-1$ |
| if (e.getStatus().getCode() == IResourceStatus.CASE_VARIANT_EXISTS) |
| createProblemFor(compilationUnit.resource, null, Util.bind("build.classFileCollision", e.getMessage()), JavaCore.ERROR); //$NON-NLS-1$ |
| else |
| createProblemFor(compilationUnit.resource, null, Util.bind("build.inconsistentClassFile"), JavaCore.ERROR); //$NON-NLS-1$ |
| } |
| } |
| finishedWith(typeLocator, result, compilationUnit.getMainTypeName(), definedTypeNames, duplicateTypeNames); |
| notifier.compiled(compilationUnit); |
| } |
| } |
| |
| protected void cleanUp() { |
| this.nameEnvironment.cleanup(); |
| |
| this.javaBuilder = null; |
| this.nameEnvironment = null; |
| this.sourceLocations = null; |
| this.notifier = null; |
| this.compiler = null; |
| this.workQueue = null; |
| this.problemSourceFiles = null; |
| } |
| |
| /* Compile the given elements, adding more elements to the work queue |
| * if they are affected by the changes. |
| */ |
| protected void compile(SourceFile[] units) { |
| int unitsLength = units.length; |
| |
| this.compiledAllAtOnce = unitsLength <= MAX_AT_ONCE; |
| if (this.compiledAllAtOnce) { |
| // do them all now |
| if (JavaBuilder.DEBUG) |
| for (int i = 0; i < unitsLength; i++) |
| System.out.println("About to compile " + units[i].typeLocator()); //$NON-NLS-1$ |
| compile(units, null); |
| } else { |
| int i = 0; |
| boolean compilingFirstGroup = true; |
| while (i < unitsLength) { |
| int doNow = unitsLength < MAX_AT_ONCE ? unitsLength : MAX_AT_ONCE; |
| int index = 0; |
| SourceFile[] toCompile = new SourceFile[doNow]; |
| while (i < unitsLength && index < doNow) { |
| // Although it needed compiling when this method was called, it may have |
| // already been compiled when it was referenced by another unit. |
| SourceFile unit = units[i++]; |
| if (compilingFirstGroup || workQueue.isWaiting(unit)) { |
| if (JavaBuilder.DEBUG) |
| System.out.println("About to compile " + unit.typeLocator()); //$NON-NLS-1$ |
| toCompile[index++] = unit; |
| } |
| } |
| if (index < doNow) |
| System.arraycopy(toCompile, 0, toCompile = new SourceFile[index], 0, index); |
| SourceFile[] additionalUnits = new SourceFile[unitsLength - i]; |
| System.arraycopy(units, i, additionalUnits, 0, additionalUnits.length); |
| compilingFirstGroup = false; |
| compile(toCompile, additionalUnits); |
| } |
| } |
| } |
| |
| void compile(SourceFile[] units, SourceFile[] additionalUnits) { |
| if (units.length == 0) return; |
| notifier.aboutToCompile(units[0]); // just to change the message |
| |
| // extend additionalFilenames with all hierarchical problem types found during this entire build |
| if (!problemSourceFiles.isEmpty()) { |
| int toAdd = problemSourceFiles.size(); |
| int length = additionalUnits == null ? 0 : additionalUnits.length; |
| if (length == 0) |
| additionalUnits = new SourceFile[toAdd]; |
| else |
| System.arraycopy(additionalUnits, 0, additionalUnits = new SourceFile[length + toAdd], 0, length); |
| for (int i = 0; i < toAdd; i++) |
| additionalUnits[length + i] = (SourceFile) problemSourceFiles.get(i); |
| } |
| String[] initialTypeNames = new String[units.length]; |
| for (int i = 0, l = units.length; i < l; i++) |
| initialTypeNames[i] = units[i].initialTypeName; |
| nameEnvironment.setNames(initialTypeNames, additionalUnits); |
| notifier.checkCancel(); |
| try { |
| inCompiler = true; |
| compiler.compile(units); |
| } catch (AbortCompilation ignored) { |
| // ignore the AbortCompilcation coming from BuildNotifier.checkCancelWithinCompiler() |
| // the Compiler failed after the user has chose to cancel... likely due to an OutOfMemory error |
| } finally { |
| inCompiler = false; |
| } |
| // Check for cancel immediately after a compile, because the compiler may |
| // have been cancelled but without propagating the correct exception |
| notifier.checkCancel(); |
| } |
| |
| protected void createProblemFor(IResource resource, IMember javaElement, String message, String problemSeverity) { |
| try { |
| IMarker marker = resource.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER); |
| int severity = problemSeverity.equals(JavaCore.WARNING) ? IMarker.SEVERITY_WARNING : IMarker.SEVERITY_ERROR; |
| |
| ISourceRange range = javaElement == null ? null : javaElement.getNameRange(); |
| int start = range == null ? 0 : range.getOffset(); |
| int end = range == null ? 1 : start + range.getLength(); |
| marker.setAttributes( |
| new String[] {IMarker.MESSAGE, IMarker.SEVERITY, IMarker.CHAR_START, IMarker.CHAR_END}, |
| new Object[] {message, new Integer(severity), new Integer(start), new Integer(end)}); |
| } catch (CoreException e) { |
| throw internalException(e); |
| } |
| } |
| |
| protected void finishedWith(String sourceLocator, CompilationResult result, char[] mainTypeName, ArrayList definedTypeNames, ArrayList duplicateTypeNames) { |
| if (duplicateTypeNames == null) { |
| newState.record(sourceLocator, result.qualifiedReferences, result.simpleNameReferences, mainTypeName, definedTypeNames); |
| return; |
| } |
| |
| char[][][] qualifiedRefs = result.qualifiedReferences; |
| char[][] simpleRefs = result.simpleNameReferences; |
| // for each duplicate type p1.p2.A, add the type name A (package was already added) |
| next : for (int i = 0, l = duplicateTypeNames.size(); i < l; i++) { |
| char[][] compoundName = (char[][]) duplicateTypeNames.get(i); |
| char[] typeName = compoundName[compoundName.length - 1]; |
| int sLength = simpleRefs.length; |
| for (int j = 0; j < sLength; j++) |
| if (CharOperation.equals(simpleRefs[j], typeName)) |
| continue next; |
| System.arraycopy(simpleRefs, 0, simpleRefs = new char[sLength + 1][], 0, sLength); |
| simpleRefs[sLength] = typeName; |
| } |
| newState.record(sourceLocator, qualifiedRefs, simpleRefs, mainTypeName, definedTypeNames); |
| } |
| |
| protected IContainer createFolder(IPath packagePath, IContainer outputFolder) throws CoreException { |
| if (packagePath.isEmpty()) return outputFolder; |
| IFolder folder = outputFolder.getFolder(packagePath); |
| if (!folder.exists()) { |
| createFolder(packagePath.removeLastSegments(1), outputFolder); |
| folder.create(true, true, null); |
| folder.setDerived(true); |
| } |
| return folder; |
| } |
| |
| protected RuntimeException internalException(CoreException t) { |
| ImageBuilderInternalException imageBuilderException = new ImageBuilderInternalException(t); |
| if (inCompiler) |
| return new AbortCompilation(true, imageBuilderException); |
| return imageBuilderException; |
| } |
| |
| protected Compiler newCompiler() { |
| // called once when the builder is initialized... can override if needed |
| Compiler newCompiler = new Compiler( |
| nameEnvironment, |
| DefaultErrorHandlingPolicies.proceedWithAllProblems(), |
| javaBuilder.javaProject.getOptions(true), |
| this, |
| ProblemFactory.getProblemFactory(Locale.getDefault())); |
| // enable the compiler reference info support |
| newCompiler.options.produceReferenceInfo = true; |
| |
| org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment env = newCompiler.lookupEnvironment; |
| synchronized (env) { |
| // enable shared byte[]'s used by ClassFile to avoid allocating MBs during a build |
| env.sharedArraysUsed = false; |
| env.sharedClassFileHeader = new byte[30000]; |
| env.sharedClassFileContents = new byte[30000]; |
| } |
| |
| return newCompiler; |
| } |
| |
| protected boolean isExcludedFromProject(IPath childPath) throws JavaModelException { |
| // answer whether the folder should be ignored when walking the project as a source folder |
| if (childPath.segmentCount() > 2) return false; // is a subfolder of a package |
| |
| for (int j = 0, k = sourceLocations.length; j < k; j++) { |
| if (childPath.equals(sourceLocations[j].binaryFolder.getFullPath())) return true; |
| if (childPath.equals(sourceLocations[j].sourceFolder.getFullPath())) return true; |
| } |
| // skip default output folder which may not be used by any source folder |
| return childPath.equals(javaBuilder.javaProject.getOutputLocation()); |
| } |
| |
| /** |
| * Creates a marker from each problem and adds it to the resource. |
| * The marker is as follows: |
| * - its type is T_PROBLEM |
| * - its plugin ID is the JavaBuilder's plugin ID |
| * - its message is the problem's message |
| * - its priority reflects the severity of the problem |
| * - its range is the problem's range |
| * - it has an extra attribute "ID" which holds the problem's id |
| */ |
| protected void storeProblemsFor(SourceFile sourceFile, IProblem[] problems) throws CoreException { |
| if (sourceFile == null || problems == null || problems.length == 0) return; |
| |
| String missingClassFile = null; |
| IResource resource = sourceFile.resource; |
| for (int i = 0, l = problems.length; i < l; i++) { |
| IProblem problem = problems[i]; |
| int id = problem.getID(); |
| if (id == IProblem.IsClassPathCorrect) { |
| JavaBuilder.removeProblemsAndTasksFor(javaBuilder.currentProject); // make this the only problem for this project |
| String[] args = problem.getArguments(); |
| missingClassFile = args[0]; |
| } |
| |
| if (id != IProblem.Task) { |
| IMarker marker = resource.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER); |
| marker.setAttributes( |
| new String[] { |
| IMarker.MESSAGE, |
| IMarker.SEVERITY, |
| IJavaModelMarker.ID, |
| IMarker.CHAR_START, |
| IMarker.CHAR_END, |
| IMarker.LINE_NUMBER, |
| IJavaModelMarker.ARGUMENTS}, |
| new Object[] { |
| problem.getMessage(), |
| new Integer(problem.isError() ? IMarker.SEVERITY_ERROR : IMarker.SEVERITY_WARNING), |
| new Integer(id), |
| new Integer(problem.getSourceStart()), |
| new Integer(problem.getSourceEnd() + 1), |
| new Integer(problem.getSourceLineNumber()), |
| Util.getProblemArgumentsForMarker(problem.getArguments()) |
| }); |
| } |
| |
| /* Do NOT want to populate the Java Model just to find the matching Java element. |
| * Also cannot query compilation units located in folders with invalid package |
| * names such as 'a/b.c.d/e'. |
| |
| // compute a user-friendly location |
| IJavaElement element = JavaCore.create(resource); |
| if (element instanceof org.eclipse.jdt.core.ICompilationUnit) { // try to find a finer grain element |
| org.eclipse.jdt.core.ICompilationUnit unit = (org.eclipse.jdt.core.ICompilationUnit) element; |
| IJavaElement fragment = unit.getElementAt(problem.getSourceStart()); |
| if (fragment != null) element = fragment; |
| } |
| String location = null; |
| if (element instanceof JavaElement) |
| location = ((JavaElement) element).readableName(); |
| if (location != null) |
| marker.setAttribute(IMarker.LOCATION, location); |
| */ |
| |
| if (missingClassFile != null) |
| throw new MissingClassFileException(missingClassFile); |
| } |
| } |
| |
| protected void storeTasksFor(SourceFile sourceFile, IProblem[] tasks) throws CoreException { |
| if (sourceFile == null || tasks == null || tasks.length == 0) return; |
| |
| IResource resource = sourceFile.resource; |
| for (int i = 0, l = tasks.length; i < l; i++) { |
| IProblem task = tasks[i]; |
| if (task.getID() == IProblem.Task) { |
| IMarker marker = resource.createMarker(IJavaModelMarker.TASK_MARKER); |
| int priority = IMarker.PRIORITY_NORMAL; |
| String compilerPriority = task.getArguments()[2]; |
| if (JavaCore.COMPILER_TASK_PRIORITY_HIGH.equals(compilerPriority)) |
| priority = IMarker.PRIORITY_HIGH; |
| else if (JavaCore.COMPILER_TASK_PRIORITY_LOW.equals(compilerPriority)) |
| priority = IMarker.PRIORITY_LOW; |
| marker.setAttributes( |
| new String[] { |
| IMarker.MESSAGE, |
| IMarker.PRIORITY, |
| IMarker.DONE, |
| IMarker.CHAR_START, |
| IMarker.CHAR_END, |
| IMarker.LINE_NUMBER, |
| IMarker.USER_EDITABLE, |
| }, |
| new Object[] { |
| task.getMessage(), |
| new Integer(priority), |
| org.eclipse.jdt.internal.compiler.util.Util.toBoolean(false), |
| new Integer(task.getSourceStart()), |
| new Integer(task.getSourceEnd() + 1), |
| new Integer(task.getSourceLineNumber()), |
| new Boolean(false), |
| }); |
| } |
| } |
| } |
| |
| protected void updateProblemsFor(SourceFile sourceFile, CompilationResult result) throws CoreException { |
| IProblem[] problems = result.getProblems(); |
| if (problems == null || problems.length == 0) return; |
| |
| notifier.updateProblemCounts(problems); |
| storeProblemsFor(sourceFile, problems); |
| } |
| |
| protected void updateTasksFor(SourceFile sourceFile, CompilationResult result) throws CoreException { |
| IProblem[] tasks = result.getTasks(); |
| if (tasks == null || tasks.length == 0) return; |
| |
| storeTasksFor(sourceFile, tasks); |
| } |
| |
| protected char[] writeClassFile(ClassFile classFile, SourceFile compilationUnit, boolean isSecondaryType) throws CoreException { |
| String fileName = new String(classFile.fileName()); // the qualified type name "p1/p2/A" |
| IPath filePath = new Path(fileName); |
| IContainer outputFolder = compilationUnit.sourceLocation.binaryFolder; |
| IContainer container = outputFolder; |
| if (filePath.segmentCount() > 1) { |
| container = createFolder(filePath.removeLastSegments(1), outputFolder); |
| filePath = new Path(filePath.lastSegment()); |
| } |
| |
| IFile file = container.getFile(filePath.addFileExtension(SuffixConstants.EXTENSION_class)); |
| writeClassFileBytes(classFile.getBytes(), file, fileName, isSecondaryType, compilationUnit.updateClassFile); |
| if (classFile.ownSharedArrays) { |
| org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment env = this.compiler.lookupEnvironment; |
| synchronized (env) { |
| env.sharedArraysUsed = false; |
| } |
| } |
| |
| // answer the name of the class file as in Y or Y$M |
| return filePath.lastSegment().toCharArray(); |
| } |
| |
| protected void writeClassFileBytes(byte[] bytes, IFile file, String qualifiedFileName, boolean isSecondaryType, boolean updateClassFile) throws CoreException { |
| if (file.exists()) { |
| // Deal with shared output folders... last one wins... no collision cases detected |
| if (JavaBuilder.DEBUG) |
| System.out.println("Writing changed class file " + file.getName());//$NON-NLS-1$ |
| file.setContents(new ByteArrayInputStream(bytes), true, false, null); |
| if (!file.isDerived()) |
| file.setDerived(true); |
| } else { |
| // Default implementation just writes out the bytes for the new class file... |
| if (JavaBuilder.DEBUG) |
| System.out.println("Writing new class file " + file.getName());//$NON-NLS-1$ |
| file.create(new ByteArrayInputStream(bytes), IResource.FORCE, null); |
| file.setDerived(true); |
| } |
| } |
| } |