Bug 20158

Summary: Close and reopen a project does not remove errors
Product: [Eclipse Project] JDT Reporter: David Audel <david_audel>
Component: CoreAssignee: Kent Johnson <kent_johnson>
Status: VERIFIED FIXED QA Contact:
Severity: normal    
Priority: P2    
Version: 2.0   
Target Milestone: 2.0 F4   
Hardware: PC   
OS: Windows 2000   
Whiteboard:

Description David Audel CLA 2002-06-13 09:39:51 EDT
1. Create simple project SP1
2. Add x.jar in SP1
3. Create Java project JP2
3. Add SP1/x.jar on JP2's classpath
4. Close SP1
5. Do ctrl+B (errors appears)
6. Open SP1
x.jar is present in package view but there is errors in task view and ctrl+B 
does not remove these errors.
Comment 1 Kent Johnson CLA 2002-06-13 14:58:57 EDT
Instead of deleting SP1's build state when its closed... its being saved 
instead. So when SP1 is reopened, no build is 'needed'... no deltas are 
generated/propagated to its dependent projects.

We should either stop saving a project's build state when its closed or delete 
the state when its reopened.

PM: comments?
Comment 2 Philipe Mulet CLA 2002-06-14 06:20:56 EDT
This wouldn't make a difference if the closed project is a non-java project 
(like in this scenario).

There must be a delta for the prereq project which isn't a Java one which is 
ignored.
Comment 3 Philipe Mulet CLA 2002-06-14 06:34:20 EDT
Indeed, when SP1 is closed, the builder can no longer find SP1/x.jar, which is 
thus removed from the binary resources. Therefore, SP1 is no longer claimed to 
be an interesting prereq project. So when reopening it later, its delta won't 
even be considered.
Comment 4 Philipe Mulet CLA 2002-06-14 06:45:00 EDT
Proposed fix is: NameEnvironment#computeLocations
-----

public static ClasspathLocation[] computeLocations(
	IWorkspaceRoot workspaceRoot,
	IJavaProject javaProject,
	String outputFolderLocation,
	ArrayList sourceFolders,
	SimpleLookupTable binaryResources) throws JavaModelException {

	IClasspathEntry[] classpathEntries = ((JavaProject) 
javaProject).getExpandedClasspath(true, true);
	int cpCount = 0;
	int max = classpathEntries.length;
	ClasspathLocation[] classpathLocations = new ClasspathLocation[max];

	boolean firstSourceFolder = true;
	nextEntry : for (int i = 0; i < max; i++) {
		IClasspathEntry entry = classpathEntries[i];
		IPath path = entry.getPath();
		Object target = JavaModel.getTarget(workspaceRoot, path, true);
		if (target == null){
			if (entry.getEntryKind() == 
IClasspathEntry.CPE_LIBRARY) {
				if (path.segmentCount() > 1) {
					IResource rsc = workspaceRoot.findMember
(path.segment(0));
					if (rsc instanceof IProject && 
rsc.exists()) {
						binaryResources.put((IProject)
rsc, new IResource[0]);
					}
				}
			}
			continue nextEntry;
		}

		if (target instanceof IResource) {
			IResource resource = (IResource) target;
			switch(entry.getEntryKind()) {
				case IClasspathEntry.CPE_SOURCE :
					if (outputFolderLocation == null || !
(resource instanceof IContainer)) continue nextEntry;
					if (sourceFolders != null) { // normal 
builder mode
						sourceFolders.add(resource);
						classpathLocations[cpCount++] =
						
	ClasspathLocation.forSourceFolder(resource.getLocation().toString(), 
outputFolderLocation);
					} else if (firstSourceFolder) { // add 
the output folder only once
						firstSourceFolder = false;
						classpathLocations[cpCount++] = 
ClasspathLocation.forBinaryFolder(outputFolderLocation);
					}
					continue nextEntry;

				case IClasspathEntry.CPE_PROJECT :
					if (!(resource instanceof IProject)) 
continue nextEntry;
					IProject prereqProject = (IProject) 
resource;
					if (!prereqProject.isAccessible()) 
continue nextEntry;
					IPath outputLocation = JavaCore.create
(prereqProject).getOutputLocation();
					IResource prereqOutputFolder;
					if (prereqProject.getFullPath().equals
(outputLocation)) {
						prereqOutputFolder = 
prereqProject;
					} else {
						prereqOutputFolder = 
workspaceRoot.findMember(outputLocation);
						if (prereqOutputFolder == null 
|| !prereqOutputFolder.exists() || !(prereqOutputFolder instanceof IFolder))
							continue nextEntry;
					}
					if (prereqOutputFolder.getLocation() == 
null) // sanity check
						continue nextEntry;
					if (binaryResources != null) { // 
normal builder mode
						IResource[] existingResources = 
(IResource[]) binaryResources.get(prereqProject);
						if (existingResources == null)
							binaryResources.put
(prereqProject, new IResource[] {prereqOutputFolder});
						else
							existingResources[0] = 
prereqOutputFolder; // project's output folder is always first
					}
					classpathLocations[cpCount++] = 
ClasspathLocation.forBinaryFolder(prereqOutputFolder.getLocation().toString());
					continue nextEntry;

				case IClasspathEntry.CPE_LIBRARY :
					if (resource.getLocation() == null) // 
sanity check
						continue nextEntry;
					if (resource instanceof IFile) {
						String extension = 
path.getFileExtension();
						if (!
(JavaBuilder.JAR_EXTENSION.equalsIgnoreCase(extension) || 
JavaBuilder.ZIP_EXTENSION.equalsIgnoreCase(extension)))
							continue nextEntry;
						classpathLocations[cpCount++] = 
ClasspathLocation.forLibrary(resource.getLocation().toString());
					} else if (resource instanceof IFolder) 
{
						classpathLocations[cpCount++] = 
ClasspathLocation.forBinaryFolder(resource.getLocation().toString());
					}
					if (binaryResources != null) { // 
normal builder mode
						IProject p = resource.getProject
(); // can be the project being built
						IResource[] existingResources = 
(IResource[]) binaryResources.get(p);
						if (existingResources == null) {
							existingResources = new 
IResource[] {null, resource}; // project's output folder is always first, null 
if not included
						} else {
							int size = 
existingResources.length;
							System.arraycopy
(existingResources, 0, existingResources = new IResource[size + 1], 0, size);
							existingResources[size] 
= resource;
						}
						binaryResources.put(p, 
existingResources);
					}
					continue nextEntry;
			}
		} else if (target instanceof File) {
			String extension = path.getFileExtension();
			if (!(JavaBuilder.JAR_EXTENSION.equalsIgnoreCase
(extension) || JavaBuilder.ZIP_EXTENSION.equalsIgnoreCase(extension)))
				continue nextEntry;
			classpathLocations[cpCount++] = 
ClasspathLocation.forLibrary(path.toString());
		}
	}
	if (cpCount < max)
		System.arraycopy(classpathLocations, 0, (classpathLocations = 
new ClasspathLocation[cpCount]), 0, cpCount);
	return classpathLocations;
}
Comment 5 Philipe Mulet CLA 2002-06-14 07:52:34 EDT
Actually, my fix shouldn't be necessary, since it should rather be detected as 
a classpath change.
Comment 6 Philipe Mulet CLA 2002-06-14 08:21:04 EDT
I take my previous comment back, the proposed fix is necessary for the Java 
builder do get invoked at all. From thereon, it can react to the change as it 
wants (in this case it will have discarded the original build state when 
aborting the build due to classpath error).
Comment 7 Philipe Mulet CLA 2002-06-14 08:32:36 EDT
Better fix below. Found some suspicious code near management of binaryResources 
for output folder (did not grow existing list if any).

