diff options
2 files changed, 349 insertions, 369 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/ProjectState.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/ProjectState.java index 05045a2..dd09355 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/ProjectState.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/ProjectState.java @@ -232,18 +232,25 @@ public final class ProjectState { } public static class LibraryDifference { - public List<LibraryState> removed = new ArrayList<LibraryState>(); + public boolean removed = false; public boolean added = false; public boolean hasDiff() { - return removed.size() > 0 || added; + return removed || added; } } /** * Reloads the content of the properties. - * <p/>This also reset the reference to the target as it may have changed. - * <p/>This should be followed by a call to {@link Sdk#loadTarget(ProjectState)}. + * <p/>This also reset the reference to the target as it may have changed, therefore this + * should be followed by a call to {@link Sdk#loadTarget(ProjectState)}. + * + * <p/>If the project libraries changes, they are updated to a certain extent.<br> + * Removed libraries are removed from the state list, and added to the {@link LibraryDifference} + * object that is returned so that they can be processed.<br> + * Added libraries are added to the state (as new {@link LibraryState} objects), but their + * IProject is not resolved. {@link ProjectState#needs(ProjectState)} should be called + * afterwards to properly initialize the libraries. * * @return an instance of {@link LibraryDifference} describing the change in libraries. */ @@ -295,7 +302,7 @@ public final class ProjectState { } // whatever's left in oldLibraries is removed. - diff.removed.addAll(oldLibraries); + diff.removed = oldLibraries.size() > 0; // update the library with what IProjet are known at the time. updateFullLibraryList(); @@ -595,7 +602,7 @@ public final class ProjectState { * Update the full library list, including indirect dependencies. The result is returned by * {@link #getFullLibraryProjects()}. */ - private void updateFullLibraryList() { + void updateFullLibraryList() { ArrayList<IProject> list = new ArrayList<IProject>(); synchronized (mLibraries) { buildFullLibraryDependencies(mLibraries, list); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java index 02b538e..4d6f784 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java @@ -86,6 +86,7 @@ import java.util.Map.Entry; */ public final class Sdk { private static final String PROP_LIBRARY = "_library"; //$NON-NLS-1$ + private static final String PROP_LIBRARY_NAME = "_library_name"; //$NON-NLS-1$ public static final String CREATOR_ADT = "ADT"; //$NON-NLS-1$ public static final String PROP_CREATOR = "_creator"; //$NON-NLS-1$ private final static Object sLock = new Object(); @@ -744,6 +745,9 @@ public final class Sdk { for (ProjectState projectState : sProjectStateMap.values()) { LibraryState libState = projectState.getLibrary(project); if (libState != null) { + // get the current libraries. + IProject[] oldLibraries = projectState.getFullLibraryProjects(); + // the unlink below will work in the job, but we need to close // the library right away. // This is because in case of a rename of a project, projectClosed and @@ -752,9 +756,13 @@ public final class Sdk { // state up to date. libState.close(); + // edit the project to remove the linked source folder. // this also calls LibraryState.close(); - startActionBundle(new UnlinkLibraryBundle(projectState, project)); + LinkUpdateBundle bundle = getLinkBundle(projectState, oldLibraries); + if (bundle != null) { + queueLinkUpdateBundle(bundle); + } if (projectState.isLibrary()) { updatedLibraries.add(projectState); @@ -821,17 +829,14 @@ public final class Sdk { } } - if (libsToLink.size() > 0) { - // link the libraries to the opened project through the job by adding an - // action bundle to the queue. - LinkLibraryBundle bundle = new LinkLibraryBundle(); - bundle.mProject = openedProject; - bundle.mLibraryProjects = libsToLink.toArray( - new IProject[libsToLink.size()]); - bundle.mPreviousLibraryPath = null; - bundle.mCleanupCPE = true; - startActionBundle(bundle); - } + // create a link bundle always, because even if there's no libraries to add + // to the CPE, the cleaning of invalid CPE must happen. + LinkUpdateBundle bundle = new LinkUpdateBundle(); + bundle.mProject = openedProject; + bundle.mNewLibraryProjects = libsToLink.toArray( + new IProject[libsToLink.size()]); + bundle.mCleanupCPE = true; + queueLinkUpdateBundle(bundle); } // if the project is a library, then add it to the list of projects being opened. @@ -847,6 +852,7 @@ public final class Sdk { } public void projectRenamed(IProject project, IPath from) { + System.out.println("RENAMED: " + project); // a project was renamed. // if the project is a library, look for any project that depended on it // and update it. (default.properties and linked source folder) @@ -865,17 +871,27 @@ public final class Sdk { IPath newRelativePath = makeRelativeTo(project.getFullPath(), projectState.getProject().getFullPath()); + // get the current libraries + IProject[] oldLibraries = projectState.getFullLibraryProjects(); + // update the library for the main project. LibraryState libState = projectState.updateLibrary( oldRelativePath.toString(), newRelativePath.toString(), renamedState); if (libState != null) { - LinkLibraryBundle bundle = new LinkLibraryBundle(); - bundle.mProject = projectState.getProject(); - bundle.mLibraryProjects = new IProject[] { - libState.getProjectState().getProject() }; - bundle.mCleanupCPE = false; - startActionBundle(bundle); + // this project depended on the renamed library, create a bundle + // with the whole library difference (in case the renamed library + // also depends on libraries). + + LinkUpdateBundle bundle = getLinkBundle(projectState, + oldLibraries); + queueLinkUpdateBundle(bundle); + + // add it to the opened projects to update whatever depends + // on it + if (projectState.isLibrary()) { + mOpenedLibraryProjects.add(projectState); + } } } } @@ -903,6 +919,9 @@ public final class Sdk { // get the current library flag boolean wasLibrary = state.isLibrary(); + // get the current list of project dependencies + IProject[] oldLibraries = state.getFullLibraryProjects(); + LibraryDifference diff = state.reloadProperties(); // load the (possibly new) target. @@ -915,45 +934,29 @@ public final class Sdk { // reload the libraries if needed if (diff.hasDiff()) { - for (LibraryState removedState : diff.removed) { - ProjectState removedPState = removedState.getProjectState(); - if (removedPState != null) { - startActionBundle( - new UnlinkLibraryBundle( - state, removedPState.getProject())); - } - } - if (diff.added) { - ArrayList<IProject> libsToLink = new ArrayList<IProject>(); synchronized (sLock) { for (ProjectState projectState : sProjectStateMap.values()) { if (projectState != state) { - LibraryState libState = state.needs(projectState); - - if (libState != null) { - IProject p = libState.getProjectState().getProject(); - if (libsToLink.contains(p) == false) { - libsToLink.add(p); - } - - // now find the dependencies of the library itself. - fillProjectDependenciesList( - libState.getMainProjectState(), libsToLink); - } + // need to call needs to do the libraryState link, + // but no need to look at the result, as we'll compare + // the result of getFullLibraryProjects() + // this is easier to due to indirect dependencies. + state.needs(projectState); } } } + } - if (libsToLink.size() > 0) { - LinkLibraryBundle bundle = new LinkLibraryBundle(); - bundle.mProject = iProject; - bundle.mLibraryProjects = - libsToLink.toArray(new IProject[libsToLink.size()]); - bundle.mPreviousLibraryPath = null; - bundle.mCleanupCPE = false; - startActionBundle(bundle); - } + // and build the real difference. A list of new projects and a list of + // removed project. + // This is not the same as the added/removed libraries because libraries + // could be indirect dependencies through several different direct + // dependencies so it's easier to compare the full lists before and after + // the reload. + LinkUpdateBundle bundle = getLinkBundle(state, oldLibraries); + if (bundle != null) { + queueLinkUpdateBundle(bundle); } } @@ -999,78 +1002,43 @@ public final class Sdk { }; /** - * Action Bundle to be used with {@link Sdk#startActionBundle(ActionBundle)}. - */ - private interface ActionBundle { - enum BundleType { LINK_LIBRARY, UNLINK_LIBRARY }; - BundleType getType(); - IProject getProject(); - }; - - /** - * Action bundle to link libraries to a project. + * Action bundle to update library links on a project. * - * @see Sdk#linkProjectAndLibrary(LinkLibraryBundle, IProgressMonitor) + * @see Sdk#queueLinkUpdateBundle(LinkUpdateBundle) + * @see Sdk#updateLibraryLinks(LinkUpdateBundle, IProgressMonitor) */ - private static class LinkLibraryBundle implements ActionBundle { + private static class LinkUpdateBundle { /** The main project receiving the library links. */ - IProject mProject; - /** The libraries to add to the main project. */ - IProject[] mLibraryProjects; + IProject mProject = null; + /** A list (possibly null/empty) of projects that should be linked. */ + IProject[] mNewLibraryProjects = null; /** an optional old library path that needs to be removed at the same time as the new * libraries are added. Can be <code>null</code> in which case no libraries are removed. */ - IPath mPreviousLibraryPath; + IPath mDeletedLibraryPath = null; + /** A list (possibly null/empty) of projects that should be unlinked */ + IProject[] mRemovedLibraryProjects = null; /** Whether unknown IClasspathEntry (that were flagged as being added by ADT) are to be * removed. This is typically only set to <code>true</code> when the project is opened. */ - boolean mCleanupCPE; - - public BundleType getType() { - return BundleType.LINK_LIBRARY; - } - - public IProject getProject() { - return mProject; - } + boolean mCleanupCPE = false; @Override public String toString() { - return String.format("LinkLibraryBundle: %1$s (%2$s) > %3$s", //$NON-NLS-1$ + return String.format( + "LinkUpdateBundle: %1$s (clean: %2$s) > added: %3$s, removed: %4$s, deleted: %5$s", //$NON-NLS-1$ mProject.getName(), mCleanupCPE, - Arrays.toString(mLibraryProjects)); + Arrays.toString(mNewLibraryProjects), + Arrays.toString(mRemovedLibraryProjects), + mDeletedLibraryPath); } } - /** - * Action bundle to unlink a library from a project. - * - * @see Sdk#unlinkLibrary(UnlinkLibraryBundle, IProgressMonitor) - */ - private static class UnlinkLibraryBundle implements ActionBundle { - /** the main project */ - final ProjectState mProject; - /** the library to remove */ - final IProject mLibrary; - - UnlinkLibraryBundle(ProjectState project, IProject library) { - mProject = project; - mLibrary = library; - } - - public BundleType getType() { - return BundleType.UNLINK_LIBRARY; - } - - public IProject getProject() { - return mProject.getProject(); - } - } - - private final ArrayList<ActionBundle> mActionBundleQueue = new ArrayList<ActionBundle>(); + private final ArrayList<LinkUpdateBundle> mLinkActionBundleQueue = + new ArrayList<LinkUpdateBundle>(); /** - * Runs the given action bundle through a job queue. + * Queues a {@link LinkUpdateBundle} bundle to be run by a job. * * All action bundles are executed in a job in the exact order they are added. * This is convenient when several actions must be executed in a job consecutively (instead @@ -1083,55 +1051,50 @@ public final class Sdk { * * @param bundle the action bundle to execute */ - private void startActionBundle(ActionBundle bundle) { + private void queueLinkUpdateBundle(LinkUpdateBundle bundle) { boolean startJob = false; - synchronized (mActionBundleQueue) { - startJob = mActionBundleQueue.size() == 0; - mActionBundleQueue.add(bundle); + synchronized (mLinkActionBundleQueue) { + startJob = mLinkActionBundleQueue.size() == 0; + mLinkActionBundleQueue.add(bundle); } if (startJob) { - Job job = new Job("Android Library Job") { //$NON-NLS-1$ + Job job = new Job("Android Library Update") { //$NON-NLS-1$ @Override protected IStatus run(IProgressMonitor monitor) { // loop until there's no bundle to process while (true) { // get the bundle, but don't remove until we're done, or a new job could be // started. - ActionBundle bundle = null; - synchronized (mActionBundleQueue) { + LinkUpdateBundle bundle = null; + synchronized (mLinkActionBundleQueue) { // there is always a bundle at this point, as they are only removed // at the end of this method, and the job is only started after adding // one - bundle = mActionBundleQueue.get(0); + bundle = mLinkActionBundleQueue.get(0); } // process the bundle. try { - switch (bundle.getType()) { - case LINK_LIBRARY: - linkProjectAndLibrary((LinkLibraryBundle)bundle, monitor); - break; - case UNLINK_LIBRARY: - unlinkLibrary((UnlinkLibraryBundle) bundle, monitor); - break; - } - - // force a recompile - bundle.getProject().build( - IncrementalProjectBuilder.FULL_BUILD, monitor); - + updateLibraryLinks(bundle, monitor); } catch (Exception e) { AdtPlugin.log(e, "Failed to process bundle: %1$s", //$NON-NLS-1$ bundle.toString()); } + try { + // force a recompile + bundle.mProject.build(IncrementalProjectBuilder.FULL_BUILD, monitor); + } catch (Exception e) { + // no need to log those. + } + // remove it from the list. - synchronized (mActionBundleQueue) { - mActionBundleQueue.remove(0); + synchronized (mLinkActionBundleQueue) { + mLinkActionBundleQueue.remove(0); // no more bundle to process? done. - if (mActionBundleQueue.size() == 0) { + if (mLinkActionBundleQueue.size() == 0) { return Status.OK_STATUS; } } @@ -1249,23 +1212,28 @@ public final class Sdk { } /** - * Links a project and a set of libraries so that the project can use the library code. + * Update the library links for a project * * This does the follow: - * - add the library projects to the main projects dynamic reference list. This is used by - * the builders to receive resource change deltas for library projects and figure out what + * - add/remove the library projects to the main projects dynamic reference list. This is used + * by the builders to receive resource change deltas for library projects and figure out what * needs to be recompiled/recreated. * - create new {@link IClasspathEntry} of type {@link IClasspathEntry#CPE_SOURCE} for each - * source folder for each library project. If there was a previous - * - If {@link LinkLibraryBundle#mCleanupCPE} is set to true, all CPE created by ADT that cannot + * source folder for each new library project. + * - remove the {@link IClasspathEntry} of type {@link IClasspathEntry#CPE_SOURCE} for each + * source folder for each removed library project. + * - If {@link LinkUpdateBundle#mCleanupCPE} is set to true, all CPE created by ADT that cannot * be resolved are removed. This should only be used when the project is opened. * - * @param bundle The {@link LinkLibraryBundle} action bundle that contains all the parameters + * <strong>This must not be called directly. Instead the {@link LinkUpdateBundle} must + * be run through a job with {@link #queueLinkUpdateBundle(LinkUpdateBundle)}.</strong> + * + * @param bundle The {@link LinkUpdateBundle} action bundle that contains all the parameters * necessary to execute the action. * @param monitor an {@link IProgressMonitor}. * @return an {@link IStatus} with the status of the action. */ - private IStatus linkProjectAndLibrary(LinkLibraryBundle bundle, IProgressMonitor monitor) { + private IStatus updateLibraryLinks(LinkUpdateBundle bundle, IProgressMonitor monitor) { if (bundle.mProject.isOpen() == false) { return Status.OK_STATUS; } @@ -1279,28 +1247,34 @@ public final class Sdk { ArrayList<IProject> list = new ArrayList<IProject>(Arrays.asList(refs)); // remove a previous library if needed (in case of a rename) - if (bundle.mPreviousLibraryPath != null) { - final int count = list.size(); - for (int i = 0 ; i < count ; i++) { - // since project basically have only one segment that matter, - // just check the names - if (list.get(i).getName().equals( - bundle.mPreviousLibraryPath.lastSegment())) { - list.remove(i); - break; - } - } + if (bundle.mDeletedLibraryPath != null) { + // since project basically have only one segment that matter, + // just check the names + removeFromList(list, bundle.mDeletedLibraryPath.lastSegment()); + } + if (bundle.mRemovedLibraryProjects != null) { + for (IProject removedProject : bundle.mRemovedLibraryProjects) { + removeFromList(list, removedProject.getName()); + } } - // add the new ones. - list.addAll(Arrays.asList(bundle.mLibraryProjects)); + // add the new ones if they don't exist + if (bundle.mNewLibraryProjects != null) { + for (IProject newProject : bundle.mNewLibraryProjects) { + if (list.contains(newProject) == false) { + list.add(newProject); + } + } + } // set the changed list projectDescription.setDynamicReferences( list.toArray(new IProject[list.size()])); } else { - projectDescription.setDynamicReferences(bundle.mLibraryProjects); + if (bundle.mNewLibraryProjects != null) { + projectDescription.setDynamicReferences(bundle.mNewLibraryProjects); + } } // get the current classpath entries for the project to add the new source @@ -1313,116 +1287,125 @@ public final class Sdk { IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); // loop on the classpath entries and look for CPE_SOURCE entries that - // are linked folders, then record them for comparison layer as we add the new + // are linked folders, then record them for comparison later as we add the new // ones. - ArrayList<IClasspathEntry> libCpeList = new ArrayList<IClasspathEntry>(); - if (bundle.mCleanupCPE) { - for (IClasspathEntry classpathEntry : classpathEntries) { - if (classpathEntry.getEntryKind() == IClasspathEntry.CPE_SOURCE) { - IPath path = classpathEntry.getPath(); - IResource linkedRes = wsRoot.findMember(path); - if (linkedRes != null && linkedRes.isLinked() && - CREATOR_ADT.equals(ProjectHelper.loadStringProperty( - linkedRes, PROP_CREATOR))) { - libCpeList.add(classpathEntry); + ArrayList<IClasspathEntry> cpeToRemove = new ArrayList<IClasspathEntry>(); + for (IClasspathEntry classpathEntry : classpathEntries) { + if (classpathEntry.getEntryKind() == IClasspathEntry.CPE_SOURCE) { + IPath path = classpathEntry.getPath(); + IResource linkedRes = wsRoot.findMember(path); + if (linkedRes != null && linkedRes.isLinked() && + CREATOR_ADT.equals(ProjectHelper.loadStringProperty( + linkedRes, PROP_CREATOR))) { + + // add always to list if we're doing clean-up + if (bundle.mCleanupCPE) { + cpeToRemove.add(classpathEntry); + } else { + String libName = ProjectHelper.loadStringProperty(linkedRes, + PROP_LIBRARY_NAME); + if (libName != null && isRemovedLibrary(bundle, libName)) { + cpeToRemove.add(classpathEntry); + } } } } } // loop on the projects to add. - for (IProject library : bundle.mLibraryProjects) { - if (library.isOpen() == false) { - continue; - } - final String libName = library.getName(); - final String varName = getLibraryVariableName(libName); - - // get the list of source folders for the library. - ArrayList<IPath> sourceFolderPaths = BaseProjectHelper.getSourceClasspaths( - library); - - // loop on all the source folder, ignoring FD_GEN and add them - // as linked folder - for (IPath sourceFolderPath : sourceFolderPaths) { - IResource sourceFolder = wsRoot.findMember(sourceFolderPath); - if (sourceFolder == null || sourceFolder.isLinked()) { - continue; - } - - IPath relativePath = sourceFolder.getProjectRelativePath(); - if (SdkConstants.FD_GEN_SOURCES.equals(relativePath.toString())) { + if (bundle.mNewLibraryProjects != null) { + for (IProject library : bundle.mNewLibraryProjects) { + if (library.isOpen() == false) { continue; } + final String libName = library.getName(); + final String varName = getLibraryVariableName(libName); + + // get the list of source folders for the library. + ArrayList<IPath> sourceFolderPaths = BaseProjectHelper.getSourceClasspaths( + library); + + // loop on all the source folder, ignoring FD_GEN and add them + // as linked folder + for (IPath sourceFolderPath : sourceFolderPaths) { + IResource sourceFolder = wsRoot.findMember(sourceFolderPath); + if (sourceFolder == null || sourceFolder.isLinked()) { + continue; + } - // create the linked path - IPath linkedPath = new Path(varName).append(relativePath); - - // look for an existing linked path - IClasspathEntry match = findClasspathEntryMatch(libCpeList, linkedPath, null); - - if (match == null) { - - // no match, create one - // get a string version, to make up the linked folder name - String srcFolderName = relativePath.toString().replace("/", //$NON-NLS-1$ - "_"); //$NON-NLS-1$ - - // folder name - String folderName = libName + "_" + srcFolderName; //$NON-NLS-1$ + IPath relativePath = sourceFolder.getProjectRelativePath(); + if (SdkConstants.FD_GEN_SOURCES.equals(relativePath.toString())) { + continue; + } - // create a linked resource for the library using the path var. - IFolder libSrc = bundle.mProject.getFolder(folderName); - IPath libSrcPath = libSrc.getFullPath(); + // create the linked path + IPath linkedPath = new Path(varName).append(relativePath); + + // look for an existing CPE that has the same linked path and that was + // going to be removed. + IClasspathEntry match = findClasspathEntryMatch(cpeToRemove, linkedPath, + null); + + if (match == null) { + // no match, create one + // get a string version, to make up the linked folder name + String srcFolderName = relativePath.toString().replace( + "/", //$NON-NLS-1$ + "_"); //$NON-NLS-1$ + + // folder name + String folderName = libName + "_" + srcFolderName; //$NON-NLS-1$ + + // create a linked resource for the library using the path var. + IFolder libSrc = bundle.mProject.getFolder(folderName); + IPath libSrcPath = libSrc.getFullPath(); + + // check if there's a CPE that would conflict, in which case it needs to + // be removed (this can happen for existing CPE that don't match an open + // project) + match = findClasspathEntryMatch(classpathEntries, null/*rawPath*/, + libSrcPath); + if (match != null) { + classpathEntries.remove(match); + } - // check if there's a CPE that would conflict, in which case it needs to - // be removed (this can happen for existing CPE that don't match an open - // project) - match = findClasspathEntryMatch(classpathEntries, null/*rawPath*/, - libSrcPath); - if (match != null) { - classpathEntries.remove(match); + // the path of the linked resource is based on the path variable + // representing the library project, followed by the source folder name. + libSrc.createLink(linkedPath, IResource.REPLACE, monitor); + + // mark it as derived so that Team plug-in ignore this + libSrc.setDerived(true); + + // set some persistent properties on it to know that it was + // created by ADT. + ProjectHelper.saveStringProperty(libSrc, PROP_CREATOR, CREATOR_ADT); + ProjectHelper.saveResourceProperty(libSrc, PROP_LIBRARY, library); + ProjectHelper.saveStringProperty(libSrc, PROP_LIBRARY_NAME, + library.getName()); + + // add the source folder to the classpath entries + classpathEntries.add(JavaCore.newSourceEntry(libSrcPath)); + } else { + // there's a valid match, do nothing, but remove the match from + // the list of previously existing CPE. + cpeToRemove.remove(match); } - - // the path of the linked resource is based on the path variable - // representing the library project, followed by the source folder name. - libSrc.createLink(linkedPath, - IResource.REPLACE, monitor); - - // mark it as derived so that Team plug-in ignore this - libSrc.setDerived(true); - - // set some persistent properties on it to know that it was - // created by ADT. - ProjectHelper.saveStringProperty(libSrc, PROP_CREATOR, CREATOR_ADT); - ProjectHelper.saveResourceProperty(libSrc, PROP_LIBRARY, library); - - // add the source folder to the classpath entries - classpathEntries.add(JavaCore.newSourceEntry(libSrcPath)); - } else { - // there's a valid match, do nothing, but remove the match from - // the list of previously existing CPE. - libCpeList.remove(match); } } } - if (bundle.mCleanupCPE) { - // remove the remaining CPE as they could not be resolved. - classpathEntries.removeAll(libCpeList); - } + // remove the CPE that should be removed. + classpathEntries.removeAll(cpeToRemove); // set the new list javaProject.setRawClasspath( classpathEntries.toArray(new IClasspathEntry[classpathEntries.size()]), monitor); - if (bundle.mCleanupCPE) { - // and delete the folders of the CPE that were removed (must be done after) - for (IClasspathEntry cpe : libCpeList) { - IResource res = wsRoot.findMember(cpe.getPath()); - res.delete(true, monitor); - } + // and delete the folders of the CPE that were removed (must be done after) + for (IClasspathEntry cpe : cpeToRemove) { + IResource res = wsRoot.findMember(cpe.getPath()); + res.delete(true, monitor); } return Status.OK_STATUS; @@ -1434,6 +1417,107 @@ public final class Sdk { } } + private boolean isRemovedLibrary(LinkUpdateBundle bundle, String libName) { + if (bundle.mDeletedLibraryPath != null && + libName.equals(bundle.mDeletedLibraryPath.lastSegment())) { + return true; + } + + if (bundle.mRemovedLibraryProjects != null) { + for (IProject removedProject : bundle.mRemovedLibraryProjects) { + if (libName.equals(removedProject.getName())) { + return true; + } + } + } + + return false; + } + + /** + * Computes the library difference based on a previous list and a current state, and creates + * a {@link LinkUpdateBundle} action to update the given project. + * @param project The current project state + * @param oldLibraries the list of old libraries. Typically the result of + * {@link ProjectState#getFullLibraryProjects()} before the ProjectState is updated. + * @return null if there no action to take, or a {@link LinkUpdateBundle} object to run. + */ + private LinkUpdateBundle getLinkBundle(ProjectState project, IProject[] oldLibraries) { + // get the new full list of projects + IProject[] newLibraries = project.getFullLibraryProjects(); + + // and build the real difference. A list of new projects and a list of + // removed project. + // This is not the same as the added/removed libraries because libraries + // could be indirect dependencies through several different direct + // dependencies so it's easier to compare the full lists before and after + // the reload. + + List<IProject> addedLibs = new ArrayList<IProject>(); + List<IProject> removedLibs = new ArrayList<IProject>(); + + // first get the list of new projects. + for (IProject newLibrary : newLibraries) { + boolean found = false; + for (IProject oldLibrary : oldLibraries) { + if (newLibrary.equals(oldLibrary)) { + found = true; + break; + } + } + + // if it was not found in the old libraries, it's really new + if (found == false) { + addedLibs.add(newLibrary); + } + } + + // now the list of removed projects. + for (IProject oldLibrary : oldLibraries) { + boolean found = false; + for (IProject newLibrary : newLibraries) { + if (newLibrary.equals(oldLibrary)) { + found = true; + break; + } + } + + // if it was not found in the new libraries, it's really been removed + if (found == false) { + removedLibs.add(oldLibrary); + } + } + + if (addedLibs.size() > 0 || removedLibs.size() > 0) { + LinkUpdateBundle bundle = new LinkUpdateBundle(); + bundle.mProject = project.getProject(); + bundle.mNewLibraryProjects = + addedLibs.toArray(new IProject[addedLibs.size()]); + bundle.mRemovedLibraryProjects = + removedLibs.toArray(new IProject[removedLibs.size()]); + return bundle; + } + + return null; + } + + /** + * Removes a project from a list based on its name. + * @param projects the list of projects. + * @param name the name of the project to remove. + */ + private void removeFromList(List<IProject> projects, String name) { + final int count = projects.size(); + for (int i = 0 ; i < count ; i++) { + // since project basically have only one segment that matter, + // just check the names + if (projects.get(i).getName().equals(name)) { + projects.remove(i); + return; + } + } + } + /** * Returns a {@link IClasspathEntry} from the given list whose linked path match the given path. * @param cpeList a list of {@link IClasspathEntry} of {@link IClasspathEntry#getEntryKind()} @@ -1464,107 +1548,6 @@ public final class Sdk { } /** - * Unlinks a project and a library. This removes the linked folder from the main project, and - * removes it from the build path. Finally, this calls {@link LibraryState#close()}. - * <p/>This can be done in a job in case the workspace is not locked for resource - * modification. See <var>doInJob</var>. - * - * @param bundle The {@link UnlinkLibraryBundle} action bundle that contains all the parameters - * necessary to execute the action. - * @param monitor an {@link IProgressMonitor}. - * @return an {@link IStatus} with the status of the action. - */ - private IStatus unlinkLibrary(UnlinkLibraryBundle bundle, IProgressMonitor monitor) { - try { - IProject project = bundle.mProject.getProject(); - - // if the library and the main project are closed at the same time, this - // is likely to return false since this is run in a new job. - if (project.isOpen() == false) { - // cannot change the description of closed projects. - return Status.OK_STATUS; - } - - // remove the library to the list of dynamic references - IProjectDescription projectDescription = project.getDescription(); - IProject[] refs = projectDescription.getDynamicReferences(); - - if (refs.length > 0) { - ArrayList<IProject> list = new ArrayList<IProject>(Arrays.asList(refs)); - - // remove a previous library if needed (in case of a rename) - final int count = list.size(); - for (int i = 0 ; i < count ; i++) { - // since project basically have only one segment that matter, - // just check the names - if (list.get(i).equals(bundle.mLibrary)) { - list.remove(i); - break; - } - } - - // set the changed list - projectDescription.setDynamicReferences( - list.toArray(new IProject[list.size()])); - } - - // edit the list of source folders. - IJavaProject javaProject = JavaCore.create(project); - IClasspathEntry[] entries = javaProject.getRawClasspath(); - ArrayList<IClasspathEntry> classpathEntries = new ArrayList<IClasspathEntry>( - Arrays.asList(entries)); - - IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); - - // list to hold the source folder to delete, as they can't be delete before - // they have been removed from the classpath entries - ArrayList<IResource> toDelete = new ArrayList<IResource>(); - - for (int i = 0 ; i < classpathEntries.size();) { - IClasspathEntry classpathEntry = classpathEntries.get(i); - if (classpathEntry.getEntryKind() == IClasspathEntry.CPE_SOURCE) { - IPath path = classpathEntry.getPath(); - IResource linkedRes = wsRoot.findMember(path); - if (linkedRes != null && linkedRes.isLinked() && CREATOR_ADT.equals( - ProjectHelper.loadStringProperty(linkedRes, PROP_CREATOR))) { - IResource originalLibrary = ProjectHelper.loadResourceProperty( - linkedRes, PROP_LIBRARY); - - // if the library is missing, or if the library is the one being - // unlinked: - // remove the classpath entry and delete the linked folder. - if (originalLibrary == null || - originalLibrary.equals(bundle.mLibrary)) { - classpathEntries.remove(i); - toDelete.add(linkedRes); - continue; // don't increment i - } - } - } - - i++; - } - - // set the new list - javaProject.setRawClasspath( - classpathEntries.toArray(new IClasspathEntry[classpathEntries.size()]), - monitor); - - // delete the resources that need deleting - for (IResource res : toDelete) { - res.delete(true, monitor); - } - - return Status.OK_STATUS; - } catch (CoreException e) { - AdtPlugin.log(e, "Failure to unlink %1$s from %2$s", //$NON-NLS-1$ - bundle.mLibrary.getName(), - bundle.mProject.getProject().getName()); - return e.getStatus(); - } - } - - /** * Updates all existing projects with a given list of new/updated libraries. * This loops through all opened projects and check if they depend on any of the given * library project, and if they do, they are linked together. @@ -1581,9 +1564,10 @@ public final class Sdk { // Once they are updated (meaning ProjectState#needs() has been called on them), // we add them to the list so that can be updated as well. for (ProjectState projectState : sProjectStateMap.values()) { - // list for all the new library dependencies we find. - ArrayList<IProject> libsToLink = new ArrayList<IProject>(); + // record the current library dependencies + IProject[] oldLibraries = projectState.getFullLibraryProjects(); + boolean needLibraryDependenciesUpdated = false; for (ProjectState library : libraries) { // Normally we would only need to test if ProjectState#needs returns non null, // meaning the link between the project and the library has not been @@ -1595,36 +1579,25 @@ public final class Sdk { // We still need to call ProjectState#needs to make the link in case it's not // been done yet (which can happen if the library project was just opened). if (projectState != library) { + // call needs in case this new library was just opened, and the link needs + // to be done LibraryState libState = projectState.needs(library); - if (libState != null || projectState.dependsOn(library)) { - // we have a match. Add it. - IProject libProject = library.getProject(); - - // library could already be here if it was an indirect dependencies - // from a previously processed updated library. - if (libsToLink.contains(libProject) == false) { - libsToLink.add(libProject); - } - - // now find what this depends on, and add it too. - // The order here doesn't matter - // as it's just to add the linked source folder, so there's no - // need to use ProjectState#getFullLibraryProjects() which - // could return project that have already been added anyway. - fillProjectDependenciesList(library, libsToLink); + if (libState == null && projectState.dependsOn(library)) { + // ProjectState.needs only returns true if the library was needed. + // but we also need to check the case where the project depends on + // the library but the link was already done. + needLibraryDependenciesUpdated = true; } } } - if (libsToLink.size() > 0) { - // create an action bundle for this link - LinkLibraryBundle bundle = new LinkLibraryBundle(); - bundle.mProject = projectState.getProject(); - bundle.mLibraryProjects = libsToLink.toArray( - new IProject[libsToLink.size()]); - bundle.mPreviousLibraryPath = null; - bundle.mCleanupCPE = false; - startActionBundle(bundle); + if (needLibraryDependenciesUpdated) { + projectState.updateFullLibraryList(); + } + + LinkUpdateBundle bundle = getLinkBundle(projectState, oldLibraries); + if (bundle != null) { + queueLinkUpdateBundle(bundle); // if this updated project is a library, add it to the list, so that // projects depending on it get updated too. |