Source code: org/eclipse/jdt/internal/core/builder/IncrementalImageBuilder.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.resources.*;
14 import org.eclipse.core.runtime.*;
15
16 import org.eclipse.jdt.core.compiler.*;
17 import org.eclipse.jdt.core.compiler.IProblem;
18 import org.eclipse.jdt.internal.compiler.*;
19 import org.eclipse.jdt.internal.compiler.classfmt.*;
20 import org.eclipse.jdt.internal.compiler.problem.*;
21 import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
22 import org.eclipse.jdt.internal.core.util.SimpleLookupTable;
23 import org.eclipse.jdt.internal.core.util.Util;
24
25 import java.io.*;
26 import java.util.*;
27
28 /**
29 * The incremental image builder
30 */
31 public class IncrementalImageBuilder extends AbstractImageBuilder {
32
33 protected ArrayList sourceFiles;
34 protected ArrayList previousSourceFiles;
35 protected StringSet qualifiedStrings;
36 protected StringSet simpleStrings;
37 protected SimpleLookupTable secondaryTypesToRemove;
38 protected boolean hasStructuralChanges;
39 protected int compileLoop;
40
41 public static int MaxCompileLoop = 5; // perform a full build if it takes more than ? incremental compile loops
42
43 protected IncrementalImageBuilder(JavaBuilder javaBuilder) {
44 super(javaBuilder);
45 this.nameEnvironment.isIncrementalBuild = true;
46 this.newState.copyFrom(javaBuilder.lastState);
47
48 this.sourceFiles = new ArrayList(33);
49 this.previousSourceFiles = null;
50 this.qualifiedStrings = new StringSet(3);
51 this.simpleStrings = new StringSet(3);
52 this.hasStructuralChanges = false;
53 this.compileLoop = 0;
54 }
55
56 public boolean build(SimpleLookupTable deltas) {
57 // initialize builder
58 // walk this project's deltas, find changed source files
59 // walk prereq projects' deltas, find changed class files & add affected source files
60 // use the build state # to skip the deltas for certain prereq projects
61 // ignore changed zip/jar files since they caused a full build
62 // compile the source files & acceptResult()
63 // compare the produced class files against the existing ones on disk
64 // recompile all dependent source files of any type with structural changes or new/removed secondary type
65 // keep a loop counter to abort & perform a full build
66
67 if (JavaBuilder.DEBUG)
68 System.out.println("INCREMENTAL build"); //$NON-NLS-1$
69
70 try {
71 resetCollections();
72
73 notifier.subTask(Util.bind("build.analyzingDeltas")); //$NON-NLS-1$
74 IResourceDelta sourceDelta = (IResourceDelta) deltas.get(javaBuilder.currentProject);
75 if (sourceDelta != null)
76 if (!findSourceFiles(sourceDelta)) return false;
77 notifier.updateProgressDelta(0.10f);
78
79 Object[] keyTable = deltas.keyTable;
80 Object[] valueTable = deltas.valueTable;
81 for (int i = 0, l = valueTable.length; i < l; i++) {
82 IResourceDelta delta = (IResourceDelta) valueTable[i];
83 if (delta != null) {
84 IProject p = (IProject) keyTable[i];
85 ClasspathLocation[] classFoldersAndJars = (ClasspathLocation[]) javaBuilder.binaryLocationsPerProject.get(p);
86 if (classFoldersAndJars != null)
87 if (!findAffectedSourceFiles(delta, classFoldersAndJars, p)) return false;
88 }
89 }
90 notifier.updateProgressDelta(0.10f);
91
92 notifier.subTask(Util.bind("build.analyzingSources")); //$NON-NLS-1$
93 addAffectedSourceFiles();
94 notifier.updateProgressDelta(0.05f);
95
96 this.compileLoop = 0;
97 float increment = 0.40f;
98 while (sourceFiles.size() > 0) { // added to in acceptResult
99 if (++this.compileLoop > MaxCompileLoop) {
100 if (JavaBuilder.DEBUG)
101 System.out.println("ABORTING incremental build... exceeded loop count"); //$NON-NLS-1$
102 return false;
103 }
104 notifier.checkCancel();
105
106 SourceFile[] allSourceFiles = new SourceFile[sourceFiles.size()];
107 sourceFiles.toArray(allSourceFiles);
108 resetCollections();
109
110 workQueue.addAll(allSourceFiles);
111 notifier.setProgressPerCompilationUnit(increment / allSourceFiles.length);
112 increment = increment / 2;
113 compile(allSourceFiles);
114 removeSecondaryTypes();
115 addAffectedSourceFiles();
116 }
117 if (this.hasStructuralChanges && javaBuilder.javaProject.hasCycleMarker())
118 javaBuilder.mustPropagateStructuralChanges();
119 } catch (AbortIncrementalBuildException e) {
120 // abort the incremental build and let the batch builder handle the problem
121 if (JavaBuilder.DEBUG)
122 System.out.println("ABORTING incremental build... problem with " + e.qualifiedTypeName + //$NON-NLS-1$
123 ". Likely renamed inside its existing source file."); //$NON-NLS-1$
124 return false;
125 } catch (CoreException e) {
126 throw internalException(e);
127 } finally {
128 cleanUp();
129 }
130 return true;
131 }
132
133 protected void addAffectedSourceFiles() {
134 if (qualifiedStrings.elementSize == 0 && simpleStrings.elementSize == 0) return;
135
136 // the qualifiedStrings are of the form 'p1/p2' & the simpleStrings are just 'X'
137 char[][][] qualifiedNames = ReferenceCollection.internQualifiedNames(qualifiedStrings);
138 // if a well known qualified name was found then we can skip over these
139 if (qualifiedNames.length < qualifiedStrings.elementSize)
140 qualifiedNames = null;
141 char[][] simpleNames = ReferenceCollection.internSimpleNames(simpleStrings);
142 // if a well known name was found then we can skip over these
143 if (simpleNames.length < simpleStrings.elementSize)
144 simpleNames = null;
145
146 Object[] keyTable = newState.references.keyTable;
147 Object[] valueTable = newState.references.valueTable;
148 next : for (int i = 0, l = valueTable.length; i < l; i++) {
149 ReferenceCollection refs = (ReferenceCollection) valueTable[i];
150 if (refs != null && refs.includes(qualifiedNames, simpleNames)) {
151 String typeLocator = (String) keyTable[i];
152 IFile file = javaBuilder.currentProject.getFile(typeLocator);
153 if (file.exists()) {
154 ClasspathMultiDirectory md = sourceLocations[0];
155 if (sourceLocations.length > 1) {
156 IPath sourceFileFullPath = file.getFullPath();
157 for (int j = 0, m = sourceLocations.length; j < m; j++) {
158 if (sourceLocations[j].sourceFolder.getFullPath().isPrefixOf(sourceFileFullPath)) {
159 md = sourceLocations[j];
160 if (md.exclusionPatterns == null && md.inclusionPatterns == null)
161 break;
162 if (!Util.isExcluded(file, md.inclusionPatterns, md.exclusionPatterns))
163 break;
164 }
165 }
166 }
167 SourceFile sourceFile = new SourceFile(file, md);
168 if (sourceFiles.contains(sourceFile)) continue next;
169 if (compiledAllAtOnce && previousSourceFiles != null && previousSourceFiles.contains(sourceFile))
170 continue next; // can skip previously compiled files since already saw hierarchy related problems
171
172 if (JavaBuilder.DEBUG)
173 System.out.println(" adding affected source file " + typeLocator); //$NON-NLS-1$
174 sourceFiles.add(sourceFile);
175 }
176 }
177 }
178 }
179
180 protected void addDependentsOf(IPath path, boolean isStructuralChange) {
181 if (isStructuralChange && !this.hasStructuralChanges) {
182 newState.tagAsStructurallyChanged();
183 this.hasStructuralChanges = true;
184 }
185 // the qualifiedStrings are of the form 'p1/p2' & the simpleStrings are just 'X'
186 path = path.setDevice(null);
187 String packageName = path.removeLastSegments(1).toString();
188 qualifiedStrings.add(packageName);
189 String typeName = path.lastSegment();
190 int memberIndex = typeName.indexOf('$');
191 if (memberIndex > 0)
192 typeName = typeName.substring(0, memberIndex);
193 if (simpleStrings.add(typeName) && JavaBuilder.DEBUG)
194 System.out.println(" will look for dependents of " //$NON-NLS-1$
195 + typeName + " in " + packageName); //$NON-NLS-1$
196 }
197
198 protected void cleanUp() {
199 super.cleanUp();
200
201 this.sourceFiles = null;
202 this.previousSourceFiles = null;
203 this.qualifiedStrings = null;
204 this.simpleStrings = null;
205 this.secondaryTypesToRemove = null;
206 this.hasStructuralChanges = false;
207 this.compileLoop = 0;
208 }
209
210 protected boolean findAffectedSourceFiles(IResourceDelta delta, ClasspathLocation[] classFoldersAndJars, IProject prereqProject) {
211 for (int i = 0, l = classFoldersAndJars.length; i < l; i++) {
212 ClasspathLocation bLocation = classFoldersAndJars[i];
213 // either a .class file folder or a zip/jar file
214 if (bLocation != null) { // skip unchanged output folder
215 IPath p = bLocation.getProjectRelativePath();
216 if (p != null) {
217 IResourceDelta binaryDelta = delta.findMember(p);
218 if (binaryDelta != null) {
219 if (bLocation instanceof ClasspathJar) {
220 if (JavaBuilder.DEBUG)
221 System.out.println("ABORTING incremental build... found delta to jar/zip file"); //$NON-NLS-1$
222 return false; // do full build since jar file was changed (added/removed were caught as classpath change)
223 }
224 if (binaryDelta.getKind() == IResourceDelta.ADDED || binaryDelta.getKind() == IResourceDelta.REMOVED) {
225 if (JavaBuilder.DEBUG)
226 System.out.println("ABORTING incremental build... found added/removed binary folder"); //$NON-NLS-1$
227 return false; // added/removed binary folder should not make it here (classpath change), but handle anyways
228 }
229 int segmentCount = binaryDelta.getFullPath().segmentCount();
230 IResourceDelta[] children = binaryDelta.getAffectedChildren(); // .class files from class folder
231 StringSet structurallyChangedTypes = null;
232 if (bLocation.isOutputFolder())
233 structurallyChangedTypes = this.newState.getStructurallyChangedTypes(javaBuilder.getLastState(prereqProject));
234 for (int j = 0, m = children.length; j < m; j++)
235 findAffectedSourceFiles(children[j], segmentCount, structurallyChangedTypes);
236 notifier.checkCancel();
237 }
238 }
239 }
240 }
241 return true;
242 }
243
244 protected void findAffectedSourceFiles(IResourceDelta binaryDelta, int segmentCount, StringSet structurallyChangedTypes) {
245 // When a package becomes a type or vice versa, expect 2 deltas,
246 // one on the folder & one on the class file
247 IResource resource = binaryDelta.getResource();
248 switch(resource.getType()) {
249 case IResource.FOLDER :
250 switch (binaryDelta.getKind()) {
251 case IResourceDelta.ADDED :
252 case IResourceDelta.REMOVED :
253 IPath packagePath = resource.getFullPath().removeFirstSegments(segmentCount);
254 String packageName = packagePath.toString();
255 if (binaryDelta.getKind() == IResourceDelta.ADDED) {
256 // see if any known source file is from the same package... classpath already includes new package
257 if (!newState.isKnownPackage(packageName)) {
258 if (JavaBuilder.DEBUG)
259 System.out.println("Found added package " + packageName); //$NON-NLS-1$
260 addDependentsOf(packagePath, false);
261 return;
262 }
263 if (JavaBuilder.DEBUG)
264 System.out.println("Skipped dependents of added package " + packageName); //$NON-NLS-1$
265 } else {
266 // see if the package still exists on the classpath
267 if (!nameEnvironment.isPackage(packageName)) {
268 if (JavaBuilder.DEBUG)
269 System.out.println("Found removed package " + packageName); //$NON-NLS-1$
270 addDependentsOf(packagePath, false);
271 return;
272 }
273 if (JavaBuilder.DEBUG)
274 System.out.println("Skipped dependents of removed package " + packageName); //$NON-NLS-1$
275 }
276 // fall thru & traverse the sub-packages and .class files
277 case IResourceDelta.CHANGED :
278 IResourceDelta[] children = binaryDelta.getAffectedChildren();
279 for (int i = 0, l = children.length; i < l; i++)
280 findAffectedSourceFiles(children[i], segmentCount, structurallyChangedTypes);
281 }
282 return;
283 case IResource.FILE :
284 if (org.eclipse.jdt.internal.compiler.util.Util.isClassFileName(resource.getName())) {
285 IPath typePath = resource.getFullPath().removeFirstSegments(segmentCount).removeFileExtension();
286 switch (binaryDelta.getKind()) {
287 case IResourceDelta.ADDED :
288 case IResourceDelta.REMOVED :
289 if (JavaBuilder.DEBUG)
290 System.out.println("Found added/removed class file " + typePath); //$NON-NLS-1$
291 addDependentsOf(typePath, false);
292 return;
293 case IResourceDelta.CHANGED :
294 if ((binaryDelta.getFlags() & IResourceDelta.CONTENT) == 0)
295 return; // skip it since it really isn't changed
296 if (structurallyChangedTypes != null && !structurallyChangedTypes.includes(typePath.toString()))
297 return; // skip since it wasn't a structural change
298 if (JavaBuilder.DEBUG)
299 System.out.println("Found changed class file " + typePath); //$NON-NLS-1$
300 addDependentsOf(typePath, false);
301 }
302 return;
303 }
304 }
305 }
306
307 protected boolean findSourceFiles(IResourceDelta delta) throws CoreException {
308 for (int i = 0, l = sourceLocations.length; i < l; i++) {
309 ClasspathMultiDirectory md = sourceLocations[i];
310 if (md.sourceFolder.equals(javaBuilder.currentProject)) {
311 // skip nested source & output folders when the project is a source folder
312 int segmentCount = delta.getFullPath().segmentCount();
313 IResourceDelta[] children = delta.getAffectedChildren();
314 for (int j = 0, m = children.length; j < m; j++)
315 if (!isExcludedFromProject(children[j].getFullPath()))
316 findSourceFiles(children[j], md, segmentCount);
317 } else {
318 IResourceDelta sourceDelta = delta.findMember(md.sourceFolder.getProjectRelativePath());
319 if (sourceDelta != null) {
320 if (sourceDelta.getKind() == IResourceDelta.REMOVED) {
321 if (JavaBuilder.DEBUG)
322 System.out.println("ABORTING incremental build... found removed source folder"); //$NON-NLS-1$
323 return false; // removed source folder should not make it here, but handle anyways (ADDED is supported)
324 }
325 int segmentCount = sourceDelta.getFullPath().segmentCount();
326 IResourceDelta[] children = sourceDelta.getAffectedChildren();
327 try {
328 for (int j = 0, m = children.length; j < m; j++)
329 findSourceFiles(children[j], md, segmentCount);
330 } catch (org.eclipse.core.internal.resources.ResourceException e) {
331 // catch the case that a package has been renamed and collides on disk with an as-yet-to-be-deleted package
332 if (e.getStatus().getCode() == IResourceStatus.CASE_VARIANT_EXISTS) {
333 if (JavaBuilder.DEBUG)
334 System.out.println("ABORTING incremental build... found renamed package"); //$NON-NLS-1$
335 return false;
336 }
337 throw e; // rethrow
338 }
339 }
340 }
341 notifier.checkCancel();
342 }
343 return true;
344 }
345
346 protected void findSourceFiles(IResourceDelta sourceDelta, ClasspathMultiDirectory md, int segmentCount) throws CoreException {
347 // When a package becomes a type or vice versa, expect 2 deltas,
348 // one on the folder & one on the source file
349 IResource resource = sourceDelta.getResource();
350 // remember that if inclusion & exclusion patterns change then a full build is done
351 boolean isExcluded = (md.exclusionPatterns != null || md.inclusionPatterns != null)
352 && Util.isExcluded(resource, md.inclusionPatterns, md.exclusionPatterns);
353 switch(resource.getType()) {
354 case IResource.FOLDER :
355 if (isExcluded && md.inclusionPatterns == null)
356 return; // no need to go further with this delta since its children cannot be included
357
358 switch (sourceDelta.getKind()) {
359 case IResourceDelta.ADDED :
360 if (!isExcluded) {
361 IPath addedPackagePath = resource.getFullPath().removeFirstSegments(segmentCount);
362 createFolder(addedPackagePath, md.binaryFolder); // ensure package exists in the output folder
363 // add dependents even when the package thinks it exists to be on the safe side
364 if (JavaBuilder.DEBUG)
365 System.out.println("Found added package " + addedPackagePath); //$NON-NLS-1$
366 addDependentsOf(addedPackagePath, true);
367 }
368 // fall thru & collect all the source files
369 case IResourceDelta.CHANGED :
370 IResourceDelta[] children = sourceDelta.getAffectedChildren();
371 for (int i = 0, l = children.length; i < l; i++)
372 findSourceFiles(children[i], md, segmentCount);
373 return;
374 case IResourceDelta.REMOVED :
375 if (isExcluded) {
376 // since this folder is excluded then there is nothing to delete (from this md), but must walk any included subfolders
377 children = sourceDelta.getAffectedChildren();
378 for (int i = 0, l = children.length; i < l; i++)
379 findSourceFiles(children[i], md, segmentCount);
380 return;
381 }
382 IPath removedPackagePath = resource.getFullPath().removeFirstSegments(segmentCount);
383 if (sourceLocations.length > 1) {
384 for (int i = 0, l = sourceLocations.length; i < l; i++) {
385 if (sourceLocations[i].sourceFolder.getFolder(removedPackagePath).exists()) {
386 // only a package fragment was removed, same as removing multiple source files
387 createFolder(removedPackagePath, md.binaryFolder); // ensure package exists in the output folder
388 IResourceDelta[] removedChildren = sourceDelta.getAffectedChildren();
389 for (int j = 0, m = removedChildren.length; j < m; j++)
390 findSourceFiles(removedChildren[j], md, segmentCount);
391 return;
392 }
393 }
394 }
395 IFolder removedPackageFolder = md.binaryFolder.getFolder(removedPackagePath);
396 if (removedPackageFolder.exists())
397 removedPackageFolder.delete(IResource.FORCE, null);
398 // add dependents even when the package thinks it does not exist to be on the safe side
399 if (JavaBuilder.DEBUG)
400 System.out.println("Found removed package " + removedPackagePath); //$NON-NLS-1$
401 addDependentsOf(removedPackagePath, true);
402 newState.removePackage(sourceDelta);
403 }
404 return;
405 case IResource.FILE :
406 if (isExcluded) return;
407
408 String resourceName = resource.getName();
409 if (org.eclipse.jdt.internal.compiler.util.Util.isJavaFileName(resourceName)) {
410 IPath typePath = resource.getFullPath().removeFirstSegments(segmentCount).removeFileExtension();
411 String typeLocator = resource.getProjectRelativePath().toString();
412 switch (sourceDelta.getKind()) {
413 case IResourceDelta.ADDED :
414 if (JavaBuilder.DEBUG)
415 System.out.println("Compile this added source file " + typeLocator); //$NON-NLS-1$
416 sourceFiles.add(new SourceFile((IFile) resource, md, true));
417 String typeName = typePath.toString();
418 if (!newState.isDuplicateLocator(typeName, typeLocator)) { // adding dependents results in 2 duplicate errors
419 if (JavaBuilder.DEBUG)
420 System.out.println("Found added source file " + typeName); //$NON-NLS-1$
421 addDependentsOf(typePath, true);
422 }
423 return;
424 case IResourceDelta.REMOVED :
425 char[][] definedTypeNames = newState.getDefinedTypeNamesFor(typeLocator);
426 if (definedTypeNames == null) { // defined a single type matching typePath
427 removeClassFile(typePath, md.binaryFolder);
428 if ((sourceDelta.getFlags() & IResourceDelta.MOVED_TO) != 0) {
429 // remove problems and tasks for a compilation unit that is being moved (to another package or renamed)
430 // if the target file is a compilation unit, the new cu will be recompiled
431 // if the target file is a non-java resource, then markers are removed
432 // see bug 2857
433 IResource movedFile = javaBuilder.workspaceRoot.getFile(sourceDelta.getMovedToPath());
434 JavaBuilder.removeProblemsAndTasksFor(movedFile);
435 }
436 } else {
437 if (JavaBuilder.DEBUG)
438 System.out.println("Found removed source file " + typePath.toString()); //$NON-NLS-1$
439 addDependentsOf(typePath, true); // add dependents of the source file since it may be involved in a name collision
440 if (definedTypeNames.length > 0) { // skip it if it failed to successfully define a type
441 IPath packagePath = typePath.removeLastSegments(1);
442 for (int i = 0, l = definedTypeNames.length; i < l; i++)
443 removeClassFile(packagePath.append(new String(definedTypeNames[i])), md.binaryFolder);
444 }
445 }
446 newState.removeLocator(typeLocator);
447 return;
448 case IResourceDelta.CHANGED :
449 if ((sourceDelta.getFlags() & IResourceDelta.CONTENT) == 0
450 && (sourceDelta.getFlags() & IResourceDelta.ENCODING) == 0)
451 return; // skip it since it really isn't changed
452 if (JavaBuilder.DEBUG)
453 System.out.println("Compile this changed source file " + typeLocator); //$NON-NLS-1$
454 sourceFiles.add(new SourceFile((IFile) resource, md, true));
455 }
456 return;
457 } else if (org.eclipse.jdt.internal.compiler.util.Util.isClassFileName(resourceName)) {
458 return; // skip class files
459 } else if (md.hasIndependentOutputFolder) {
460 if (javaBuilder.filterExtraResource(resource)) return;
461
462 // copy all other resource deltas to the output folder
463 IPath resourcePath = resource.getFullPath().removeFirstSegments(segmentCount);
464 IResource outputFile = md.binaryFolder.getFile(resourcePath);
465 switch (sourceDelta.getKind()) {
466 case IResourceDelta.ADDED :
467 if (outputFile.exists()) {
468 if (JavaBuilder.DEBUG)
469 System.out.println("Deleting existing file " + resourcePath); //$NON-NLS-1$
470 outputFile.delete(IResource.FORCE, null);
471 }
472 if (JavaBuilder.DEBUG)
473 System.out.println("Copying added file " + resourcePath); //$NON-NLS-1$
474 createFolder(resourcePath.removeLastSegments(1), md.binaryFolder); // ensure package exists in the output folder
475 resource.copy(outputFile.getFullPath(), IResource.FORCE, null);
476 outputFile.setDerived(true);
477 outputFile.setReadOnly(false); // just in case the original was read only
478 return;
479 case IResourceDelta.REMOVED :
480 if (outputFile.exists()) {
481 if (JavaBuilder.DEBUG)
482 System.out.println("Deleting removed file " + resourcePath); //$NON-NLS-1$
483 outputFile.delete(IResource.FORCE, null);
484 }
485 return;
486 case IResourceDelta.CHANGED :
487 if ((sourceDelta.getFlags() & IResourceDelta.CONTENT) == 0
488 && (sourceDelta.getFlags() & IResourceDelta.ENCODING) == 0)
489 return; // skip it since it really isn't changed
490 if (outputFile.exists()) {
491 if (JavaBuilder.DEBUG)
492 System.out.println("Deleting existing file " + resourcePath); //$NON-NLS-1$
493 outputFile.delete(IResource.FORCE, null);
494 }
495 if (JavaBuilder.DEBUG)
496 System.out.println("Copying changed file " + resourcePath); //$NON-NLS-1$
497 createFolder(resourcePath.removeLastSegments(1), md.binaryFolder); // ensure package exists in the output folder
498 resource.copy(outputFile.getFullPath(), IResource.FORCE, null);
499 outputFile.setDerived(true);
500 outputFile.setReadOnly(false); // just in case the original was read only
501 }
502 return;
503 }
504 }
505 }
506
507 protected void finishedWith(String sourceLocator, CompilationResult result, char[] mainTypeName, ArrayList definedTypeNames, ArrayList duplicateTypeNames) {
508 char[][] previousTypeNames = newState.getDefinedTypeNamesFor(sourceLocator);
509 if (previousTypeNames == null)
510 previousTypeNames = new char[][] {mainTypeName};
511 IPath packagePath = null;
512 next : for (int i = 0, l = previousTypeNames.length; i < l; i++) {
513 char[] previous = previousTypeNames[i];
514 for (int j = 0, m = definedTypeNames.size(); j < m; j++)
515 if (CharOperation.equals(previous, (char[]) definedTypeNames.get(j)))
516 continue next;
517
518 SourceFile sourceFile = (SourceFile) result.getCompilationUnit();
519 if (packagePath == null) {
520 int count = sourceFile.sourceLocation.sourceFolder.getFullPath().segmentCount();
521 packagePath = sourceFile.resource.getFullPath().removeFirstSegments(count).removeLastSegments(1);
522 }
523 if (secondaryTypesToRemove == null)
524 this.secondaryTypesToRemove = new SimpleLookupTable();
525 ArrayList types = (ArrayList) secondaryTypesToRemove.get(sourceFile.sourceLocation.binaryFolder);
526 if (types == null)
527 types = new ArrayList(definedTypeNames.size());
528 types.add(packagePath.append(new String(previous)));
529 secondaryTypesToRemove.put(sourceFile.sourceLocation.binaryFolder, types);
530 }
531 super.finishedWith(sourceLocator, result, mainTypeName, definedTypeNames, duplicateTypeNames);
532 }
533
534 protected void removeClassFile(IPath typePath, IContainer outputFolder) throws CoreException {
535 if (typePath.lastSegment().indexOf('$') == -1) { // is not a nested type
536 newState.removeQualifiedTypeName(typePath.toString());
537 // add dependents even when the type thinks it does not exist to be on the safe side
538 if (JavaBuilder.DEBUG)
539 System.out.println("Found removed type " + typePath); //$NON-NLS-1$
540 addDependentsOf(typePath, true); // when member types are removed, their enclosing type is structurally changed
541 }
542 IFile classFile = outputFolder.getFile(typePath.addFileExtension(SuffixConstants.EXTENSION_class));
543 if (classFile.exists()) {
544 if (JavaBuilder.DEBUG)
545 System.out.println("Deleting class file of removed type " + typePath); //$NON-NLS-1$
546 classFile.delete(IResource.FORCE, null);
547 }
548 }
549
550 protected void removeSecondaryTypes() throws CoreException {
551 if (secondaryTypesToRemove != null) { // delayed deleting secondary types until the end of the compile loop
552 Object[] keyTable = secondaryTypesToRemove.keyTable;
553 Object[] valueTable = secondaryTypesToRemove.valueTable;
554 for (int i = 0, l = keyTable.length; i < l; i++) {
555 IContainer outputFolder = (IContainer) keyTable[i];
556 if (outputFolder != null) {
557 ArrayList paths = (ArrayList) valueTable[i];
558 for (int j = 0, m = paths.size(); j < m; j++)
559 removeClassFile((IPath) paths.get(j), outputFolder);
560 }
561 }
562 this.secondaryTypesToRemove = null;
563 if (previousSourceFiles != null && previousSourceFiles.size() > 1)
564 this.previousSourceFiles = null; // cannot optimize recompile case when a secondary type is deleted
565 }
566 }
567
568 protected void resetCollections() {
569 previousSourceFiles = sourceFiles.isEmpty() ? null : (ArrayList) sourceFiles.clone();
570
571 sourceFiles.clear();
572 qualifiedStrings.clear();
573 simpleStrings.clear();
574 workQueue.clear();
575 }
576
577 protected void updateProblemsFor(SourceFile sourceFile, CompilationResult result) throws CoreException {
578 IMarker[] markers = JavaBuilder.getProblemsFor(sourceFile.resource);
579 IProblem[] problems = result.getProblems();
580 if (problems == null && markers.length == 0) return;
581
582 notifier.updateProblemCounts(markers, problems);
583 JavaBuilder.removeProblemsFor(sourceFile.resource);
584 storeProblemsFor(sourceFile, problems);
585 }
586
587 protected void updateTasksFor(SourceFile sourceFile, CompilationResult result) throws CoreException {
588 IMarker[] markers = JavaBuilder.getTasksFor(sourceFile.resource);
589 IProblem[] tasks = result.getTasks();
590 if (tasks == null && markers.length == 0) return;
591
592 JavaBuilder.removeTasksFor(sourceFile.resource);
593 storeTasksFor(sourceFile, tasks);
594 }
595
596 protected void writeClassFileBytes(byte[] bytes, IFile file, String qualifiedFileName, boolean isSecondaryType, boolean updateClassFile) throws CoreException {
597 // Before writing out the class file, compare it to the previous file
598 // If structural changes occured then add dependent source files
599 if (file.exists()) {
600 if (writeClassFileCheck(file, qualifiedFileName, bytes) || updateClassFile) { // see 46093
601 if (JavaBuilder.DEBUG)
602 System.out.println("Writing changed class file " + file.getName());//$NON-NLS-1$
603 file.setContents(new ByteArrayInputStream(bytes), true, false, null);
604 if (!file.isDerived())
605 file.setDerived(true);
606 } else if (JavaBuilder.DEBUG) {
607 System.out.println("Skipped over unchanged class file " + file.getName());//$NON-NLS-1$
608 }
609 } else {
610 if (isSecondaryType)
611 addDependentsOf(new Path(qualifiedFileName), true); // new secondary type
612 if (JavaBuilder.DEBUG)
613 System.out.println("Writing new class file " + file.getName());//$NON-NLS-1$
614 try {
615 file.create(new ByteArrayInputStream(bytes), IResource.FORCE, null);
616 } catch (org.eclipse.core.internal.resources.ResourceException e) {
617 if (e.getStatus().getCode() == IResourceStatus.CASE_VARIANT_EXISTS)
618 // catch the case that a nested type has been renamed and collides on disk with an as-yet-to-be-deleted type
619 throw new AbortCompilation(true, new AbortIncrementalBuildException(qualifiedFileName));
620 throw e; // rethrow
621 }
622 file.setDerived(true);
623 }
624 }
625
626 protected boolean writeClassFileCheck(IFile file, String fileName, byte[] newBytes) throws CoreException {
627 try {
628 byte[] oldBytes = Util.getResourceContentsAsByteArray(file);
629 notEqual : if (newBytes.length == oldBytes.length) {
630 for (int i = newBytes.length; --i >= 0;)
631 if (newBytes[i] != oldBytes[i]) break notEqual;
632 return false; // bytes are identical so skip them
633 }
634 IPath location = file.getLocation();
635 if (location == null) return false; // unable to determine location of this class file
636 ClassFileReader reader = new ClassFileReader(oldBytes, location.toString().toCharArray());
637 // ignore local types since they're only visible inside a single method
638 if (!(reader.isLocal() || reader.isAnonymous()) && reader.hasStructuralChanges(newBytes)) {
639 if (JavaBuilder.DEBUG)
640 System.out.println("Type has structural changes " + fileName); //$NON-NLS-1$
641 addDependentsOf(new Path(fileName), true);
642 this.newState.wasStructurallyChanged(fileName);
643 }
644 } catch (ClassFormatException e) {
645 addDependentsOf(new Path(fileName), true);
646 this.newState.wasStructurallyChanged(fileName);
647 }
648 return true;
649 }
650
651 public String toString() {
652 return "incremental image builder for:\n\tnew state: " + newState; //$NON-NLS-1$
653 }
654
655
656 /* Debug helper
657
658 static void dump(IResourceDelta delta) {
659 StringBuffer buffer = new StringBuffer();
660 IPath path = delta.getFullPath();
661 for (int i = path.segmentCount(); --i > 0;)
662 buffer.append(" ");
663 switch (delta.getKind()) {
664 case IResourceDelta.ADDED:
665 buffer.append('+');
666 break;
667 case IResourceDelta.REMOVED:
668 buffer.append('-');
669 break;
670 case IResourceDelta.CHANGED:
671 buffer.append('*');
672 break;
673 case IResourceDelta.NO_CHANGE:
674 buffer.append('=');
675 break;
676 default:
677 buffer.append('?');
678 break;
679 }
680 buffer.append(path);
681 System.out.println(buffer.toString());
682 IResourceDelta[] children = delta.getAffectedChildren();
683 for (int i = 0, l = children.length; i < l; i++)
684 dump(children[i]);
685 }
686 */
687 }