OSDN Git Service

Merge "ADT GLE2: synchronized selection between canvas, outline and properties."
authorRaphael Moll <ralf@android.com>
Fri, 2 Jul 2010 21:54:41 +0000 (14:54 -0700)
committerAndroid Code Review <code-review@android.com>
Fri, 2 Jul 2010 21:54:41 +0000 (14:54 -0700)
17 files changed:
changes.txt
eclipse/changes.txt
eclipse/plugins/.gitignore
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/MultiApkExportAction.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PostCompilerBuilder.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PreCompilerBuilder.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectState.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/properties/AndroidPropertyPage.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/properties/LibraryProperties.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java
eclipse/plugins/com.android.ide.eclipse.ddms/.classpath
eclipse/plugins/com.android.ide.eclipse.ddms/icons/device.png [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.ddms/icons/emulator.png [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.ddms/icons/heap.png [new file with mode: 0644]
eclipse/plugins/com.android.ide.eclipse.ddms/icons/thread.png [new file with mode: 0644]

index 97c5112..721d152 100644 (file)
@@ -3,6 +3,7 @@ Change log for Android SDK Tools.
 Revision 7:
 - Support for Ant rules provided by the Tools components (override the one in
   the platform component)
+- Added support for libraries with library dependencies.
 - Support for aidl files in library projects.
 - Support for extension targets in Ant build to perform tasks between the
   normal tasks: -pre-build, -pre-compile, -post-compile.
index 5cf67e2..f9eaf9d 100644 (file)
@@ -1,6 +1,7 @@
 0.9.8:
 - Fixed issue with library project names containing characters that aren't compatible with Eclipse path variable. The link between the main project and the library would fail to create.
 - Added support for library projects that don't have a source folder called "src". There is now support for any number of source folder, with no name restriction. They can even be in sub folder such as "src/java".
+- Added support for libraries with library dependencies.
 - added support for new resource qualifiers: car/desk, night/notnight and navexposed/navhidden
 
 0.9.7:
index 4d1d3dd..f0ab8f3 100644 (file)
@@ -2,55 +2,6 @@ com.android.ide.eclipse.adt/bin
 com.android.ide.eclipse.ddms/bin
 com.android.ide.eclipse.tests/bin
 
-com.android.ide.eclipse.adt/androidprefs.jar
-com.android.ide.eclipse.adt/kxml2-2.3.0.jar
-com.android.ide.eclipse.adt/layoutlib_api.jar
-com.android.ide.eclipse.adt/layoutlib_utils.jar
-com.android.ide.eclipse.adt/ninepatch.jar
-com.android.ide.eclipse.adt/sdklib.jar
-com.android.ide.eclipse.adt/sdkstats.jar
-com.android.ide.eclipse.adt/sdkuilib.jar
-com.android.ide.eclipse.adt/commons-compress-1.0.jar
-com.android.ide.eclipse.ddms/icons/add.png
-com.android.ide.eclipse.ddms/icons/backward.png
-com.android.ide.eclipse.ddms/icons/clear.png
-com.android.ide.eclipse.ddms/icons/d.png
-com.android.ide.eclipse.ddms/icons/debug-attach.png
-com.android.ide.eclipse.ddms/icons/debug-error.png
-com.android.ide.eclipse.ddms/icons/debug-wait.png
-com.android.ide.eclipse.ddms/icons/delete.png
-com.android.ide.eclipse.ddms/icons/device.png
-com.android.ide.eclipse.ddms/icons/down.png
-com.android.ide.eclipse.ddms/icons/e.png
-com.android.ide.eclipse.ddms/icons/edit.png
-com.android.ide.eclipse.ddms/icons/empty.png
-com.android.ide.eclipse.ddms/icons/emulator.png
-com.android.ide.eclipse.ddms/icons/forward.png
-com.android.ide.eclipse.ddms/icons/gc.png
-com.android.ide.eclipse.ddms/icons/halt.png
-com.android.ide.eclipse.ddms/icons/heap.png
-com.android.ide.eclipse.ddms/icons/hprof.png
-com.android.ide.eclipse.ddms/icons/i.png
-com.android.ide.eclipse.ddms/icons/importBug.png
-com.android.ide.eclipse.ddms/icons/load.png
-com.android.ide.eclipse.ddms/icons/pause.png
-com.android.ide.eclipse.ddms/icons/play.png
-com.android.ide.eclipse.ddms/icons/pull.png
-com.android.ide.eclipse.ddms/icons/push.png
-com.android.ide.eclipse.ddms/icons/save.png
-com.android.ide.eclipse.ddms/icons/thread.png
-com.android.ide.eclipse.ddms/icons/tracing_start.png
-com.android.ide.eclipse.ddms/icons/tracing_stop.png
-com.android.ide.eclipse.ddms/icons/up.png
-com.android.ide.eclipse.ddms/icons/v.png
-com.android.ide.eclipse.ddms/icons/w.png
-com.android.ide.eclipse.ddms/icons/warning.png
-com.android.ide.eclipse.ddms/libs/jcommon-1.0.12.jar
-com.android.ide.eclipse.ddms/libs/jfreechart-1.0.9-swt.jar
-com.android.ide.eclipse.ddms/libs/jfreechart-1.0.9.jar
-com.android.ide.eclipse.ddms/src/com/android/ddmlib
-com.android.ide.eclipse.ddms/src/com/android/ddmuilib
-com.android.ide.eclipse.tests/kxml2-2.3.0.jar
 com.android.ide.eclipse.tests/unittests/com/android/ddmlib
 com.android.ide.eclipse.tests/unittests/com/android/sdklib
 com.android.ide.eclipse.tests/unittests/com/android/sdkuilib
index c6fda52..1f89a01 100644 (file)
@@ -308,6 +308,27 @@ public class AdtPlugin extends AbstractUIPlugin {
                     AndroidLaunchController.debugRunningApp(project, port);
                     return true;
                 } else {
+                    // check to see if there's a platform project defined by an env var.
+                    String var = System.getenv("ANDROID_PLATFORM_PROJECT"); //$NON-NLS-1$
+                    if (var != null && var.length() > 0) {
+                        boolean auto = "AUTO".equals(var); //$NON-NLS-1$
+
+                        // Get the list of project for the current workspace
+                        IWorkspace workspace = ResourcesPlugin.getWorkspace();
+                        IProject[] projects = workspace.getRoot().getProjects();
+
+                        // look for a project that matches the env var or take the first
+                        // one if in automatic mode.
+                        for (IProject p : projects) {
+                            if (p.isOpen()) {
+                                if (auto || p.getName().equals(var)) {
+                                    AndroidLaunchController.debugRunningApp(p, port);
+                                    return true;
+                                }
+                            }
+                        }
+
+                    }
                     return false;
                 }
             }
index cce8ac8..f5876e9 100644 (file)
@@ -244,7 +244,7 @@ public class MultiApkExportAction implements IObjectActionDelegate {
             int versionCode, ApkData apk, Entry<String, String> softVariant, IFolder binFolder)
             throws CoreException {
         // get the libraries for this project
-        IProject[] libProjects = projectState.getLibraryProjects();
+        IProject[] libProjects = projectState.getFullLibraryProjects();
 
         IProject project = projectState.getProject();
         IJavaProject javaProject = JavaCore.create(project);
index ffec6e7..539c8e3 100644 (file)
@@ -206,7 +206,7 @@ public class PostCompilerBuilder extends BaseBuilder {
             }
 
             // get the libraries
-            libProjects = projectState.getLibraryProjects();
+            libProjects = projectState.getFullLibraryProjects();
 
             IJavaProject javaProject = JavaCore.create(project);
 
@@ -218,7 +218,7 @@ public class PostCompilerBuilder extends BaseBuilder {
             IJavaProject[] referencedJavaProjects = PostCompilerHelper.getJavaProjects(javaProjects);
 
             // mix the java project and the library projects
-            final int libCount = libProjects != null ? libProjects.length : 0;
+            final int libCount = libProjects.length;
             final int javaCount = javaProjects != null ? javaProjects.length : 0;
             allRefProjects = new IProject[libCount + javaCount];
             if (libCount > 0) {
@@ -268,7 +268,7 @@ public class PostCompilerBuilder extends BaseBuilder {
                 // if the main resources didn't change, then we check for the library
                 // ones (will trigger resource repackaging too)
                 if ((mPackageResources == false || mBuildFinalPackage == false) &&
-                        libProjects != null && libProjects.length > 0) {
+                        libProjects.length > 0) {
                     for (IProject libProject : libProjects) {
                         delta = getDelta(libProject);
                         if (delta != null) {
@@ -524,7 +524,7 @@ public class PostCompilerBuilder extends BaseBuilder {
             }
 
             msg = String.format("Unknown error: %1$s", msg);
-            AdtPlugin.printErrorToConsole(project, msg);
+            AdtPlugin.logAndPrintError(exception, project.getName(), msg);
             markProject(AndroidConstants.MARKER_PACKAGING, msg, IMarker.SEVERITY_ERROR);
         }
 
index 868ecf7..e3a6715 100644 (file)
@@ -234,7 +234,7 @@ public class PreCompilerBuilder extends BaseBuilder {
             IAndroidTarget projectTarget = projectState.getTarget();
 
             // get the libraries
-            libProjects = projectState.getLibraryProjects();
+            libProjects = projectState.getFullLibraryProjects();
 
             IJavaProject javaProject = JavaCore.create(project);
 
@@ -287,8 +287,7 @@ public class PreCompilerBuilder extends BaseBuilder {
 
                     // if the main resources didn't change, then we check for the library
                     // ones (will trigger resource recompilation too)
-                    if (mMustCompileResources == false && libProjects != null &&
-                            libProjects.length > 0) {
+                    if (mMustCompileResources == false && libProjects.length > 0) {
                         for (IProject libProject : libProjects) {
                             delta = getDelta(libProject);
                             if (delta != null) {
index 93c77f9..5886692 100644 (file)
@@ -42,6 +42,18 @@ import java.util.regex.Matcher;
  * <p>This gives raw access to the properties (from <code>default.properties</code>), as well
  * as direct access to target, apksettings and library information.
  *
+ * This also gives access to library information.
+ *
+ * {@link #isLibrary()} indicates if the project is a library.
+ * {@link #hasLibraries()} and {@link #getLibraries()} give access to the libraries through
+ * instances of {@link LibraryState}. A {@link LibraryState} instance is a link between a main
+ * project and its library. Theses instances are owned by the {@link ProjectState}.
+ *
+ * {@link #isMissingLibraries()} will indicate if the project has libraries that are not resolved.
+ * Unresolved libraries are libraries that do not have any matching opened Eclipse project.
+ * When there are missing libraries, the {@link LibraryState} instance for them will return null
+ * for {@link LibraryState#getProjectState()}.
+ *
  */
 public final class ProjectState {
 
@@ -74,13 +86,14 @@ public final class ProjectState {
          * Closes the library. This resets the IProject from this object ({@link #getProjectState()} will
          * return <code>null</code>), and updates the main project data so that the library
          * {@link IProject} object does not show up in the return value of
-         * {@link ProjectState#getLibraryProjects()}.
+         * {@link ProjectState#getFullLibraryProjects()}.
          */
         public void close() {
+            mProjectState.removeParentProject(getMainProjectState());
             mProjectState = null;
             mPath = null;
 
-            updateLibraries();
+            getMainProjectState().updateFullLibraryList();
         }
 
         private void setRelativePath(String relativePath) {
@@ -90,8 +103,9 @@ public final class ProjectState {
         private void setProject(ProjectState project) {
             mProjectState = project;
             mPath = project.getProject().getLocation().toOSString();
+            mProjectState.addParentProject(getMainProjectState());
 
-            updateLibraries();
+            getMainProjectState().updateFullLibraryList();
         }
 
         /**
@@ -145,15 +159,22 @@ public final class ProjectState {
 
     private final IProject mProject;
     private final ProjectProperties mProperties;
+    private IAndroidTarget mTarget;
+    private ApkSettings mApkSettings;
     /**
      * list of libraries. Access to this list must be protected by
      * <code>synchronized(mLibraries)</code>, but it is important that such code do not call
      * out to other classes (especially those protected by {@link Sdk#getLock()}.)
      */
     private final ArrayList<LibraryState> mLibraries = new ArrayList<LibraryState>();
-    private IAndroidTarget mTarget;
-    private ApkSettings mApkSettings;
-    private IProject[] mLibraryProjects;
+    /** Cached list of all IProject instances representing the resolved libraries, including
+     * indirect dependencies. This must never be null. */
+    private IProject[] mLibraryProjects = new IProject[0];
+    /**
+     * List of parent projects. When this instance is a library ({@link #isLibrary()} returns
+     * <code>true</code>) then this is filled with projects that depends on this project.
+     */
+    private final ArrayList<ProjectState> mParentProjects = new ArrayList<ProjectState>();
 
     public ProjectState(IProject project, ProjectProperties properties) {
         mProject = project;
@@ -281,7 +302,7 @@ public final class ProjectState {
             diff.removed.addAll(oldLibraries);
 
             // update the library with what IProjet are known at the time.
-            updateLibraries();
+            updateFullLibraryList();
         }
 
         return diff;
@@ -305,13 +326,14 @@ public final class ProjectState {
     }
 
     /**
-     * Convenience method returning all the IProject objects for the resolved libraries.
+     * Returns all the <strong>resolved</strong> library projects, including indirect dependencies.
+     * The array is ordered to match the library priority order for resource processing with
+     * <code>aapt</code>.
      * <p/>If some dependencies are not resolved (or their projects is not opened in Eclipse),
      * they will not show up in this list.
-     * @return the resolved projects or null if there are no project (either no resolved or no
-     * dependencies)
+     * @return the resolved projects. May be an empty list.
      */
-    public IProject[] getLibraryProjects() {
+    public IProject[] getFullLibraryProjects() {
         return mLibraryProjects;
     }
 
@@ -369,6 +391,15 @@ public final class ProjectState {
         return null;
     }
 
+    /**
+     * Returns the {@link LibraryState} object for a given <var>name</var>.
+     * </p>This can only return a non-null object if the link between the main project's
+     * {@link IProject} and the library's {@link IProject} was done.
+     *
+     * @return the matching LibraryState or <code>null</code>
+     *
+     * @see #needs(IProject)
+     */
     public LibraryState getLibrary(String name) {
         synchronized (mLibraries) {
             for (LibraryState state : mLibraries) {
@@ -424,6 +455,26 @@ public final class ProjectState {
     }
 
     /**
+     * Returns whether the project depends on a given <var>library</var>
+     * @param library the library to check.
+     * @return true if the project depends on the library. This is not affected by whether the link
+     * was done through {@link #needs(ProjectState)}.
+     */
+    public boolean dependsOn(ProjectState library) {
+        synchronized (mLibraries) {
+            for (LibraryState state : mLibraries) {
+                if (state != null && state.getProjectState() != null &&
+                        library.getProject().equals(state.getProjectState().getProject())) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+
+    /**
      * Updates a library with a new path.
      * <p/>This method acts both as a check and an action. If the project does not depend on the
      * given <var>oldRelativePath</var> then no action is done and <code>null</code> is returned.
@@ -491,6 +542,15 @@ public final class ProjectState {
         return null;
     }
 
+
+    private void addParentProject(ProjectState parentState) {
+        mParentProjects.add(parentState);
+    }
+
+    private void removeParentProject(ProjectState parentState) {
+        mParentProjects.remove(parentState);
+    }
+
     /**
      * Saves the default.properties file and refreshes it to make sure that it gets reloaded
      * by Eclipse
@@ -543,20 +603,51 @@ public final class ProjectState {
         return null;
     }
 
-    private void updateLibraries() {
+    /**
+     * Update the full library list, including indirect dependencies. The result is returned by
+     * {@link #getFullLibraryProjects()}.
+     */
+    private void updateFullLibraryList() {
         ArrayList<IProject> list = new ArrayList<IProject>();
         synchronized (mLibraries) {
-            for (LibraryState state : mLibraries) {
-                if (state.getProjectState() != null) {
-                    list.add(state.getProjectState().getProject());
-                }
-            }
+            buildFullLibraryDependencies(mLibraries, list);
         }
 
         mLibraryProjects = list.toArray(new IProject[list.size()]);
     }
 
     /**
+     * Resolves a given list of libraries, finds out if they depend on other libraries, and
+     * returns a full list of all the direct and indirect dependencies in the proper order (first
+     * is higher priority when calling aapt).
+     * @param inLibraries the libraries to resolve
+     * @param outLibraries where to store all the libraries.
+     */
+    private void buildFullLibraryDependencies(List<LibraryState> inLibraries,
+            ArrayList<IProject> outLibraries) {
+        // loop in the inverse order to resolve dependencies on the libraries, so that if a library
+        // is required by two higher level libraries it can be inserted in the correct place
+        for (int i = inLibraries.size() - 1  ; i >= 0 ; i--) {
+            LibraryState library = inLibraries.get(i);
+
+            // get its libraries if possible
+            ProjectState libProjectState = library.getProjectState();
+            if (libProjectState != null) {
+                List<LibraryState> dependencies = libProjectState.getLibraries();
+
+                // build the dependencies for those libraries
+                buildFullLibraryDependencies(dependencies, outLibraries);
+
+                // and add the current library (if needed) in front (higher priority)
+                if (outLibraries.contains(libProjectState.getProject()) == false) {
+                    outLibraries.add(0, libProjectState.getProject());
+                }
+            }
+        }
+    }
+
+
+    /**
      * Converts a path containing only / by the proper platform separator.
      */
     private String convertPath(String path) {
@@ -589,4 +680,9 @@ public final class ProjectState {
     public int hashCode() {
         return mProject.hashCode();
     }
+
+    @Override
+    public String toString() {
+        return mProject.getName();
+    }
 }
index 4621431..44da3a9 100644 (file)
@@ -85,12 +85,6 @@ public class AndroidPropertyPage extends PropertyPage implements IWorkbenchPrope
 
         mIsLibrary = new Button(libraryGroup, SWT.CHECK);
         mIsLibrary.setText("Is Library");
-        mIsLibrary.addSelectionListener(new SelectionAdapter() {
-            @Override
-            public void widgetSelected(SelectionEvent e) {
-                mLibraryDependencies.setEnabled(!mIsLibrary.getSelection());
-            }
-        });
 
         mLibraryDependencies = new LibraryProperties(libraryGroup);
 
@@ -149,7 +143,7 @@ public class AndroidPropertyPage extends PropertyPage implements IWorkbenchPrope
                 mustSaveProp = true;
             }
 
-            if (mLibraryDependencies.save(mIsLibrary.getSelection())) {
+            if (mLibraryDependencies.save()) {
                 mustSaveProp = true;
             }
 
@@ -184,9 +178,7 @@ public class AndroidPropertyPage extends PropertyPage implements IWorkbenchPrope
             }
 
             mIsLibrary.setSelection(state.isLibrary());
-
             mLibraryDependencies.setContent(state);
-            mLibraryDependencies.setEnabled(!state.isLibrary());
 
             /*
              * APK-SPLIT: This is not yet supported, so we hide the UI
index 1675f7f..67de298 100644 (file)
@@ -113,7 +113,6 @@ final class LibraryProperties {
         mMatchIcon = AdtPlugin.getImageDescriptor("/icons/match.png").createImage(); //$NON-NLS-1$
         mErrorIcon = AdtPlugin.getImageDescriptor("/icons/error.png").createImage(); //$NON-NLS-1$
 
-
         // Layout has 2 column
         mTop = new Composite(parent, SWT.NONE);
         mTop.setLayout(new GridLayout(2, false));
@@ -259,13 +258,12 @@ final class LibraryProperties {
      * {@link #setContent(ProjectState)}.
      * <p/>This only saves the data into the {@link ProjectProperties} of the state, but does
      * not update the {@link ProjectState} or the list of {@link LibraryState}.
-     * @param isLibrary the new library flag of the project.
      * @return <code>true</code> if there was actually new data saved in the project state, false
      * otherwise.
      */
-    boolean save(boolean isLibrary) {
-        boolean mustSave = mMustSave || (isLibrary && mState.getLibraries().size() > 0);
-        if (mustSave) {
+    boolean save() {
+        boolean mustSave = mMustSave;
+        if (mMustSave) {
             // remove all previous library dependencies.
             ProjectProperties props = mState.getProperties();
             Set<String> keys = props.keySet();
@@ -276,12 +274,10 @@ final class LibraryProperties {
             }
 
             // now add the new libraries.
-            if (isLibrary == false) {
-                int index = 1;
-                for (ItemData data : mItemDataList) {
-                    props.setProperty(ProjectProperties.PROPERTY_LIB_REF + index++,
-                            data.relativePath);
-                }
+            int index = 1;
+            for (ItemData data : mItemDataList) {
+                props.setProperty(ProjectProperties.PROPERTY_LIB_REF + index++,
+                        data.relativePath);
             }
         }
 
index ef2e950..e46c964 100644 (file)
@@ -384,7 +384,7 @@ public class ProjectResources implements IResourceRepository {
         if (mProject != null) {
             ProjectState state = Sdk.getProjectState(mProject);
             if (state != null) {
-                IProject[] libraries = state.getLibraryProjects();
+                IProject[] libraries = state.getFullLibraryProjects();
 
                 ResourceManager resMgr = ResourceManager.getInstance();
 
@@ -392,8 +392,7 @@ public class ProjectResources implements IResourceRepository {
                 // one will have priority over the 2nd one. So it's better to loop in the inverse
                 // order and fill the map with resources that will be overwritten by higher
                 // priority resources
-                final int libCount = libraries != null ? libraries.length : 0;
-                for (int i = libCount - 1 ; i >= 0 ; i--) {
+                for (int i = libraries.length - 1 ; i >= 0 ; i--) {
                     IProject library = libraries[i];
 
                     ProjectResources libRes = resMgr.getProjectResources(library);
index c3dc894..a4c216c 100644 (file)
@@ -27,6 +27,7 @@ import com.android.ide.eclipse.adt.internal.project.ProjectState.LibraryState;
 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor;
 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IFileListener;
 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IProjectListener;
+import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IResourceEventListener;
 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData.LayoutBridge;
 import com.android.prefs.AndroidLocation.AndroidLocationException;
 import com.android.sdklib.AndroidVersion;
@@ -48,20 +49,17 @@ import org.eclipse.core.resources.IProjectDescription;
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.resources.IResourceDelta;
 import org.eclipse.core.resources.IWorkspaceRoot;
-import org.eclipse.core.resources.IncrementalProjectBuilder;
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.NullProgressMonitor;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.core.runtime.jobs.Job;
 import org.eclipse.jdt.core.IClasspathEntry;
 import org.eclipse.jdt.core.IJavaProject;
 import org.eclipse.jdt.core.JavaCore;
-import org.eclipse.ui.progress.IJobRunnable;
 
 import java.io.File;
 import java.io.IOException;
@@ -71,6 +69,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.Map.Entry;
@@ -605,6 +604,7 @@ public final class Sdk  {
         GlobalProjectMonitor monitor = GlobalProjectMonitor.getMonitor();
         monitor.addProjectListener(mProjectListener);
         monitor.addFileListener(mFileListener, IResourceDelta.CHANGED | IResourceDelta.ADDED);
+        monitor.addResourceEventListener(mResourceEventListener);
 
         // pre-compute some paths
         mDocBaseUrl = getDocumentationBaseUrl(mManager.getLocation() +
@@ -631,6 +631,7 @@ public final class Sdk  {
         GlobalProjectMonitor monitor = GlobalProjectMonitor.getMonitor();
         monitor.removeProjectListener(mProjectListener);
         monitor.removeFileListener(mFileListener);
+        monitor.removeResourceEventListener(mResourceEventListener);
 
         // the IAndroidTarget objects are now obsolete so update the project states.
         synchronized (sLock) {
@@ -736,6 +737,9 @@ public final class Sdk  {
 
                     // 2. if the project is a library, make sure to update the
                     // LibraryState for any main project using this.
+                    // Also, record the updated projects that are libraries, to update
+                    // projects that depend on them.
+                    ArrayList<ProjectState> updatedLibraries = new ArrayList<ProjectState>();
                     for (ProjectState projectState : sProjectStateMap.values()) {
                         LibraryState libState = projectState.getLibrary(project);
                         if (libState != null) {
@@ -749,7 +753,11 @@ public final class Sdk  {
 
                             // edit the project to remove the linked source folder.
                             // this also calls LibraryState.close();
-                            unlinkLibrary(projectState, project, true /*doInJob*/);
+                            startActionBundle(new UnlinkLibraryBundle(projectState, project));
+
+                            if (projectState.isLibrary()) {
+                                updatedLibraries.add(projectState);
+                            }
                         }
                     }
 
@@ -760,71 +768,79 @@ public final class Sdk  {
 
                     // now remove the project for the project map.
                     sProjectStateMap.remove(project);
+
+                    // update the projects that depend on the updated project
+                    updateProjectsWithNewLibraries(updatedLibraries);
                 }
             }
         }
 
         public void projectOpened(IProject project) {
-            onProjectOpened(project, true /*recompile*/);
+            onProjectOpened(project);
         }
 
         public void projectOpenedWithWorkspace(IProject project) {
             // no need to force recompilation when projects are opened with the workspace.
-            onProjectOpened(project, false /*recompile*/);
+            onProjectOpened(project);
         }
 
-        private void onProjectOpened(IProject openedProject, boolean recompile) {
+        private void onProjectOpened(final IProject openedProject) {
             ProjectState openedState = getProjectState(openedProject);
             if (openedState != null) {
-                // find dependencies, if any
-                if (openedState.isMissingLibraries()) {
-                    // the opened project depends on some libraries.
-                    // Look for all other opened projects to see if any is a library for it.
-                    // if they are, fix its LibraryState to get the IProject reference and
-                    // link the two projects with the linked source folder.
-                    boolean foundLibrary = false;
+                if (openedState.hasLibraries()) {
+                    // list of library to link to the opened project.
+                    final ArrayList<IProject> libsToLink = new ArrayList<IProject>();
+
+                    // Look for all other opened projects to see if any is a library for the opened
+                    // project.
                     synchronized (sLock) {
                         for (ProjectState projectState : sProjectStateMap.values()) {
                             if (projectState != openedState) {
+                                // ProjectState#needs() both checks if this is a missing library
+                                // and updates LibraryState to contains the new values.
                                 LibraryState libState = openedState.needs(projectState);
 
                                 if (libState != null) {
-                                    foundLibrary = true;
-                                    linkProjectAndLibrary(openedState, libState, null,
-                                            true /*doInJob*/);
+                                    // we have a match! Add the library to the list (if it was
+                                    // not added through an indirect dependency before).
+                                    IProject libProject = libState.getProjectState().getProject();
+                                    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(libState.getProjectState(),
+                                            libsToLink);
                                 }
                             }
                         }
                     }
 
-                    // force a recompile of the main project through a job
-                    // (tree is locked)
-                    if (recompile && foundLibrary) {
-                        recompile(openedState.getProject());
+                    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);
                     }
                 }
 
-                // if the project is a library, then try to see if it's required by other projects.
+                // if the project is a library, then add it to the list of projects being opened.
+                // They will be processed in IResourceEventListener#resourceChangeEventEnd.
+                // This is done so that we are sure to process all the projects being opened
+                // first and only then process projects depending on the projects that were opened.
                 if (openedState.isLibrary()) {
                     setupLibraryProject(openedProject);
 
-                    synchronized (sLock) {
-                        for (ProjectState projectState : sProjectStateMap.values()) {
-                            if (projectState != openedState && projectState.isMissingLibraries()) {
-                                LibraryState libState = projectState.needs(openedState);
-                                if (libState != null) {
-                                    linkProjectAndLibrary(projectState, libState, null,
-                                            true /*doInJob*/);
-
-                                    // force a recompile of the main project through a job
-                                    // (tree is locked)
-                                    if (recompile) {
-                                        recompile(projectState.getProject());
-                                    }
-                                }
-                            }
-                        }
-                    }
+                    mOpenedLibraryProjects.add(openedState);
                 }
             }
         }
@@ -853,12 +869,12 @@ public final class Sdk  {
                                     oldRelativePath.toString(), newRelativePath.toString(),
                                     renamedState);
                             if (libState != null) {
-                                linkProjectAndLibrary(projectState, libState, from,
-                                        true /*doInJob*/);
-
-                                // force a recompile of the main project through a job
-                                // (tree is locked)
-                                recompile(projectState.getProject());
+                                LinkLibraryBundle bundle = new LinkLibraryBundle();
+                                bundle.mProject = projectState.getProject();
+                                bundle.mLibraryProjects = new IProject[] {
+                                        libState.getProjectState().getProject() };
+                                bundle.mCleanupCPE = false;
+                                startActionBundle(bundle);
                             }
                         }
                     }
@@ -896,10 +912,11 @@ public final class Sdk  {
                             // reload the libraries if needed
                             if (diff.hasDiff()) {
                                 for (LibraryState removedState : diff.removed) {
-                                    ProjectState removePState = removedState.getProjectState();
-                                    if (removePState != null) {
-                                        unlinkLibrary(state, removePState.getProject(),
-                                                false /*doInJob*/);
+                                    ProjectState removedPState = removedState.getProjectState();
+                                    if (removedPState != null) {
+                                        startActionBundle(
+                                                new UnlinkLibraryBundle(
+                                                        state, removedPState.getProject()));
                                     }
                                 }
 
@@ -910,16 +927,21 @@ public final class Sdk  {
                                                 LibraryState libState = state.needs(projectState);
 
                                                 if (libState != null) {
-                                                    linkProjectAndLibrary(state, libState, null,
-                                                            false /*doInJob*/);
+                                                    IProject[] libArray = new IProject[] {
+                                                            libState.getProjectState().getProject()
+                                                    };
+                                                    LinkLibraryBundle bundle =
+                                                            new LinkLibraryBundle();
+                                                    bundle.mProject = iProject;
+                                                    bundle.mLibraryProjects = libArray;
+                                                    bundle.mPreviousLibraryPath = null;
+                                                    bundle.mCleanupCPE = false;
+                                                    startActionBundle(bundle);
                                                 }
                                             }
                                         }
                                     }
                                 }
-
-                                // need to force a full recompile.
-                                iProject.build(IncrementalProjectBuilder.FULL_BUILD, monitor);
                             }
 
                             // apply the new target if needed.
@@ -948,6 +970,202 @@ public final class Sdk  {
         }
     };
 
+    /** List of opened project. This is filled in {@link IProjectListener#projectOpened(IProject)}
+     * and {@link IProjectListener#projectOpenedWithWorkspace(IProject)}, and processed in
+     * {@link IResourceEventListener#resourceChangeEventEnd()}.
+     */
+    private final ArrayList<ProjectState> mOpenedLibraryProjects = new ArrayList<ProjectState>();
+
+    /**
+     * Delegate listener for resource changes. This is called before and after any calls to the
+     * project and file listeners (for a given resource change event).
+     */
+    private IResourceEventListener mResourceEventListener = new IResourceEventListener() {
+        public void resourceChangeEventStart() {
+            // pass
+        }
+
+        public void resourceChangeEventEnd() {
+            updateProjectsWithNewLibraries(mOpenedLibraryProjects);
+            mOpenedLibraryProjects.clear();
+        }
+    };
+
+    /**
+     * Action Bundle to be used with {@link Sdk#startActionBundle(ActionBundle)}.
+     */
+    private interface ActionBundle {
+        enum BundleType { LINK_LIBRARY, UNLINK_LIBRARY };
+        BundleType getType();
+    };
+
+    /**
+     * Action bundle to link libraries to a project.
+     *
+     * @see Sdk#linkProjectAndLibrary(LinkLibraryBundle, IProgressMonitor)
+     */
+    private static class LinkLibraryBundle implements ActionBundle {
+
+        /** The main project receiving the library links. */
+        IProject mProject;
+        /** The libraries to add to the main project. */
+        IProject[] mLibraryProjects;
+        /** 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;
+        /** 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;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("LinkLibraryBundle: %1$s (%2$s) > %3$s", //$NON-NLS-1$
+                    mProject.getName(),
+                    mCleanupCPE,
+                    Arrays.toString(mLibraryProjects));
+        }
+    }
+
+    /**
+     * 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;
+        }
+    }
+
+    private final ArrayList<ActionBundle> mActionBundleQueue = new ArrayList<ActionBundle>();
+
+    /**
+     * Runs the given action bundle through a job queue.
+     *
+     * 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
+     * of in parallel as it would happen if each started its own job) but it is impossible
+     * to manually control the job that's running them (for instance each action is started from
+     * different callbacks such as {@link IProjectListener#projectOpened(IProject)}.
+     *
+     * If the job is not yet started, or has terminated due to lack of action bundle, it is
+     * restarted.
+     *
+     * @param bundle the action bundle to execute
+     */
+    private void startActionBundle(ActionBundle bundle) {
+        boolean startJob = false;
+        synchronized (mActionBundleQueue) {
+            startJob = mActionBundleQueue.size() == 0;
+            mActionBundleQueue.add(bundle);
+        }
+
+        if (startJob) {
+            Job job = new Job("Android Library Job") { //$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) {
+                            // 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);
+                        }
+
+                        // process the bundle.
+                        try {
+                            switch (bundle.getType()) {
+                                case LINK_LIBRARY:
+                                    linkProjectAndLibrary((LinkLibraryBundle)bundle, monitor);
+                                    break;
+                                case UNLINK_LIBRARY:
+                                    unlinkLibrary((UnlinkLibraryBundle) bundle, monitor);
+                                    break;
+                            }
+                        } catch (Exception e) {
+                            AdtPlugin.log(e, "Failed to process bundle: %1$s", //$NON-NLS-1$
+                                    bundle.toString());
+                        }
+
+                        // remove it from the list.
+                        synchronized (mActionBundleQueue) {
+                            mActionBundleQueue.remove(0);
+
+                            // no more bundle to process? done.
+                            if (mActionBundleQueue.size() == 0) {
+                                return Status.OK_STATUS;
+                            }
+                        }
+                    }
+                }
+            };
+            job.setPriority(Job.BUILD);
+            job.schedule();
+        }
+    }
+
+
+    /**
+     * Adds to a list the resolved {@link IProject} dependencies for a given {@link ProjectState}.
+     * This recursively goes down to indirect dependencies.
+     *
+     * <strong>The list is filled in an order that is not valid for calling <code>aapt</code>
+     * </strong>.
+     * Use {@link ProjectState#getFullLibraryProjects()} for use with <code>aapt</code>.
+     *
+     * @param projectState the ProjectState of the project from which to add the libraries.
+     * @param libraries the list of {@link IProject} to fill.
+     */
+    private void fillProjectDependenciesList(ProjectState projectState,
+            ArrayList<IProject> libraries) {
+        for (LibraryState libState : projectState.getLibraries()) {
+            ProjectState libProjectState = libState.getProjectState();
+
+            // only care if the LibraryState has a resolved ProjectState
+            if (libProjectState != null) {
+                // try not to add duplicate. This can happen if a project depends on 2 different
+                // libraries that both depend on the same one.
+                IProject libProject = libProjectState.getProject();
+                if (libraries.contains(libProject) == false) {
+                    libraries.add(libProject);
+                }
+
+                // process the libraries of this library too.
+                fillProjectDependenciesList(libProjectState, libraries);
+            }
+        }
+    }
+
+    /**
+     * Sets up a path variable for a given project.
+     * The name of the variable is based on the name of the project. However some valid character
+     * for project names can be invalid for variable paths.
+     * {@link #getLibraryVariableName(String)} return the name of the variable based on the
+     * project name.
+     *
+     * @param libProject the project
+     *
+     * @see IPathVariableManager
+     * @see #getLibraryVariableName(String)
+     */
     private void setupLibraryProject(IProject libProject) {
         // if needed add a path var for this library
         IPathVariableManager pathVarMgr =
@@ -968,15 +1186,28 @@ public final class Sdk  {
     }
 
 
+    /**
+     * Deletes the path variable that was setup for the given project.
+     * @param project the project
+     * @see #disposeLibraryProject(String)
+     */
     private void disposeLibraryProject(IProject project) {
         disposeLibraryProject(project.getName());
     }
 
-    private void disposeLibraryProject(String libName) {
+    /**
+     * Deletes the path variable that was setup for the given project name.
+     * The name of the variable is based on the name of the project. However some valid character
+     * for project names can be invalid for variable paths.
+     * {@link #getLibraryVariableName(String)} return the name of the variable based on the
+     * project name.
+     * @param projectName the name of the project, unmodified.
+     */
+    private void disposeLibraryProject(String projectName) {
         IPathVariableManager pathVarMgr =
             ResourcesPlugin.getWorkspace().getPathVariableManager();
 
-        final String varName = getLibraryVariableName(libName);
+        final String varName = getLibraryVariableName(projectName);
 
         // remove the value by setting the value to null.
         try {
@@ -997,178 +1228,209 @@ public final class Sdk  {
     }
 
     /**
-     * Links a project and a library so that the project can use the library code and resources.
-     * <p/>This can be done in a job in case the workspace is not locked for resource
-     * modification. See <var>doInJob</var>.
+     * Links a project and a set of libraries so that the project can use the library code.
+     *
+     * 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
+     *   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
+     *   be resolved are removed. This should only be used when the project is opened.
      *
-     * @param projectState the {@link ProjectState} for the main project
-     * @param libraryState the {@link LibraryState} for the library project.
-     * @param previousLibraryPath an optional old library path that needs to be removed at the
-     * same time. Can be <code>null</code> in which case no libraries are removed.
-     * @param doInJob whether the action must be done in a new {@link Job}.
+     * @param bundle The {@link LinkLibraryBundle} 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 void linkProjectAndLibrary(
-            final ProjectState projectState,
-            final LibraryState libraryState,
-            final IPath previousLibraryPath,
-            boolean doInJob) {
-        final IJobRunnable jobRunnable = new IJobRunnable() {
-            public IStatus run(final IProgressMonitor monitor) {
-                try {
-                    IProject project = projectState.getProject();
-                    IProject library = libraryState.getProjectState().getProject();
-
-                    // add 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)
-                        if (previousLibraryPath != 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(
-                                        previousLibraryPath.lastSegment())) {
-                                    list.remove(i);
-                                    break;
-                                }
-                            }
-
+    private IStatus linkProjectAndLibrary(LinkLibraryBundle bundle, IProgressMonitor monitor) {
+        try {
+            // add the library to the list of dynamic references. This is necessary to receive
+            // notifications that the library content changed in the builders.
+            IProjectDescription projectDescription = bundle.mProject.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)
+                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;
                         }
+                    }
 
-                        // add the new one.
-                        list.add(library);
+                }
 
-                        // set the changed list
-                        projectDescription.setDynamicReferences(
-                                list.toArray(new IProject[list.size()]));
-                    } else {
-                        projectDescription.setDynamicReferences(new IProject[] { library });
-                    }
+                // add the new ones.
+                list.addAll(Arrays.asList(bundle.mLibraryProjects));
 
-                    // add a linked resource for the source of the library and add it to the project
-                    final String libName = library.getName();
-                    final String varName = getLibraryVariableName(libName);
-
-                    // get the current classpath entries for the project to add the new 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
-                    final ArrayList<IResource> toDelete = new ArrayList<IResource>();
-
-                    // loop on the classpath entries and look for CPE_SOURCE entries that
-                    // are linked folders. If they are created by us for the given library, then
-                    // we remove them as they'll be created again later (it's easier than trying
-                    // to keep old one--if they link to the same resource)
-                    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
-                                // added or the same path as the one being removed:
-                                // remove the classpath entry and delete the linked folder.
-                                if (originalLibrary == null || originalLibrary.equals(library) ||
-                                        originalLibrary.getFullPath().equals(previousLibraryPath)) {
-                                    classpathEntries.remove(i);
-                                    toDelete.add(linkedRes);
-                                    continue; // don't increment i
-                                }
-                            }
+                // set the changed list
+                projectDescription.setDynamicReferences(
+                        list.toArray(new IProject[list.size()]));
+            } else {
+                projectDescription.setDynamicReferences(bundle.mLibraryProjects);
+            }
+
+            // get the current classpath entries for the project to add the new source
+            // folders.
+            IJavaProject javaProject = JavaCore.create(bundle.mProject);
+            IClasspathEntry[] entries = javaProject.getRawClasspath();
+            ArrayList<IClasspathEntry> classpathEntries = new ArrayList<IClasspathEntry>(
+                    Arrays.asList(entries));
+
+            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
+            // 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);
                         }
+                    }
+                }
+            }
 
-                        i++;
+            // loop on the projects to add.
+            for (IProject library : bundle.mLibraryProjects) {
+                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;
                     }
 
-                    // get the list of source folders for the library.
-                    ArrayList<IPath> sourceFolderPaths = BaseProjectHelper.getSourceClasspaths(
-                            library);
+                    IPath relativePath = sourceFolder.getProjectRelativePath();
+                    if (SdkConstants.FD_GEN_SOURCES.equals(relativePath.toString())) {
+                        continue;
+                    }
 
-                    // 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) {
-                            continue;
-                        }
+                    // create the linked path
+                    IPath linkedPath = new Path(varName).append(relativePath);
 
-                        IPath relativePath = sourceFolder.getProjectRelativePath();
-                        if (SdkConstants.FD_GEN_SOURCES.equals(relativePath.toString())) {
-                            continue;
-                        }
+                    // 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$
+
                         // create a linked resource for the library using the path var.
-                        IFolder libSrc = project.getFolder(libName + "_" + srcFolderName);  //$NON-NLS-1$
+                        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);
+                        }
 
-                        libSrc.createLink(new Path(varName).append(relativePath),
+                        // 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);
 
-                        // if we were deleting something called exactly the same (which could
-                        // have linked to a different folder), we remove it from the list
-                        // of items to delete
-                        if (toDelete.contains(libSrc)) {
-                            toDelete.remove(libSrc);
-                        }
-
-                        // set some persistent properties on it to know that it was created by ADT.
+                        // 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(libSrc.getFullPath()));
+                        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);
                     }
+                }
+            }
 
-                    // set the new list
-                    javaProject.setRawClasspath(
-                            classpathEntries.toArray(new IClasspathEntry[classpathEntries.size()]),
-                            monitor);
+            if (bundle.mCleanupCPE) {
+                // remove the remaining CPE as they could not be resolved.
+                classpathEntries.removeAll(libCpeList);
+            }
 
-                    for (IResource res : toDelete) {
-                        res.delete(true, monitor);
-                    }
+            // set the new list
+            javaProject.setRawClasspath(
+                    classpathEntries.toArray(new IClasspathEntry[classpathEntries.size()]),
+                    monitor);
 
-                    return Status.OK_STATUS;
-                } catch (CoreException e) {
-                    AdtPlugin.logAndPrintError(e, "Library Project", "Failed to create link between library %1$s and project %2$s: %3$s",
-                            libraryState.getProjectState().getProject().getName(),
-                            projectState.getProject().getName(),
-                            e.getMessage());
-                    return e.getStatus();
+            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);
                 }
             }
-        };
 
-        if (doInJob) {
-            Job job = new Job("Android Library link creation") { //$NON-NLS-1$
-                @Override
-                protected IStatus run(IProgressMonitor monitor) {
-                    return jobRunnable.run(monitor);
-                }
-            };
-            job.setPriority(Job.BUILD);
-            job.schedule();
-        } else {
-            jobRunnable.run(new NullProgressMonitor());
+            return Status.OK_STATUS;
+        } catch (CoreException e) {
+            AdtPlugin.logAndPrintError(e, bundle.mProject.getName(),
+                    "Failed to create library links: %1$s", //$NON-NLS-1$
+                    e.getMessage());
+            return e.getStatus();
+        }
+    }
+
+    /**
+     * 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()}
+     *                {@link IClasspathEntry#CPE_SOURCE} whose {@link IClasspathEntry#getPath()}
+     *                points to a linked folder.
+     * @param rawPath the raw path to compare to. Can be null if <var>path</var> is used instead.
+     * @param path the path to compare to. Can be null if <var>rawPath</var> is used instead.
+     * @return the matching IClasspathEntry or null.
+     */
+    private IClasspathEntry findClasspathEntryMatch(ArrayList<IClasspathEntry> cpeList,
+            IPath rawPath, IPath path) {
+        IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();
+        for (IClasspathEntry cpe : cpeList) {
+            IPath cpePath = cpe.getPath();
+            // test the normal path of the resource.
+            if (path != null && path.equals(cpePath)) {
+                return cpe;
+            }
+
+            IResource res = wsRoot.findMember(cpePath);
+            // getRawLocation returns the path that the linked folder points to.
+            if (rawPath != null && res.getRawLocation().equals(rawPath)) {
+                return cpe;
+            }
+
         }
+        return null;
     }
 
     /**
@@ -1177,138 +1439,176 @@ public final class Sdk  {
      * <p/>This can be done in a job in case the workspace is not locked for resource
      * modification. See <var>doInJob</var>.
      *
-     * @param projectState the {@link ProjectState} for the main project
-     * @param libraryProject the library project that needs to be removed
-     * @param doInJob whether the action must be done in a new {@link Job}.
+     * @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 void unlinkLibrary(final ProjectState projectState, final IProject libraryProject,
-            boolean doInJob) {
-        final IJobRunnable jobRunnable = new IJobRunnable() {
-            public IStatus run(IProgressMonitor monitor) {
-                try {
-                    IProject project = projectState.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;
-                    }
+    private IStatus unlinkLibrary(UnlinkLibraryBundle bundle, IProgressMonitor monitor) {
+        try {
+            IProject project = bundle.mProject.getProject();
 
-                    // 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(libraryProject)) {
-                                list.remove(i);
-                                break;
-                            }
-                        }
+            // 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;
+            }
 
-                        // set the changed list
-                        projectDescription.setDynamicReferences(
-                                list.toArray(new IProject[list.size()]));
+            // 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;
                     }
+                }
 
-                    // 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(libraryProject)) {
-                                    classpathEntries.remove(i);
-                                    toDelete.add(linkedRes);
-                                    continue; // don't increment i
-                                }
-                            }
-                        }
+                // set the changed list
+                projectDescription.setDynamicReferences(
+                        list.toArray(new IProject[list.size()]));
+            }
 
-                        i++;
+            // 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
+                        }
                     }
+                }
 
-                    // set the new list
-                    javaProject.setRawClasspath(
-                            classpathEntries.toArray(new IClasspathEntry[classpathEntries.size()]),
-                            monitor);
+                i++;
+            }
 
-                    // delete the resources that need deleting
-                    for (IResource res : toDelete) {
-                        res.delete(true, monitor);
-                    }
+            // set the new list
+            javaProject.setRawClasspath(
+                    classpathEntries.toArray(new IClasspathEntry[classpathEntries.size()]),
+                    monitor);
 
-                    return Status.OK_STATUS;
-                } catch (CoreException e) {
-                    AdtPlugin.log(e, "Failure to unlink %1$s from %2$s", libraryProject.getName(),
-                            projectState.getProject().getName());
-                    return e.getStatus();
-                }
+            // delete the resources that need deleting
+            for (IResource res : toDelete) {
+                res.delete(true, monitor);
             }
-        };
-
-        if (doInJob) {
-            Job job = new Job("Android Library unlinking") { //$NON-NLS-1$
-                @Override
-                protected IStatus run(IProgressMonitor monitor) {
-                    return jobRunnable.run(monitor);
-                }
-            };
 
-            job.setPriority(Job.BUILD);
-            job.schedule();
-        } else {
-            jobRunnable.run(new NullProgressMonitor());
+            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();
         }
     }
 
     /**
-     * Triggers a project recompilation in a new {@link Job}. This is useful when the
-     * tree is locked and the {@link IProject#build(int, IProgressMonitor)} call would failed.
-     * @param project the project to recompile.
+     * 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.
+     * @param libraries the list of new/updated library projects.
      */
-    private void recompile(final IProject project) {
-        Job job = new Job("Project recompilation trigger") { //$NON-NLS-1$
-            @Override
-            protected IStatus run(IProgressMonitor monitor) {
-                try {
-                    project.build( IncrementalProjectBuilder.FULL_BUILD, null);
-                    return Status.OK_STATUS;
-                } catch (CoreException e) {
-                    return e.getStatus();
+    private void updateProjectsWithNewLibraries(List<ProjectState> libraries) {
+        if (libraries.size() == 0) {
+            return;
+        }
+
+        ArrayList<ProjectState> updatedLibraries = new ArrayList<ProjectState>();
+        synchronized (sLock) {
+            // for each projects, look for projects that depend on it, and update them.
+            // 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>();
+
+                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
+                    // done yet.
+                    // However what matters here is that the library is a dependency,
+                    // period. If the library project was updated, then we redo the link,
+                    // with all indirect dependencies (which *have* changed, since this is
+                    // what this method is all about.)
+                    // 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) {
+                        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 (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 this updated project is a library, add it to the list, so that
+                    // projects depending on it get updated too.
+                    if (projectState.isLibrary() &&
+                            updatedLibraries.contains(projectState) == false) {
+                        updatedLibraries.add(projectState);
+                    }
                 }
             }
-        };
+        }
 
-        job.setPriority(Job.BUILD);
-        job.schedule();
+        // done, but there may be updated projects that were libraries, so we need to do the same
+        // for this libraries, to update the project there were depending on.
+        updateProjectsWithNewLibraries(updatedLibraries);
     }
 
     /**
index 9132c7b..7192196 100644 (file)
@@ -6,7 +6,7 @@
        <classpathentry kind="lib" path="libs/jfreechart-1.0.9.jar"/>
        <classpathentry kind="lib" path="libs/jcommon-1.0.12.jar"/>
        <classpathentry kind="lib" path="libs/jfreechart-1.0.9-swt.jar"/>
-       <classpathentry kind="lib" path="libs/ddmlib.jar"/>
-       <classpathentry kind="lib" path="libs/ddmuilib.jar"/>
+       <classpathentry kind="lib" path="libs/ddmlib.jar" sourcepath="/ddmlib"/>
+       <classpathentry kind="lib" path="libs/ddmuilib.jar" sourcepath="/ddmuilib"/>
        <classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/icons/device.png b/eclipse/plugins/com.android.ide.eclipse.ddms/icons/device.png
new file mode 100644 (file)
index 0000000..7dbbbb6
Binary files /dev/null and b/eclipse/plugins/com.android.ide.eclipse.ddms/icons/device.png differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/icons/emulator.png b/eclipse/plugins/com.android.ide.eclipse.ddms/icons/emulator.png
new file mode 100644 (file)
index 0000000..a718042
Binary files /dev/null and b/eclipse/plugins/com.android.ide.eclipse.ddms/icons/emulator.png differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/icons/heap.png b/eclipse/plugins/com.android.ide.eclipse.ddms/icons/heap.png
new file mode 100644 (file)
index 0000000..e3aa3f0
Binary files /dev/null and b/eclipse/plugins/com.android.ide.eclipse.ddms/icons/heap.png differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/icons/thread.png b/eclipse/plugins/com.android.ide.eclipse.ddms/icons/thread.png
new file mode 100644 (file)
index 0000000..ac839e8
Binary files /dev/null and b/eclipse/plugins/com.android.ide.eclipse.ddms/icons/thread.png differ