Fix to library projects in Eclipse.

- Properly setup source attachment for library jar files.
- Force refresh the bin folder of libraries to make sure main projects
  pick up the changes.
- Force update the library container of parent projects when a library is
  recompile. This makes sure the parent projects are recompiled when a library
  code change.
- Fix how we check the libraries' res folders exist. This ensures we pick up
  all the res folders even if Eclipse isn't aware of them due to refresh issues.

Change-Id: Ie4b7ae770cfc782589a983ec654a11a1dfa4ef2d
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java
index 3209ec1..dad3c5c 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java
@@ -214,27 +214,30 @@
             assetsFolder = null;
         }
 
-        IPath cacheLocation = cacheFolder.getLocation();
+        // list of res folder (main project + maybe libraries)
+        ArrayList<String> osResPaths = new ArrayList<String>();
+
         IPath resLocation = resFolder.getLocation();
         IPath manifestLocation = manifestFile.getLocation();
 
         if (resLocation != null && manifestLocation != null) {
-            // list of res folder (main project + maybe libraries)
-            ArrayList<String> osResPaths = new ArrayList<String>();
-            osResPaths.add(cacheLocation.toOSString()); // PNG crunch cache
-            osResPaths.add(resLocation.toOSString()); //main project
 
-            // libraries?
+            // png cache folder first.
+            addFolderToList(osResPaths, cacheFolder);
+
+            // regular res folder next.
+            osResPaths.add(resLocation.toOSString());
+
+            // then libraries
             if (libProjects != null) {
                 for (IProject lib : libProjects) {
+                    // png cache folder first
                     IFolder libCacheFolder = lib.getFolder(AdtConstants.WS_CRUNCHCACHE);
-                    if (libCacheFolder.exists()) {
-                        osResPaths.add(libCacheFolder.getLocation().toOSString());
-                    }
+                    addFolderToList(osResPaths, libCacheFolder);
+
+                    // regular res folder next.
                     IFolder libResFolder = lib.getFolder(AdtConstants.WS_RESOURCES);
-                    if (libResFolder.exists()) {
-                        osResPaths.add(libResFolder.getLocation().toOSString());
-                    }
+                    addFolderToList(osResPaths, libResFolder);
                 }
             }
 
@@ -260,6 +263,19 @@
     }
 
     /**
+     * Adds os path of a folder to a list only if the folder actually exists.
+     * @param pathList
+     * @param folder
+     */
+    private void addFolderToList(List<String> pathList, IFolder folder) {
+        // use a File instead of the IFolder API to ignore workspace refresh issue.
+        File testFile = new File(folder.getLocation().toOSString());
+        if (testFile.isDirectory()) {
+            pathList.add(testFile.getAbsolutePath());
+        }
+    }
+
+    /**
      * Makes a final package signed with the debug key.
      *
      * Packages the dex files, the temporary resource file into the final package file.
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java
index 6c2af35..f3d546a 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java
@@ -31,6 +31,7 @@
 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
 import com.android.ide.eclipse.adt.internal.project.ApkInstallManager;
 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
+import com.android.ide.eclipse.adt.internal.project.LibraryClasspathContainerInitializer;
 import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
 import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
@@ -294,6 +295,7 @@
 
             // get the android output folder
             IFolder androidOutputFolder = BaseProjectHelper.getAndroidOutputFolder(project);
+            IFolder resOutputFolder = androidOutputFolder.getFolder(SdkConstants.FD_RES);
 
             // now we need to get the classpath list
             List<IPath> sourceList = BaseProjectHelper.getSourceClasspaths(javaProject);
@@ -410,15 +412,6 @@
                     mConvertToDex = true;
                 }
 
-                if (mConvertToDex) { // in this case this means some class files changed and
-                                     // we need to update the jar file.
-                    IFolder javaOutputFolder = BaseProjectHelper.getJavaOutputFolder(project);
-
-                    writeLibraryPackage(jarIFile, project, javaOutputFolder,
-                            referencedJavaProjects);
-                    saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex = false);
-                }
-
                 // also update the crunch cache if needed.
                 if (mUpdateCrunchCache) {
                     BuildHelper helper = new BuildHelper(project,
@@ -426,6 +419,27 @@
                             true /*debugMode*/,
                             AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE);
                     updateCrunchCache(project, helper);
