/******************************************************************************* * Copyright (c) 2000, 2009 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.internal.core; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaElementDelta; import org.eclipse.jdt.core.IPackageFragment; import org.eclipse.jdt.core.IPackageFragmentRoot; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.internal.compiler.util.ObjectVector; import org.eclipse.jdt.internal.core.JavaModelManager.PerProjectInfo; import org.eclipse.jdt.internal.core.search.indexing.IndexManager; import org.eclipse.jdt.internal.core.util.Util; public class ClasspathChange { public static final int NO_DELTA= 0x00; public static final int HAS_DELTA= 0x01; public static final int HAS_PROJECT_CHANGE= 0x02; public static final int HAS_LIBRARY_CHANGE= 0x04; JavaProject project; IClasspathEntry[] oldRawClasspath; IPath oldOutputLocation; IClasspathEntry[] oldResolvedClasspath; public ClasspathChange(JavaProject project, IClasspathEntry[] oldRawClasspath, IPath oldOutputLocation, IClasspathEntry[] oldResolvedClasspath) { this.project= project; this.oldRawClasspath= oldRawClasspath; this.oldOutputLocation= oldOutputLocation; this.oldResolvedClasspath= oldResolvedClasspath; } private void addClasspathDeltas(JavaElementDelta delta, IPackageFragmentRoot[] roots, int flag) { for (int i= 0; i < roots.length; i++) { IPackageFragmentRoot root= roots[i]; delta.changed(root, flag); if ((flag & IJavaElementDelta.F_REMOVED_FROM_CLASSPATH) != 0 || (flag & IJavaElementDelta.F_SOURCEATTACHED) != 0 || (flag & IJavaElementDelta.F_SOURCEDETACHED) != 0) { try { root.close(); } catch (JavaModelException e) { // ignore } } } } /* * Returns the index of the item in the list if the given list contains the specified entry. If the list does * not contain the entry, -1 is returned. */ private int classpathContains(IClasspathEntry[] list, IClasspathEntry entry) { IPath[] exclusionPatterns= entry.getExclusionPatterns(); IPath[] inclusionPatterns= entry.getInclusionPatterns(); int listLen= list == null ? 0 : list.length; nextEntry: for (int i= 0; i < listLen; i++) { IClasspathEntry other= list[i]; if (other.getContentKind() == entry.getContentKind() && other.getEntryKind() == entry.getEntryKind() && other.isExported() == entry.isExported() && other.getPath().equals(entry.getPath())) { // check custom outputs IPath entryOutput= entry.getOutputLocation(); IPath otherOutput= other.getOutputLocation(); if (entryOutput == null) { if (otherOutput != null) continue; } else { if (!entryOutput.equals(otherOutput)) continue; } // check inclusion patterns IPath[] otherIncludes= other.getInclusionPatterns(); if (inclusionPatterns != otherIncludes) { if (inclusionPatterns == null) continue; int includeLength= inclusionPatterns.length; if (otherIncludes == null || otherIncludes.length != includeLength) continue; for (int j= 0; j < includeLength; j++) { // compare toStrings instead of IPaths // since IPath.equals is specified to ignore trailing separators if (!inclusionPatterns[j].toString().equals(otherIncludes[j].toString())) continue nextEntry; } } // check exclusion patterns IPath[] otherExcludes= other.getExclusionPatterns(); if (exclusionPatterns != otherExcludes) { if (exclusionPatterns == null) continue; int excludeLength= exclusionPatterns.length; if (otherExcludes == null || otherExcludes.length != excludeLength) continue; for (int j= 0; j < excludeLength; j++) { // compare toStrings instead of IPaths // since IPath.equals is specified to ignore trailing separators if (!exclusionPatterns[j].toString().equals(otherExcludes[j].toString())) continue nextEntry; } } return i; } } return -1; } /* * Recursively adds all subfolders of <code>folder</code> to the given collection. */ private void collectAllSubfolders(IFolder folder, ArrayList collection) throws JavaModelException { try { IResource[] members= folder.members(); for (int i= 0, max= members.length; i < max; i++) { IResource r= members[i]; if (r.getType() == IResource.FOLDER) { collection.add(r); collectAllSubfolders((IFolder)r, collection); } } } catch (CoreException e) { throw new JavaModelException(e); } } /* * Returns a collection of package fragments that have been added/removed * as the result of changing the output location to/from the given * location. The collection is empty if no package fragments are * affected. */ private ArrayList determineAffectedPackageFragments(IPath location) throws JavaModelException { ArrayList fragments= new ArrayList(); // see if this will cause any package fragments to be affected IWorkspace workspace= ResourcesPlugin.getWorkspace(); IResource resource= null; if (location != null) { resource= workspace.getRoot().findMember(location); } if (resource != null && resource.getType() == IResource.FOLDER) { IFolder folder= (IFolder)resource; // only changes if it actually existed IClasspathEntry[] classpath= this.project.getExpandedClasspath(); for (int i= 0; i < classpath.length; i++) { IClasspathEntry entry= classpath[i]; IPath path= classpath[i].getPath(); if (entry.getEntryKind() != IClasspathEntry.CPE_PROJECT && path.isPrefixOf(location) && !path.equals(location)) { IPackageFragmentRoot[] roots= this.project.computePackageFragmentRoots(classpath[i]); PackageFragmentRoot root= (PackageFragmentRoot)roots[0]; // now the output location becomes a package fragment - along with any subfolders ArrayList folders= new ArrayList(); folders.add(folder); collectAllSubfolders(folder, folders); Iterator elements= folders.iterator(); int segments= path.segmentCount(); while (elements.hasNext()) { IFolder f= (IFolder)elements.next(); IPath relativePath= f.getFullPath().removeFirstSegments(segments); String[] pkgName= relativePath.segments(); IPackageFragment pkg= root.getPackageFragment(pkgName); if (!Util.isExcluded(pkg)) fragments.add(pkg); } } } } return fragments; } public boolean equals(Object obj) { if (!(obj instanceof ClasspathChange)) return false; return this.project.equals(((ClasspathChange)obj).project); } /* * Generates a classpath change delta for this classpath change. * Returns whether a delta was generated, and whether project reference have changed. */ public int generateDelta(JavaElementDelta delta, boolean addClasspathChange) { JavaModelManager manager= JavaModelManager.getJavaModelManager(); DeltaProcessingState state= manager.deltaState; if (state.findJavaProject(this.project.getElementName()) == null) // project doesn't exist yet (we're in an IWorkspaceRunnable) // no need to create a delta here and no need to index (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=133334) // the delta processor will create an ADDED project delta, and index the project return NO_DELTA; DeltaProcessor deltaProcessor= state.getDeltaProcessor(); IClasspathEntry[] newResolvedClasspath= null; IPath newOutputLocation= null; int result= NO_DELTA; try { PerProjectInfo perProjectInfo= this.project.getPerProjectInfo(); // get new info this.project.resolveClasspath(perProjectInfo, false/*don't use previous session values*/, addClasspathChange); IClasspathEntry[] newRawClasspath; // use synchronized block to ensure consistency synchronized (perProjectInfo) { newRawClasspath= perProjectInfo.rawClasspath; newResolvedClasspath= perProjectInfo.getResolvedClasspath(); newOutputLocation= perProjectInfo.outputLocation; } if (newResolvedClasspath == null) { // another thread reset the resolved classpath, use a temporary PerProjectInfo PerProjectInfo temporaryInfo= this.project.newTemporaryInfo(); this.project.resolveClasspath(temporaryInfo, false/*don't use previous session values*/, addClasspathChange); newRawClasspath= temporaryInfo.rawClasspath; newResolvedClasspath= temporaryInfo.getResolvedClasspath(); newOutputLocation= temporaryInfo.outputLocation; } // check if raw classpath has changed if (this.oldRawClasspath != null && !JavaProject.areClasspathsEqual(this.oldRawClasspath, newRawClasspath, this.oldOutputLocation, newOutputLocation)) { delta.changed(this.project, IJavaElementDelta.F_CLASSPATH_CHANGED); result|= HAS_DELTA; // reset containers that are no longer on the classpath // (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=139446) for (int i= 0, length= this.oldRawClasspath.length; i < length; i++) { IClasspathEntry entry= this.oldRawClasspath[i]; if (entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) { if (classpathContains(newRawClasspath, entry) == -1) manager.containerPut(this.project, entry.getPath(), null); } } } // if no changes to resolved classpath, nothing more to do if (this.oldResolvedClasspath != null && JavaProject.areClasspathsEqual(this.oldResolvedClasspath, newResolvedClasspath, this.oldOutputLocation, newOutputLocation)) return result; // close cached info this.project.close(); // ensure caches of dependent projects are reset as well (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=207890) deltaProcessor.projectCachesToReset.add(this.project); } catch (JavaModelException e) { if (DeltaProcessor.VERBOSE) { e.printStackTrace(); } // project no longer exist return result; } if (this.oldResolvedClasspath == null) return result; delta.changed(this.project, IJavaElementDelta.F_RESOLVED_CLASSPATH_CHANGED); result|= HAS_DELTA; state.addForRefresh(this.project); // ensure external jars are refreshed for this project (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=212769 ) Map removedRoots= null; IPackageFragmentRoot[] roots= null; Map allOldRoots; if ((allOldRoots= deltaProcessor.oldRoots) != null) { roots= (IPackageFragmentRoot[])allOldRoots.get(this.project); } if (roots != null) { removedRoots= new HashMap(); for (int i= 0; i < roots.length; i++) { IPackageFragmentRoot root= roots[i]; removedRoots.put(root.getPath(), root); } } int newLength= newResolvedClasspath.length; int oldLength= this.oldResolvedClasspath.length; for (int i= 0; i < oldLength; i++) { int index= classpathContains(newResolvedClasspath, this.oldResolvedClasspath[i]); if (index == -1) { // remote project changes int entryKind= this.oldResolvedClasspath[i].getEntryKind(); if (entryKind == IClasspathEntry.CPE_PROJECT) { result|= HAS_PROJECT_CHANGE; continue; } if (entryKind == IClasspathEntry.CPE_LIBRARY) { result|= HAS_LIBRARY_CHANGE; } PackageFragmentRoot[] pkgFragmentRoots= null; if (removedRoots != null) { PackageFragmentRoot oldRoot= (PackageFragmentRoot)removedRoots.get(this.oldResolvedClasspath[i].getPath()); if (oldRoot != null) { // use old root if any (could be none if entry wasn't bound) pkgFragmentRoots= new PackageFragmentRoot[] { oldRoot }; } } if (pkgFragmentRoots == null) { try { ObjectVector accumulatedRoots= new ObjectVector(); HashSet rootIDs= new HashSet(5); rootIDs.add(this.project.rootID()); this.project.computePackageFragmentRoots( this.oldResolvedClasspath[i], accumulatedRoots, rootIDs, null, // inside original project false, // don't retrieve exported roots null); /*no reverse map*/ pkgFragmentRoots= new PackageFragmentRoot[accumulatedRoots.size()]; accumulatedRoots.copyInto(pkgFragmentRoots); } catch (JavaModelException e) { pkgFragmentRoots= new PackageFragmentRoot[] {}; } } addClasspathDeltas(delta, pkgFragmentRoots, IJavaElementDelta.F_REMOVED_FROM_CLASSPATH); } else { // remote project changes if (this.oldResolvedClasspath[i].getEntryKind() == IClasspathEntry.CPE_PROJECT) { result|= HAS_PROJECT_CHANGE; continue; } if (index != i) { //reordering of the classpath addClasspathDeltas(delta, this.project.computePackageFragmentRoots(this.oldResolvedClasspath[i]), IJavaElementDelta.F_REORDER); } // check source attachment IPath newSourcePath= newResolvedClasspath[index].getSourceAttachmentPath(); int sourceAttachmentFlags= getSourceAttachmentDeltaFlag(this.oldResolvedClasspath[i].getSourceAttachmentPath(), newSourcePath); IPath oldRootPath= this.oldResolvedClasspath[i].getSourceAttachmentRootPath(); IPath newRootPath= newResolvedClasspath[index].getSourceAttachmentRootPath(); int sourceAttachmentRootFlags= getSourceAttachmentDeltaFlag(oldRootPath, newRootPath); int flags= sourceAttachmentFlags | sourceAttachmentRootFlags; if (flags != 0) { addClasspathDeltas(delta, this.project.computePackageFragmentRoots(this.oldResolvedClasspath[i]), flags); } else { if (oldRootPath == null && newRootPath == null) { // if source path is specified and no root path, it needs to be recomputed dynamically // force detach source on jar package fragment roots (source will be lazily computed when needed) IPackageFragmentRoot[] computedRoots= this.project.computePackageFragmentRoots(this.oldResolvedClasspath[i]); for (int j= 0; j < computedRoots.length; j++) { IPackageFragmentRoot root= computedRoots[j]; // force detach source on jar package fragment roots (source will be lazily computed when needed) try { root.close(); } catch (JavaModelException e) { // ignore } } } } } } for (int i= 0; i < newLength; i++) { int index= classpathContains(this.oldResolvedClasspath, newResolvedClasspath[i]); if (index == -1) { // remote project changes int entryKind= newResolvedClasspath[i].getEntryKind(); if (entryKind == IClasspathEntry.CPE_PROJECT) { result|= HAS_PROJECT_CHANGE; continue; } if (entryKind == IClasspathEntry.CPE_LIBRARY) { result|= HAS_LIBRARY_CHANGE; } addClasspathDeltas(delta, this.project.computePackageFragmentRoots(newResolvedClasspath[i]), IJavaElementDelta.F_ADDED_TO_CLASSPATH); } // classpath reordering has already been generated in previous loop } // see if a change in output location will cause any package fragments to be added/removed if ((newOutputLocation == null && this.oldOutputLocation != null) || (newOutputLocation != null && !newOutputLocation.equals(this.oldOutputLocation))) { try { ArrayList added= determineAffectedPackageFragments(this.oldOutputLocation); Iterator iter= added.iterator(); while (iter.hasNext()) { IPackageFragment frag= (IPackageFragment)iter.next(); ((IPackageFragmentRoot)frag.getParent()).close(); delta.added(frag); } // see if this will cause any package fragments to be removed ArrayList removed= determineAffectedPackageFragments(newOutputLocation); iter= removed.iterator(); while (iter.hasNext()) { IPackageFragment frag= (IPackageFragment)iter.next(); ((IPackageFragmentRoot)frag.getParent()).close(); delta.removed(frag); } } catch (JavaModelException e) { if (DeltaProcessor.VERBOSE) e.printStackTrace(); } } return result; } /* * Returns the source attachment flag for the delta between the 2 give source paths. * Returns either F_SOURCEATTACHED, F_SOURCEDETACHED, F_SOURCEATTACHED | F_SOURCEDETACHED * or 0 if there is no difference. */ private int getSourceAttachmentDeltaFlag(IPath oldPath, IPath newPath) { if (oldPath == null) { if (newPath != null) { return IJavaElementDelta.F_SOURCEATTACHED; } else { return 0; } } else if (newPath == null) { return IJavaElementDelta.F_SOURCEDETACHED; } else if (!oldPath.equals(newPath)) { return IJavaElementDelta.F_SOURCEATTACHED | IJavaElementDelta.F_SOURCEDETACHED; } else { return 0; } } public int hashCode() { return this.project.hashCode(); } /* * Request the indexing of entries that have been added, and remove the index for removed entries. */ public void requestIndexing() { IClasspathEntry[] newResolvedClasspath= null; try { newResolvedClasspath= this.project.getResolvedClasspath(); } catch (JavaModelException e) { // project doesn't exist return; } JavaModelManager manager= JavaModelManager.getJavaModelManager(); IndexManager indexManager= manager.indexManager; if (indexManager == null) return; DeltaProcessingState state= manager.deltaState; int newLength= newResolvedClasspath.length; int oldLength= this.oldResolvedClasspath == null ? 0 : this.oldResolvedClasspath.length; for (int i= 0; i < oldLength; i++) { int index= classpathContains(newResolvedClasspath, this.oldResolvedClasspath[i]); if (index == -1) { // remote projects are not indexed in this project if (this.oldResolvedClasspath[i].getEntryKind() == IClasspathEntry.CPE_PROJECT) { continue; } // Remove the .java files from the index for a source folder // For a lib folder or a .jar file, remove the corresponding index if not shared. IClasspathEntry oldEntry= this.oldResolvedClasspath[i]; final IPath path= oldEntry.getPath(); int changeKind= this.oldResolvedClasspath[i].getEntryKind(); switch (changeKind) { case IClasspathEntry.CPE_SOURCE: char[][] inclusionPatterns= ((ClasspathEntry)oldEntry).fullInclusionPatternChars(); char[][] exclusionPatterns= ((ClasspathEntry)oldEntry).fullExclusionPatternChars(); indexManager.removeSourceFolderFromIndex(this.project, path, inclusionPatterns, exclusionPatterns); break; case IClasspathEntry.CPE_LIBRARY: if (state.otherRoots.get(path) == null) { // if root was not shared indexManager.discardJobs(path.toString()); indexManager.removeIndex(path); // TODO (kent) we could just remove the in-memory index and have the indexing check for timestamps } break; } } } for (int i= 0; i < newLength; i++) { int index= classpathContains(this.oldResolvedClasspath, newResolvedClasspath[i]); if (index == -1) { // remote projects are not indexed in this project if (newResolvedClasspath[i].getEntryKind() == IClasspathEntry.CPE_PROJECT) { continue; } // Request indexing int entryKind= newResolvedClasspath[i].getEntryKind(); switch (entryKind) { case IClasspathEntry.CPE_LIBRARY: boolean pathHasChanged= true; IPath newPath= newResolvedClasspath[i].getPath(); for (int j= 0; j < oldLength; j++) { IClasspathEntry oldEntry= this.oldResolvedClasspath[j]; if (oldEntry.getPath().equals(newPath)) { pathHasChanged= false; break; } } if (pathHasChanged) { indexManager.indexLibrary(newPath, this.project.getProject()); } break; case IClasspathEntry.CPE_SOURCE: IClasspathEntry entry= newResolvedClasspath[i]; IPath path= entry.getPath(); char[][] inclusionPatterns= ((ClasspathEntry)entry).fullInclusionPatternChars(); char[][] exclusionPatterns= ((ClasspathEntry)entry).fullExclusionPatternChars(); indexManager.indexSourceFolder(this.project, path, inclusionPatterns, exclusionPatterns); break; } } } } public String toString() { return "ClasspathChange: " + this.project.getElementName(); //$NON-NLS-1$ } }