diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java | 641 |
1 files changed, 641 insertions, 0 deletions
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 new file mode 100644 index 000000000..8fbee4089 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java @@ -0,0 +1,641 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.project; + +import static com.android.ide.eclipse.adt.AdtConstants.CONTAINER_DEPENDENCIES; + +import com.android.SdkConstants; +import com.android.ide.common.sdk.LoadStatus; +import com.android.ide.eclipse.adt.AdtConstants; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.AndroidPrintStream; +import com.android.ide.eclipse.adt.internal.sdk.ProjectState; +import com.android.ide.eclipse.adt.internal.sdk.Sdk; +import com.android.sdklib.BuildToolInfo; +import com.android.sdklib.build.JarListSanitizer; +import com.android.sdklib.build.JarListSanitizer.DifferentLibException; +import com.android.sdklib.build.JarListSanitizer.Sha1Exception; +import com.android.sdklib.build.RenderScriptProcessor; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +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; +import org.eclipse.core.runtime.Path; +import org.eclipse.jdt.core.IAccessRule; +import org.eclipse.jdt.core.IClasspathAttribute; +import org.eclipse.jdt.core.IClasspathContainer; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Properties; +import java.util.Set; + +public class LibraryClasspathContainerInitializer extends BaseClasspathContainerInitializer { + + private final static String ATTR_SRC = "src"; //$NON-NLS-1$ + private final static String ATTR_DOC = "doc"; //$NON-NLS-1$ + private final static String DOT_PROPERTIES = ".properties"; //$NON-NLS-1$ + + public LibraryClasspathContainerInitializer() { + } + + /** + * 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 updateProjects(IJavaProject[] androidProjects) { + try { + // Allocate a new AndroidClasspathContainer, and associate it to the library + // container id for each projects. + int projectCount = androidProjects.length; + + IClasspathContainer[] libraryContainers = new IClasspathContainer[projectCount]; + IClasspathContainer[] dependencyContainers = new IClasspathContainer[projectCount]; + for (int i = 0 ; i < projectCount; i++) { + libraryContainers[i] = allocateLibraryContainer(androidProjects[i]); + dependencyContainers[i] = allocateDependencyContainer(androidProjects[i]); + } + + // give each project their new container in one call. + JavaCore.setClasspathContainer( + new Path(AdtConstants.CONTAINER_PRIVATE_LIBRARIES), + androidProjects, libraryContainers, new NullProgressMonitor()); + + JavaCore.setClasspathContainer( + new Path(AdtConstants.CONTAINER_DEPENDENCIES), + androidProjects, dependencyContainers, new NullProgressMonitor()); + return true; + } catch (JavaModelException e) { + return false; + } + } + + /** + * 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_PRIVATE_LIBRARIES.equals(containerPath.toString())) { + IClasspathContainer libraries = allocateLibraryContainer(project); + if (libraries != null) { + JavaCore.setClasspathContainer(new Path(AdtConstants.CONTAINER_PRIVATE_LIBRARIES), + new IJavaProject[] { project }, + new IClasspathContainer[] { libraries }, + new NullProgressMonitor()); + } + + } else if(AdtConstants.CONTAINER_DEPENDENCIES.equals(containerPath.toString())) { + IClasspathContainer dependencies = allocateDependencyContainer(project); + if (dependencies != null) { + JavaCore.setClasspathContainer(new Path(AdtConstants.CONTAINER_DEPENDENCIES), + new IJavaProject[] { project }, + new IClasspathContainer[] { dependencies }, + new NullProgressMonitor()); + } + } + } + + private static IClasspathContainer allocateLibraryContainer(IJavaProject javaProject) { + final IProject iProject = javaProject.getProject(); + + // check if the project has a valid target. + ProjectState state = Sdk.getProjectState(iProject); + if (state == null) { + // getProjectState should already have logged an error. Just bail out. + return null; + } + + /* + * At this point we're going to gather a list of all that need to go in the + * dependency container. + * - Library project outputs (direct and indirect) + * - Java project output (those can be indirectly referenced through library projects + * or other other Java projects) + * - Jar files: + * + inside this project's libs/ + * + inside the library projects' libs/ + * + inside the referenced Java projects' classpath + */ + List<IClasspathEntry> entries = new ArrayList<IClasspathEntry>(); + + // list of java project dependencies and jar files that will be built while + // going through the library projects. + Set<File> jarFiles = new HashSet<File>(); + Set<IProject> refProjects = new HashSet<IProject>(); + + // process all the libraries + + List<IProject> libProjects = state.getFullLibraryProjects(); + for (IProject libProject : libProjects) { + // process all of the library project's dependencies + getDependencyListFromClasspath(libProject, refProjects, jarFiles, true); + } + + // now process this projects' referenced projects only. + processReferencedProjects(iProject, refProjects, jarFiles); + + // and the content of its libs folder + getJarListFromLibsFolder(iProject, jarFiles); + + // now add a classpath entry for each Java project (this is a set so dups are already + // removed) + for (IProject p : refProjects) { + entries.add(JavaCore.newProjectEntry(p.getFullPath(), true /*isExported*/)); + } + + entries.addAll(convertJarsToClasspathEntries(iProject, jarFiles)); + + return allocateContainer(javaProject, entries, new Path(AdtConstants.CONTAINER_PRIVATE_LIBRARIES), + "Android Private Libraries"); + } + + private static List<IClasspathEntry> convertJarsToClasspathEntries(final IProject iProject, + Set<File> jarFiles) { + List<IClasspathEntry> entries = new ArrayList<IClasspathEntry>(jarFiles.size()); + + // and process the jar files list, but first sanitize it to remove dups. + JarListSanitizer sanitizer = new JarListSanitizer( + iProject.getFolder(SdkConstants.FD_OUTPUT).getLocation().toFile(), + new AndroidPrintStream(iProject, null /*prefix*/, + AdtPlugin.getOutStream())); + + String errorMessage = null; + + try { + List<File> sanitizedList = sanitizer.sanitize(jarFiles); + + for (File jarFile : sanitizedList) { + if (jarFile instanceof CPEFile) { + CPEFile cpeFile = (CPEFile) jarFile; + IClasspathEntry e = cpeFile.getClasspathEntry(); + + entries.add(JavaCore.newLibraryEntry( + e.getPath(), + e.getSourceAttachmentPath(), + e.getSourceAttachmentRootPath(), + e.getAccessRules(), + e.getExtraAttributes(), + true /*isExported*/)); + } else { + String jarPath = jarFile.getAbsolutePath(); + + IPath sourceAttachmentPath = null; + IClasspathAttribute javaDocAttribute = null; + + File jarProperties = new File(jarPath + DOT_PROPERTIES); + if (jarProperties.isFile()) { + Properties p = new Properties(); + InputStream is = null; + try { + p.load(is = new FileInputStream(jarProperties)); + + String value = p.getProperty(ATTR_SRC); + if (value != null) { + File srcPath = getFile(jarFile, value); + + if (srcPath.exists()) { + sourceAttachmentPath = new Path(srcPath.getAbsolutePath()); + } + } + + value = p.getProperty(ATTR_DOC); + if (value != null) { + File docPath = getFile(jarFile, value); + if (docPath.exists()) { + try { + javaDocAttribute = JavaCore.newClasspathAttribute( + IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME, + docPath.toURI().toURL().toString()); + } catch (MalformedURLException e) { + AdtPlugin.log(e, "Failed to process 'doc' attribute for %s", + jarProperties.getAbsolutePath()); + } + } + } + + } catch (FileNotFoundException e) { + // shouldn't happen since we check upfront + } catch (IOException e) { + AdtPlugin.log(e, "Failed to read %s", jarProperties.getAbsolutePath()); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + // ignore + } + } + } + } + + if (javaDocAttribute != null) { + entries.add(JavaCore.newLibraryEntry(new Path(jarPath), + sourceAttachmentPath, null /*sourceAttachmentRootPath*/, + new IAccessRule[0], + new IClasspathAttribute[] { javaDocAttribute }, + true /*isExported*/)); + } else { + entries.add(JavaCore.newLibraryEntry(new Path(jarPath), + sourceAttachmentPath, null /*sourceAttachmentRootPath*/, + true /*isExported*/)); + } + } + } + } catch (DifferentLibException e) { + errorMessage = e.getMessage(); + AdtPlugin.printErrorToConsole(iProject, (Object[]) e.getDetails()); + } catch (Sha1Exception e) { + errorMessage = e.getMessage(); + } + + processError(iProject, errorMessage, AdtConstants.MARKER_DEPENDENCY, + true /*outputToConsole*/); + + return entries; + } + + private static IClasspathContainer allocateDependencyContainer(IJavaProject javaProject) { + final IProject iProject = javaProject.getProject(); + final List<IClasspathEntry> entries = new ArrayList<IClasspathEntry>(); + final Set<File> jarFiles = new HashSet<File>(); + final IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); + + AdtPlugin plugin = AdtPlugin.getDefault(); + if (plugin == null) { // This is totally weird, but I've seen it happen! + return null; + } + + synchronized (Sdk.getLock()) { + boolean sdkIsLoaded = plugin.getSdkLoadStatus() == LoadStatus.LOADED; + + // check if the project has a valid target. + final ProjectState state = Sdk.getProjectState(iProject); + if (state == null) { + // getProjectState should already have logged an error. Just bail out. + return null; + } + + // annotations support for older version of android + if (state.getTarget() != null && state.getTarget().getVersion().getApiLevel() <= 15) { + File annotationsJar = new File(Sdk.getCurrent().getSdkOsLocation(), + SdkConstants.FD_TOOLS + File.separator + SdkConstants.FD_SUPPORT + + File.separator + SdkConstants.FN_ANNOTATIONS_JAR); + + jarFiles.add(annotationsJar); + } + + if (state.getRenderScriptSupportMode()) { + if (!sdkIsLoaded) { + return null; + } + BuildToolInfo buildToolInfo = state.getBuildToolInfo(); + if (buildToolInfo == null) { + buildToolInfo = Sdk.getCurrent().getLatestBuildTool(); + + if (buildToolInfo == null) { + return null; + } + } + + File renderScriptSupportJar = RenderScriptProcessor.getSupportJar( + buildToolInfo.getLocation().getAbsolutePath()); + + jarFiles.add(renderScriptSupportJar); + } + + // process all the libraries + + List<IProject> libProjects = state.getFullLibraryProjects(); + for (IProject libProject : libProjects) { + // get the project output + IFolder outputFolder = BaseProjectHelper.getAndroidOutputFolder(libProject); + + if (outputFolder != null) { // can happen when closing/deleting a library) + IFile jarIFile = outputFolder.getFile(libProject.getName().toLowerCase() + + SdkConstants.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; + } + } + + // we can directly add a CPE for this jar as there's no risk of a duplicate. + IClasspathEntry entry = JavaCore.newLibraryEntry( + jarIFile.getLocation(), + sourceFolder, // source attachment path + null, // default source attachment root path. + true /*isExported*/); + + entries.add(entry); + } + } + + entries.addAll(convertJarsToClasspathEntries(iProject, jarFiles)); + + return allocateContainer(javaProject, entries, new Path(CONTAINER_DEPENDENCIES), + "Android Dependencies"); + } + } + + private static IClasspathContainer allocateContainer(IJavaProject javaProject, + List<IClasspathEntry> entries, IPath id, String description) { + + if (AdtPlugin.getDefault() == null) { // This is totally weird, but I've seen it happen! + return null; + } + + // First check that the project has a library-type container. + try { + IClasspathEntry[] rawClasspath = javaProject.getRawClasspath(); + final IClasspathEntry[] oldRawClasspath = rawClasspath; + + boolean foundContainer = false; + for (IClasspathEntry entry : rawClasspath) { + // get the entry and kind + final int kind = entry.getEntryKind(); + + if (kind == IClasspathEntry.CPE_CONTAINER) { + String path = entry.getPath().toString(); + String idString = id.toString(); + if (idString.equals(path)) { + foundContainer = true; + break; + } + } + } + + // if there isn't any, add it. + if (foundContainer == false) { + // add the android container to the array + rawClasspath = ProjectHelper.addEntryToClasspath(rawClasspath, + JavaCore.newContainerEntry(id, true /*isExported*/)); + } + + // set the new list of entries to the project + if (rawClasspath != oldRawClasspath) { + javaProject.setRawClasspath(rawClasspath, new NullProgressMonitor()); + } + } catch (JavaModelException e) { + // This really shouldn't happen, but if it does, simply return null (the calling + // method will fails as well) + return null; + } + + return new AndroidClasspathContainer( + entries.toArray(new IClasspathEntry[entries.size()]), + id, + description, + IClasspathContainer.K_APPLICATION); + } + + private static File getFile(File root, String value) { + File file = new File(value); + if (file.isAbsolute() == false) { + file = new File(root.getParentFile(), value); + } + + return file; + } + + /** + * Finds all the jar files inside a project's libs folder. + * @param project + * @param jarFiles + */ + private static void getJarListFromLibsFolder(IProject project, Set<File> jarFiles) { + IFolder libsFolder = project.getFolder(SdkConstants.FD_NATIVE_LIBS); + if (libsFolder.exists()) { + try { + IResource[] members = libsFolder.members(); + for (IResource member : members) { + if (member.getType() == IResource.FILE && + SdkConstants.EXT_JAR.equalsIgnoreCase(member.getFileExtension())) { + IPath location = member.getLocation(); + if (location != null) { + jarFiles.add(location.toFile()); + } + } + } + } catch (CoreException e) { + // can't get the list? ignore this folder. + } + } + } + + /** + * Process reference projects from the main projects to add indirect dependencies coming + * from Java project. + * @param project the main project + * @param projects the project list to add to + * @param jarFiles the jar list to add to. + */ + private static void processReferencedProjects(IProject project, + Set<IProject> projects, Set<File> jarFiles) { + try { + IProject[] refs = project.getReferencedProjects(); + for (IProject p : refs) { + // ignore if it's an Android project, or if it's not a Java + // Project + if (p.hasNature(JavaCore.NATURE_ID) + && p.hasNature(AdtConstants.NATURE_DEFAULT) == false) { + + // process this project's dependencies + getDependencyListFromClasspath(p, projects, jarFiles, true /*includeJarFiles*/); + } + } + } catch (CoreException e) { + // can't get the referenced projects? ignore + } + } + + /** + * Finds all the dependencies of a given project and add them to a project list and + * a jar list. + * Only classpath entries that are exported are added, and only Java project (not Android + * project) are added. + * + * @param project the project to query + * @param projects the referenced project list to add to + * @param jarFiles the jar list to add to + * @param includeJarFiles whether to include jar files or just projects. This is useful when + * calling on an Android project (value should be <code>false</code>) + */ + private static void getDependencyListFromClasspath(IProject project, Set<IProject> projects, + Set<File> jarFiles, boolean includeJarFiles) { + IJavaProject javaProject = JavaCore.create(project); + IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); + + // we could use IJavaProject.getResolvedClasspath directly, but we actually + // want to see the containers themselves. + IClasspathEntry[] classpaths = javaProject.readRawClasspath(); + if (classpaths != null) { + for (IClasspathEntry e : classpaths) { + // ignore entries that are not exported + if (!e.getPath().toString().equals(CONTAINER_DEPENDENCIES) && e.isExported()) { + processCPE(e, javaProject, wsRoot, projects, jarFiles, includeJarFiles); + } + } + } + } + + /** + * Processes a {@link IClasspathEntry} and add it to one of the list if applicable. + * @param entry the entry to process + * @param javaProject the {@link IJavaProject} from which this entry came. + * @param wsRoot the {@link IWorkspaceRoot} + * @param projects the project list to add to + * @param jarFiles the jar list to add to + * @param includeJarFiles whether to include jar files or just projects. This is useful when + * calling on an Android project (value should be <code>false</code>) + */ + private static void processCPE(IClasspathEntry entry, IJavaProject javaProject, + IWorkspaceRoot wsRoot, + Set<IProject> projects, Set<File> jarFiles, boolean includeJarFiles) { + + // if this is a classpath variable reference, we resolve it. + if (entry.getEntryKind() == IClasspathEntry.CPE_VARIABLE) { + entry = JavaCore.getResolvedClasspathEntry(entry); + } + + if (entry.getEntryKind() == IClasspathEntry.CPE_PROJECT) { + IProject refProject = wsRoot.getProject(entry.getPath().lastSegment()); + try { + // ignore if it's an Android project, or if it's not a Java Project + if (refProject.hasNature(JavaCore.NATURE_ID) && + refProject.hasNature(AdtConstants.NATURE_DEFAULT) == false) { + // add this project to the list + projects.add(refProject); + + // also get the dependency from this project. + getDependencyListFromClasspath(refProject, projects, jarFiles, + true /*includeJarFiles*/); + } + } catch (CoreException exception) { + // can't query the project nature? ignore + } + } else if (entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) { + if (includeJarFiles) { + handleClasspathLibrary(entry, wsRoot, jarFiles); + } + } else if (entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) { + // get the container and its content + try { + IClasspathContainer container = JavaCore.getClasspathContainer( + entry.getPath(), javaProject); + // ignore the system and default_system types as they represent + // libraries that are part of the runtime. + if (container != null && + container.getKind() == IClasspathContainer.K_APPLICATION) { + IClasspathEntry[] entries = container.getClasspathEntries(); + for (IClasspathEntry cpe : entries) { + processCPE(cpe, javaProject, wsRoot, projects, jarFiles, includeJarFiles); + } + } + } catch (JavaModelException jme) { + // can't resolve the container? ignore it. + AdtPlugin.log(jme, "Failed to resolve ClasspathContainer: %s", entry.getPath()); + } + } + } + + private static final class CPEFile extends File { + private static final long serialVersionUID = 1L; + + private final IClasspathEntry mClasspathEntry; + + public CPEFile(String pathname, IClasspathEntry classpathEntry) { + super(pathname); + mClasspathEntry = classpathEntry; + } + + public CPEFile(File file, IClasspathEntry classpathEntry) { + super(file.getAbsolutePath()); + mClasspathEntry = classpathEntry; + } + + public IClasspathEntry getClasspathEntry() { + return mClasspathEntry; + } + } + + private static void handleClasspathLibrary(IClasspathEntry e, IWorkspaceRoot wsRoot, + Set<File> jarFiles) { + // get the IPath + IPath path = e.getPath(); + + IResource resource = wsRoot.findMember(path); + + if (SdkConstants.EXT_JAR.equalsIgnoreCase(path.getFileExtension())) { + // case of a jar file (which could be relative to the workspace or a full path) + if (resource != null && resource.exists() && + resource.getType() == IResource.FILE) { + jarFiles.add(new CPEFile(resource.getLocation().toFile(), e)); + } else { + // if the jar path doesn't match a workspace resource, + // then we get an OSString and check if this links to a valid file. + String osFullPath = path.toOSString(); + + File f = new CPEFile(osFullPath, e); + if (f.isFile()) { + jarFiles.add(f); + } + } + } + } +} |