AndroidClasspathContainerInitializerpublic class AndroidClasspathContainerInitializer extends org.eclipse.jdt.core.ClasspathContainerInitializer Classpath container initializer responsible for binding {@link AndroidClasspathContainer} to
{@link IProject}s. This removes the hard-coded path to the android.jar. |
Fields Summary |
---|
private static final String | CONTAINER_IDThe container id for the android framework jar file | private static final String | PATH_SEPARATORpath separator to store multiple paths in a single property. This is guaranteed to not
be in a path. | private static final String | PROPERTY_CONTAINER_CACHE | private static final String | PROPERTY_TARGET_NAME | private static final String | CACHE_VERSION | private static final String | CACHE_VERSION_SEP | private static final int | CACHE_INDEX_JAR | private static final int | CACHE_INDEX_SRC | private static final int | CACHE_INDEX_DOCS_URI | private static final int | CACHE_INDEX_OPT_DOCS_URI | private static final int | CACHE_INDEX_ADD_ON_START |
Constructors Summary |
---|
public AndroidClasspathContainerInitializer()
// pass
|
Methods Summary |
---|
private static org.eclipse.jdt.core.IClasspathContainer | allocateAndroidContainer(org.eclipse.jdt.core.IJavaProject javaProject)Allocates and returns an {@link AndroidClasspathContainer} object with the proper
path to the framework jar file.
final IProject iProject = javaProject.getProject();
String markerMessage = null;
boolean outputToConsole = true;
try {
AdtPlugin plugin = AdtPlugin.getDefault();
// get the lock object for project manipulation during SDK load.
Object lock = plugin.getSdkLockObject();
synchronized (lock) {
boolean sdkIsLoaded = plugin.getSdkLoadStatus() == LoadStatus.LOADED;
// check if the project has a valid target.
IAndroidTarget target = null;
if (sdkIsLoaded) {
target = Sdk.getCurrent().getTarget(iProject);
}
// if we are loaded and the target is non null, we create a valid ClassPathContainer
if (sdkIsLoaded && target != null) {
String targetName = target.getClasspathName();
return new AndroidClasspathContainer(
createClasspathEntries(iProject, target, targetName),
new Path(CONTAINER_ID), targetName);
}
// In case of error, we'll try different thing to provide the best error message
// possible.
// Get the project's target's hash string (if it exists)
String hashString = Sdk.getProjectTargetHashString(iProject);
if (hashString == null || hashString.length() == 0) {
// if there is no hash string we only show this if the SDK is loaded.
// For a project opened at start-up with no target, this would be displayed
// twice, once when the project is opened, and once after the SDK has
// finished loading.
// By testing the sdk is loaded, we only show this once in the console.
if (sdkIsLoaded) {
markerMessage = String.format(
"Project has no target set. Edit the project properties to set one.");
}
} else if (sdkIsLoaded) {
markerMessage = String.format(
"Unable to resolve target '%s'", hashString);
} else {
// this is the case where there is a hashString but the SDK is not yet
// loaded and therefore we can't get the target yet.
// We check if there is a cache of the needed information.
AndroidClasspathContainer container = getContainerFromCache(iProject);
if (container == null) {
// either the cache was wrong (ie folder does not exists anymore), or
// there was no cache. In this case we need to make sure the project
// is resolved again after the SDK is loaded.
plugin.setProjectToResolve(javaProject);
markerMessage = String.format(
"Unable to resolve target '%s' until the SDK is loaded.",
hashString);
// let's not log this one to the console as it will happen at every boot,
// and it's expected. (we do keep the error marker though).
outputToConsole = false;
} else {
// we created a container from the cache, so we register the project
// to be checked for cache validity once the SDK is loaded
plugin.setProjectToCheck(javaProject);
// and return the container
return container;
}
}
// return a dummy container to replace the one we may have had before.
// It'll be replaced by the real when if/when the target is resolved if/when the
// SDK finishes loading.
return new IClasspathContainer() {
public IClasspathEntry[] getClasspathEntries() {
return new IClasspathEntry[0];
}
public String getDescription() {
return "Unable to get system library for the project";
}
public int getKind() {
return IClasspathContainer.K_DEFAULT_SYSTEM;
}
public IPath getPath() {
return null;
}
};
}
} finally {
if (markerMessage != null) {
// log the error and put the marker on the project if we can.
if (outputToConsole) {
AdtPlugin.printErrorToConsole(iProject, markerMessage);
}
try {
BaseProjectHelper.addMarker(iProject, AdtConstants.MARKER_TARGET, markerMessage,
-1, IMarker.SEVERITY_ERROR, IMarker.PRIORITY_HIGH);
} catch (CoreException e) {
// In some cases, the workspace may be locked for modification when we
// pass here.
// We schedule a new job to put the marker after.
final String fmessage = markerMessage;
Job markerJob = new Job("Android SDK: Resolving error markers") {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
BaseProjectHelper.addMarker(iProject, AdtConstants.MARKER_TARGET,
fmessage, -1, IMarker.SEVERITY_ERROR,
IMarker.PRIORITY_HIGH);
} catch (CoreException e2) {
return e2.getStatus();
}
return Status.OK_STATUS;
}
};
// build jobs are run after other interactive jobs
markerJob.setPriority(Job.BUILD);
markerJob.schedule();
}
} else {
// no error, remove potential MARKER_TARGETs.
try {
if (iProject.exists()) {
iProject.deleteMarkers(AdtConstants.MARKER_TARGET, true,
IResource.DEPTH_INFINITE);
}
} catch (CoreException ce) {
// In some cases, the workspace may be locked for modification when we pass
// here, so we schedule a new job to put the marker after.
Job markerJob = new Job("Android SDK: Resolving error markers") {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
iProject.deleteMarkers(AdtConstants.MARKER_TARGET, true,
IResource.DEPTH_INFINITE);
} catch (CoreException e2) {
return e2.getStatus();
}
return Status.OK_STATUS;
}
};
// build jobs are run after other interactive jobs
markerJob.setPriority(Job.BUILD);
markerJob.schedule();
}
}
}
| public static boolean | checkPath(org.eclipse.core.runtime.IPath path)Checks the {@link IPath} objects against the android framework container id and
returns true if they are identical.
return CONTAINER_ID.equals(path.toString());
| public static void | checkProjectsCache(java.util.ArrayList projects)Checks the projects' caches. If the cache was valid, the project is removed from the list.
int i = 0;
projectLoop: while (i < projects.size()) {
IJavaProject javaProject = projects.get(i);
IProject iProject = javaProject.getProject();
// check if the project is opened
if (iProject.isOpen() == false) {
// remove from the list
// we do not increment i in this case.
projects.remove(i);
continue;
}
// get the target from the project and its paths
IAndroidTarget target = Sdk.getCurrent().getTarget(javaProject.getProject());
if (target == null) {
// this is really not supposed to happen. This would mean there are cached paths,
// but default.properties was deleted. Keep the project in the list to force
// a resolve which will display the error.
i++;
continue;
}
String[] targetPaths = getTargetPaths(target);
// now get the cached paths
String cache = ProjectHelper.loadStringProperty(iProject, PROPERTY_CONTAINER_CACHE);
if (cache == null) {
// this should not happen. We'll force resolve again anyway.
i++;
continue;
}
String[] cachedPaths = cache.split(Pattern.quote(PATH_SEPARATOR));
if (cachedPaths.length < 3 || cachedPaths.length == 4) {
// paths length is wrong. simply resolve the project again
i++;
continue;
}
// Now we compare the paths. The first 4 can be compared directly.
// because of case sensitiveness we need to use File objects
if (targetPaths.length != cachedPaths.length) {
// different paths, force resolve again.
i++;
continue;
}
// compare the main paths (android.jar, main sources, main javadoc)
if (new File(targetPaths[CACHE_INDEX_JAR]).equals(
new File(cachedPaths[CACHE_INDEX_JAR])) == false ||
new File(targetPaths[CACHE_INDEX_SRC]).equals(
new File(cachedPaths[CACHE_INDEX_SRC])) == false ||
new File(targetPaths[CACHE_INDEX_DOCS_URI]).equals(
new File(cachedPaths[CACHE_INDEX_DOCS_URI])) == false) {
// different paths, force resolve again.
i++;
continue;
}
if (cachedPaths.length > CACHE_INDEX_OPT_DOCS_URI) {
// compare optional libraries javadoc
if (new File(targetPaths[CACHE_INDEX_OPT_DOCS_URI]).equals(
new File(cachedPaths[CACHE_INDEX_OPT_DOCS_URI])) == false) {
// different paths, force resolve again.
i++;
continue;
}
// testing the optional jar files is a little bit trickier.
// The order is not guaranteed to be identical.
// From a previous test, we do know however that there is the same number.
// The number of libraries should be low enough that we can simply go through the
// lists manually.
targetLoop: for (int tpi = 4 ; tpi < targetPaths.length; tpi++) {
String targetPath = targetPaths[tpi];
// look for a match in the other array
for (int cpi = 4 ; cpi < cachedPaths.length; cpi++) {
if (new File(targetPath).equals(new File(cachedPaths[cpi]))) {
// found a match. Try the next targetPath
continue targetLoop;
}
}
// if we stop here, we haven't found a match, which means there's a
// discrepancy in the libraries. We force a resolve.
i++;
continue projectLoop;
}
}
// at the point the check passes, and we can remove the project from the list.
// we do not increment i in this case.
projects.remove(i);
}
| private static org.eclipse.jdt.core.IClasspathEntry[] | createClasspathEntries(org.eclipse.core.resources.IProject project, com.android.sdklib.IAndroidTarget target, java.lang.String targetName)Creates and returns an array of {@link IClasspathEntry} objects for the android
framework and optional libraries.
This references the OS path to the android.jar and the
java doc directory. This is dynamically created when a project is opened,
and never saved in the project itself, so there's no risk of storing an
obsolete path.
The method also stores the paths used to create the entries in the project persistent
properties. A new {@link AndroidClasspathContainer} can be created from the stored path
using the {@link #getContainerFromCache(IProject)} method.
// get the path from the target
String[] paths = getTargetPaths(target);
// create the classpath entry from the paths
IClasspathEntry[] entries = createClasspathEntriesFromPaths(paths);
// paths now contains all the path required to recreate the IClasspathEntry with no
// target info. We encode them in a single string, with each path separated by
// OS path separator.
StringBuilder sb = new StringBuilder(CACHE_VERSION);
for (String p : paths) {
sb.append(PATH_SEPARATOR);
sb.append(p);
}
// store this in a project persistent property
ProjectHelper.saveStringProperty(project, PROPERTY_CONTAINER_CACHE, sb.toString());
ProjectHelper.saveStringProperty(project, PROPERTY_TARGET_NAME, targetName);
return entries;
| private static org.eclipse.jdt.core.IClasspathEntry[] | createClasspathEntriesFromPaths(java.lang.String[] paths)Generates an array of {@link IClasspathEntry} from a set of paths.
ArrayList<IClasspathEntry> list = new ArrayList<IClasspathEntry>();
// First, we create the IClasspathEntry for the framework.
// now add the android framework to the class path.
// create the path object.
IPath android_lib = new Path(paths[CACHE_INDEX_JAR]);
IPath android_src = new Path(paths[CACHE_INDEX_SRC]);
// create the java doc link.
IClasspathAttribute cpAttribute = JavaCore.newClasspathAttribute(
IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME,
paths[CACHE_INDEX_DOCS_URI]);
// create the access rule to restrict access to classes in com.android.internal
IAccessRule accessRule = JavaCore.newAccessRule(
new Path("com/android/internal/**"), //$NON-NLS-1$
IAccessRule.K_NON_ACCESSIBLE);
IClasspathEntry frameworkClasspathEntry = JavaCore.newLibraryEntry(android_lib,
android_src, // source attachment path
null, // default source attachment root path.
new IAccessRule[] { accessRule },
new IClasspathAttribute[] { cpAttribute },
false // not exported.
);
list.add(frameworkClasspathEntry);
// now deal with optional libraries
if (paths.length >= 5) {
String docPath = paths[CACHE_INDEX_OPT_DOCS_URI];
int i = 4;
while (i < paths.length) {
Path jarPath = new Path(paths[i++]);
IClasspathAttribute[] attributes = null;
if (docPath.length() > 0) {
attributes = new IClasspathAttribute[] {
JavaCore.newClasspathAttribute(
IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME,
docPath)
};
}
IClasspathEntry entry = JavaCore.newLibraryEntry(
jarPath,
null, // source attachment path
null, // default source attachment root path.
null,
attributes,
false // not exported.
);
list.add(entry);
}
}
return list.toArray(new IClasspathEntry[list.size()]);
| public static org.eclipse.jdt.core.IClasspathEntry | getContainerEntry()Creates a new {@link IClasspathEntry} of type {@link IClasspathEntry#CPE_CONTAINER}
linking to the Android Framework.
return JavaCore.newContainerEntry(new Path(CONTAINER_ID));
| private static AndroidClasspathContainer | getContainerFromCache(org.eclipse.core.resources.IProject project)Generates an {@link AndroidClasspathContainer} from the project cache, if possible.
// get the cached info from the project persistent properties.
String cache = ProjectHelper.loadStringProperty(project, PROPERTY_CONTAINER_CACHE);
String targetNameCache = ProjectHelper.loadStringProperty(project, PROPERTY_TARGET_NAME);
if (cache == null || targetNameCache == null) {
return null;
}
// the first 2 chars must match CACHE_VERSION. The 3rd char is the normal separator.
if (cache.startsWith(CACHE_VERSION_SEP) == false) {
return null;
}
cache = cache.substring(CACHE_VERSION_SEP.length());
// the cache contains multiple paths, separated by a character guaranteed to not be in
// the path (\u001C).
// The first 3 are for android.jar (jar, source, doc), the rest are for the optional
// libraries and should contain at least one doc and a jar (if there are any libraries).
// Therefore, the path count should be 3 or 5+
String[] paths = cache.split(Pattern.quote(PATH_SEPARATOR));
if (paths.length < 3 || paths.length == 4) {
return null;
}
// now we check the paths actually exist.
// There's an exception: If the source folder for android.jar does not exist, this is
// not a problem, so we skip it.
// Also paths[CACHE_INDEX_DOCS_URI] is a URI to the javadoc, so we test it a
// bit differently.
try {
if (new File(paths[CACHE_INDEX_JAR]).exists() == false ||
new File(new URI(paths[CACHE_INDEX_DOCS_URI])).exists() == false) {
return null;
}
// check the path for the add-ons, if they exist.
if (paths.length > CACHE_INDEX_ADD_ON_START) {
// check the docs path separately from the rest of the paths as it's a URI.
if (new File(new URI(paths[CACHE_INDEX_OPT_DOCS_URI])).exists() == false) {
return null;
}
// now just check the remaining paths.
for (int i = CACHE_INDEX_ADD_ON_START + 1; i < paths.length; i++) {
String path = paths[i];
if (path.length() > 0) {
File f = new File(path);
if (f.exists() == false) {
return null;
}
}
}
}
} catch (URISyntaxException e) {
return null;
}
IClasspathEntry[] entries = createClasspathEntriesFromPaths(paths);
return new AndroidClasspathContainer(entries,
new Path(CONTAINER_ID), targetNameCache);
| private static java.lang.String[] | getTargetPaths(com.android.sdklib.IAndroidTarget target)Returns the paths necessary to create the {@link IClasspathEntry} for this targets.
The paths are always in the same order.
- Path to android.jar
- Path to the source code for android.jar
- Path to the javadoc for the android platform
Additionally, if there are optional libraries, the array will contain:
- Path to the librairies javadoc
- Path to the first .jar file
- (more .jar as needed)
ArrayList<String> paths = new ArrayList<String>();
// first, we get the path for android.jar
// The order is: android.jar, source folder, docs folder
paths.add(target.getPath(IAndroidTarget.ANDROID_JAR));
paths.add(target.getPath(IAndroidTarget.SOURCES));
paths.add(AdtPlugin.getUrlDoc());
// now deal with optional libraries.
IOptionalLibrary[] libraries = target.getOptionalLibraries();
if (libraries != null) {
// all the optional libraries use the same javadoc, so we start with this
String targetDocPath = target.getPath(IAndroidTarget.DOCS);
if (targetDocPath != null) {
paths.add(ProjectHelper.getJavaDocPath(targetDocPath));
} else {
// we add an empty string, to always have the same count.
paths.add("");
}
// because different libraries could use the same jar file, we make sure we add
// each jar file only once.
HashSet<String> visitedJars = new HashSet<String>();
for (IOptionalLibrary library : libraries) {
String jarPath = library.getJarPath();
if (visitedJars.contains(jarPath) == false) {
visitedJars.add(jarPath);
paths.add(jarPath);
}
}
}
return paths.toArray(new String[paths.size()]);
| public void | initialize(org.eclipse.core.runtime.IPath containerPath, org.eclipse.jdt.core.IJavaProject project)Binds a classpath container to a {@link IClasspathContainer} for a given project,
or silently fails if unable to do so.
if (CONTAINER_ID.equals(containerPath.toString())) {
JavaCore.setClasspathContainer(new Path(CONTAINER_ID),
new IJavaProject[] { project },
new IClasspathContainer[] { allocateAndroidContainer(project) },
new NullProgressMonitor());
}
| public static boolean | updateProjects(org.eclipse.jdt.core.IJavaProject[] androidProjects)Updates the {@link IJavaProject} objects with new android framework container. This forces
JDT to recompile them.
try {
// Allocate a new AndroidClasspathContainer, and associate it to the android framework
// container id for each projects.
// By providing a new association between a container id and a IClasspathContainer,
// this forces the JDT to query the IClasspathContainer for new IClasspathEntry (with
// IClasspathContainer#getClasspathEntries()), and therefore force recompilation of
// the projects.
int projectCount = androidProjects.length;
IClasspathContainer[] containers = new IClasspathContainer[projectCount];
for (int i = 0 ; i < projectCount; i++) {
containers[i] = allocateAndroidContainer(androidProjects[i]);
}
// give each project their new container in one call.
JavaCore.setClasspathContainer(
new Path(CONTAINER_ID),
androidProjects, containers, new NullProgressMonitor());
return true;
} catch (JavaModelException e) {
return false;
}
|
|