blob: 8fbee4089eb7ca9df101b6db546617415217c513 [file] [log] [blame]
/*
* 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);
}
}
}
}
}