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