+
+                    // refresh recursively bin/res folder
+                    resOutputFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor);
+                }
+
+                if (mConvertToDex) { // in this case this means some class files changed and
+                                     // we need to update the jar file.
+                    IFolder javaOutputFolder = BaseProjectHelper.getJavaOutputFolder(project);
+
+                    writeLibraryPackage(jarIFile, project, javaOutputFolder,
+                            referencedJavaProjects);
+                    saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex = false);
+
+                    // refresh the bin folder content with no recursion to update the library
+                    // jar file.
+                    androidOutputFolder.refreshLocal(IResource.DEPTH_ONE, monitor);
+
+                    // Also update the projects. The only way to force recompile them is to
+                    // reset the library container.
+                    List<ProjectState> parentProjects = projectState.getParentProjects();
+                    LibraryClasspathContainerInitializer.updateProject(parentProjects);
                 }
 
                 return allRefProjects;
@@ -549,6 +563,9 @@
                     if (updateCrunchCache(project, helper) == false) {
                         return allRefProjects;
                     }
+
+                    // refresh recursively bin/res folder
+                    resOutputFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor);
                 }
 
                 // Check if we need to package the resources.
@@ -690,7 +707,7 @@
 
                 // we are done.
 
-                // get the resource to bin
+                // refresh the bin folder content with no recursion.
                 androidOutputFolder.refreshLocal(IResource.DEPTH_ONE, monitor);
 
                 // build has been done. reset the state of the builder
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java
index 6540038..e31d70d 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java
@@ -24,6 +24,8 @@
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.resources.IFolder;
 import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.NullProgressMonitor;
@@ -70,6 +72,26 @@
         }
     }
 
+    /**
+     * Updates the {@link IJavaProject} objects with new library.
+     * @param androidProjects the projects to update.
+     * @return <code>true</code> if success, <code>false</code> otherwise.
+     */
+    public static boolean updateProject(List<ProjectState> projects) {
+        List<IJavaProject> javaProjectList = new ArrayList<IJavaProject>(projects.size());
+        for (ProjectState p : projects) {
+            IJavaProject javaProject = JavaCore.create(p.getProject());
+            if (javaProject != null) {
+                javaProjectList.add(javaProject);
+            }
+        }
+
+        IJavaProject[] javaProjects = javaProjectList.toArray(
+                new IJavaProject[javaProjectList.size()]);
+
+        return updateProjects(javaProjects);
+    }
+
     @Override
     public void initialize(IPath containerPath, IJavaProject project) throws CoreException {
         if (AdtConstants.CONTAINER_LIBRARIES.equals(containerPath.toString())) {
@@ -132,6 +154,8 @@
 
         List<IClasspathEntry> entries = new ArrayList<IClasspathEntry>();
 
+        IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
+
         List<IProject> libProjects = state.getFullLibraryProjects();
         for (IProject libProject : libProjects) {
             // get the project output
@@ -141,10 +165,22 @@
                 IFile jarIFile = outputFolder.getFile(libProject.getName().toLowerCase() +
                         AdtConstants.DOT_JAR);
 
+                // get the source folder for the library project
+                List<IPath> srcs = BaseProjectHelper.getSourceClasspaths(libProject);
+                // find the first non-derived source folder.
+                IPath sourceFolder = null;
+                for (IPath src : srcs) {
+                    IFolder srcFolder = workspaceRoot.getFolder(src);
+                    if (srcFolder.isDerived() == false) {
+                        sourceFolder = src;
+                        break;
+                    }
+                }
+
                 IClasspathEntry entry = JavaCore.newLibraryEntry(
                         jarIFile.getLocation(),
-                        libProject.getLocation(), // source attachment path
-                        null);                    // default source attachment root path.
+                        sourceFolder, // source attachment path
+                        null);        // default source attachment root path.
 
                 entries.add(entry);
             }