NameEnvironment#computeLocations

[public static ClasspathLocation[] computeLocations(
	IWorkspaceRoot workspaceRoot,
	IJavaProject javaProject,
	String outputFolderLocation,
	ArrayList sourceFolders,
	SimpleLookupTable binaryResources) throws JavaModelException {

	IClasspathEntry[] classpathEntries = ((JavaProject) 
javaProject).getExpandedClasspath(true, true);
	int cpCount = 0;
	int max = classpathEntries.length;
	ClasspathLocation[] classpathLocations = new ClasspathLocation[max];

	boolean firstSourceFolder = true;
	nextEntry : for (int i = 0; i < max; i++) {
		IClasspathEntry entry = classpathEntries[i];
		IPath path = entry.getPath();
		Object target = JavaModel.getTarget(workspaceRoot, path, true);
		if (target == null){
			// still remember a dependency onto the container 
project (20158)
			if (entry.getEntryKind() == 
IClasspathEntry.CPE_LIBRARY) {
				if (path.segmentCount() > 1) {
					IResource rsc = workspaceRoot.findMember
(path.segment(0));
					if (rsc instanceof IProject) {
						binaryResources.put((IProject)
rsc, new IResource[0]);
					}
				}
			}
			continue nextEntry;
		}

		if (target instanceof IResource) {
			IResource resource = (IResource) target;
			switch(entry.getEntryKind()) {
				case IClasspathEntry.CPE_SOURCE :
					if (outputFolderLocation == null || !
(resource instanceof IContainer)) continue nextEntry;
					if (sourceFolders != null) { // normal 
builder mode
						sourceFolders.add(resource);
						classpathLocations[cpCount++] =
						
	ClasspathLocation.forSourceFolder(resource.getLocation().toString(), 
outputFolderLocation);
					} else if (firstSourceFolder) { // add 
the output folder only once
						firstSourceFolder = false;
						classpathLocations[cpCount++] = 
ClasspathLocation.forBinaryFolder(outputFolderLocation);
					}
					continue nextEntry;

				case IClasspathEntry.CPE_PROJECT :
					if (!(resource instanceof IProject)) 
continue nextEntry;
					IProject prereqProject = (IProject) 
resource;
					if (!prereqProject.isAccessible()) 
continue nextEntry;
					IPath outputLocation = JavaCore.create
(prereqProject).getOutputLocation();
					IResource prereqOutputFolder;
					if (prereqProject.getFullPath().equals
(outputLocation)) {
						prereqOutputFolder = 
prereqProject;
					} else {
						prereqOutputFolder = 
workspaceRoot.findMember(outputLocation);
						if (prereqOutputFolder == null 
|| !prereqOutputFolder.exists() || !(prereqOutputFolder instanceof IFolder))
							continue nextEntry;
					}
					if (prereqOutputFolder.getLocation() == 
null) // sanity check
						continue nextEntry;
					if (binaryResources != null) { // 
normal builder mode
						IResource[] existingResources = 
(IResource[]) binaryResources.get(prereqProject);
						if (existingResources == null)
							existingResources = new 
IResource[] {prereqOutputFolder};
						else {
							int size = 
existingResources.length;
							System.arraycopy
(existingResources, 0, existingResources = new IResource[size + 1], 1, size);
							existingResources[0] = 
prereqOutputFolder; // project's output folder is always first
						}
						binaryResources.put
(prereqProject, existingResources);
					}
					classpathLocations[cpCount++] = 
ClasspathLocation.forBinaryFolder(prereqOutputFolder.getLocation().toString());
					continue nextEntry;

				case IClasspathEntry.CPE_LIBRARY :
					if (resource.getLocation() == null) // 
sanity check
						continue nextEntry;
					if (resource instanceof IFile) {
						String extension = 
path.getFileExtension();
						if (!
(JavaBuilder.JAR_EXTENSION.equalsIgnoreCase(extension) || 
JavaBuilder.ZIP_EXTENSION.equalsIgnoreCase(extension)))
							continue nextEntry;
						classpathLocations[cpCount++] = 
ClasspathLocation.forLibrary(resource.getLocation().toString());
					} else if (resource instanceof IFolder) 
{
						classpathLocations[cpCount++] = 
ClasspathLocation.forBinaryFolder(resource.getLocation().toString());
					}
					if (binaryResources != null) { // 
normal builder mode
						IProject p = resource.getProject
(); // can be the project being built
						IResource[] existingResources = 
(IResource[]) binaryResources.get(p);
						if (existingResources == null) {
							existingResources = new 
IResource[] {null, resource}; // project's output folder is always first, null 
if not included
						} else {
							int size = 
existingResources.length;
							System.arraycopy
(existingResources, 0, existingResources = new IResource[size + 1], 0, size);
							existingResources[size] 
= resource;
						}
						binaryResources.put(p, 
existingResources);
					}
					continue nextEntry;
			}
		} else if (target instanceof File) {
			String extension = path.getFileExtension();
			if (!(JavaBuilder.JAR_EXTENSION.equalsIgnoreCase
(extension) || JavaBuilder.ZIP_EXTENSION.equalsIgnoreCase(extension)))
				continue nextEntry;
			classpathLocations[cpCount++] = 
ClasspathLocation.forLibrary(path.toString());
		}
	}
	if (cpCount < max)
		System.arraycopy(classpathLocations, 0, (classpathLocations = 
new ClasspathLocation[cpCount]), 0, cpCount);
	return classpathLocations;
}
]
Comment 8 Philipe Mulet CLA 2002-06-14 08:34:32 EDT
Fixed
Comment 9 Kent Johnson CLA 2002-06-14 11:38:53 EDT
Replaced fix with one on JavaBuilder.getRequiredProjects().

It now searches for Library entries as well as Project entries.
Comment 10 Philipe Mulet CLA 2002-06-14 11:55:16 EDT
Last fix is indeed the most safe. It doesn't interact with the binary resources 
collection, which did work but wasn't as clean as new fix which only changes 
the method for computing prereq project names (which used to use the binary 
resources collection) by simply walking the classpath.

Did both (KJ and PM) write the new fix separately and it was the same 
implementation.

Removing 'FIXED' status, until it gets approved for F4
Comment 11 Philipe Mulet CLA 2002-06-14 11:57:13 EDT
If we don't fix it, then the Java builder wouldn't notice additions/removals of 
internal JARs contained in distinct projects onto which it has no other 
explicit references (as a project prereq on its classpath).

Fix is fairly simple in the end.
Comment 12 Olivier Thomann CLA 2002-06-14 13:11:23 EDT
Verified. It works great.
Comment 13 Jerome Lanneluc CLA 2002-06-17 08:21:42 EDT
No veto. Marking as fixed.
Comment 14 Jerome Lanneluc CLA 2002-06-17 08:25:05 EDT
Verified.