| /* |
| * Copyright (C) 2007 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 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.internal.sdk.ProjectState; |
| import com.android.ide.eclipse.adt.internal.sdk.Sdk; |
| import com.android.sdklib.AndroidVersion; |
| import com.android.sdklib.IAndroidTarget; |
| import com.android.sdklib.IAndroidTarget.IOptionalLibrary; |
| import com.google.common.collect.Maps; |
| import com.google.common.io.Closeables; |
| |
| 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.FileLocator; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.core.runtime.Platform; |
| 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.IJavaModel; |
| import org.eclipse.jdt.core.IJavaProject; |
| import org.eclipse.jdt.core.JavaCore; |
| import org.eclipse.jdt.core.JavaModelException; |
| import org.osgi.framework.Bundle; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.net.URL; |
| import java.net.URLConnection; |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.regex.Pattern; |
| |
| /** |
| * Classpath container initializer responsible for binding {@link AndroidClasspathContainer} to |
| * {@link IProject}s. This removes the hard-coded path to the android.jar. |
| */ |
| public class AndroidClasspathContainerInitializer extends BaseClasspathContainerInitializer { |
| |
| public static final String NULL_API_URL = "<null>"; //$NON-NLS-1$ |
| |
| public static final String SOURCES_ZIP = "/sources.zip"; //$NON-NLS-1$ |
| |
| public static final String COM_ANDROID_IDE_ECLIPSE_ADT_SOURCE = |
| "com.android.ide.eclipse.source"; //$NON-NLS-1$ |
| |
| private static final String ANDROID_API_REFERENCE = |
| "http://developer.android.com/reference/"; //$NON-NLS-1$ |
| |
| private final static String PROPERTY_ANDROID_API = "androidApi"; //$NON-NLS-1$ |
| |
| private final static String PROPERTY_ANDROID_SOURCE = "androidSource"; //$NON-NLS-1$ |
| |
| /** path separator to store multiple paths in a single property. This is guaranteed to not |
| * be in a path. |
| */ |
| private final static String PATH_SEPARATOR = "\u001C"; //$NON-NLS-1$ |
| |
| private final static String PROPERTY_CONTAINER_CACHE = "androidContainerCache"; //$NON-NLS-1$ |
| private final static String PROPERTY_TARGET_NAME = "androidTargetCache"; //$NON-NLS-1$ |
| private final static String CACHE_VERSION = "01"; //$NON-NLS-1$ |
| private final static String CACHE_VERSION_SEP = CACHE_VERSION + PATH_SEPARATOR; |
| |
| private final static int CACHE_INDEX_JAR = 0; |
| private final static int CACHE_INDEX_SRC = 1; |
| private final static int CACHE_INDEX_DOCS_URI = 2; |
| private final static int CACHE_INDEX_OPT_DOCS_URI = 3; |
| private final static int CACHE_INDEX_ADD_ON_START = CACHE_INDEX_OPT_DOCS_URI; |
| |
| public AndroidClasspathContainerInitializer() { |
| // pass |
| } |
| |
| /** |
| * Binds a classpath container to a {@link IClasspathContainer} for a given project, |
| * or silently fails if unable to do so. |
| * @param containerPath the container path that is the container id. |
| * @param project the project to bind |
| */ |
| @Override |
| public void initialize(IPath containerPath, IJavaProject project) throws CoreException { |
| if (AdtConstants.CONTAINER_FRAMEWORK.equals(containerPath.toString())) { |
| IClasspathContainer container = allocateAndroidContainer(project); |
| if (container != null) { |
| JavaCore.setClasspathContainer(new Path(AdtConstants.CONTAINER_FRAMEWORK), |
| new IJavaProject[] { project }, |
| new IClasspathContainer[] { container }, |
| new NullProgressMonitor()); |
| } |
| } |
| } |
| |
| /** |
| * Updates the {@link IJavaProject} objects with new android framework container. This forces |
| * JDT to recompile them. |
| * @param androidProjects the projects to update. |
| * @return <code>true</code> if success, <code>false</code> otherwise. |
| */ |
| static boolean updateProjects(IJavaProject[] androidProjects) { |
| 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(AdtConstants.CONTAINER_FRAMEWORK), |
| androidProjects, containers, new NullProgressMonitor()); |
| |
| return true; |
| } catch (JavaModelException e) { |
| return false; |
| } |
| } |
| |
| /** |
| * Allocates and returns an {@link AndroidClasspathContainer} object with the proper |
| * path to the framework jar file. |
| * @param javaProject The java project that will receive the container. |
| */ |
| private static IClasspathContainer allocateAndroidContainer(IJavaProject javaProject) { |
| final IProject iProject = javaProject.getProject(); |
| |
| String markerMessage = null; |
| boolean outputToConsole = true; |
| IAndroidTarget target = null; |
| |
| try { |
| 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. |
| ProjectState state = Sdk.getProjectState(iProject); |
| if (state == null) { |
| // looks like the project state (project.properties) couldn't be read! |
| markerMessage = String.format( |
| "Project has no %1$s file! Edit the project properties to set one.", |
| SdkConstants.FN_PROJECT_PROPERTIES); |
| } else { |
| // this might be null if the sdk is not yet loaded. |
| target = state.getTarget(); |
| |
| // if we are loaded and the target is non null, we create a valid |
| // ClassPathContainer |
| if (sdkIsLoaded && target != null) { |
| // check the renderscript support mode. If support mode is enabled, |
| // target API must be 18+ |
| if (!state.getRenderScriptSupportMode() || |
| target.getVersion().getApiLevel() >= 18) { |
| // first make sure the target has loaded its data |
| Sdk.getCurrent().checkAndLoadTargetData(target, null /*project*/); |
| |
| String targetName = target.getClasspathName(); |
| |
| return new AndroidClasspathContainer( |
| createClasspathEntries(iProject, target, targetName), |
| new Path(AdtConstants.CONTAINER_FRAMEWORK), |
| targetName, |
| IClasspathContainer.K_DEFAULT_SYSTEM); |
| } else { |
| markerMessage = "Renderscript support mode requires compilation target API to be 18+."; |
| } |
| } else { |
| // 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 = state.getTargetHashString(); |
| |
| 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, |
| target); |
| |
| 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() { |
| @Override |
| public IClasspathEntry[] getClasspathEntries() { |
| return new IClasspathEntry[0]; |
| } |
| |
| @Override |
| public String getDescription() { |
| return "Unable to get system library for the project"; |
| } |
| |
| @Override |
| public int getKind() { |
| return IClasspathContainer.K_DEFAULT_SYSTEM; |
| } |
| |
| @Override |
| public IPath getPath() { |
| return null; |
| } |
| }; |
| } |
| } finally { |
| processError(iProject, markerMessage, AdtConstants.MARKER_TARGET, outputToConsole); |
| } |
| } |
| |
| /** |
| * Creates and returns an array of {@link IClasspathEntry} objects for the android |
| * framework and optional libraries. |
| * <p/>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. |
| * @param project |
| * @param target The target that contains the libraries. |
| * @param targetName |
| */ |
| private static IClasspathEntry[] createClasspathEntries(IProject project, |
| IAndroidTarget target, String targetName) { |
| |
| // get the path from the target |
| String[] paths = getTargetPaths(target); |
| |
| // create the classpath entry from the paths |
| IClasspathEntry[] entries = createClasspathEntriesFromPaths(paths, target); |
| |
| // 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; |
| } |
| |
| /** |
| * Generates an {@link AndroidClasspathContainer} from the project cache, if possible. |
| */ |
| private static AndroidClasspathContainer getContainerFromCache(IProject project, |
| IAndroidTarget target) { |
| // 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, target); |
| |
| return new AndroidClasspathContainer(entries, |
| new Path(AdtConstants.CONTAINER_FRAMEWORK), |
| targetNameCache, IClasspathContainer.K_DEFAULT_SYSTEM); |
| } |
| |
| /** |
| * Generates an array of {@link IClasspathEntry} from a set of paths. |
| * @see #getTargetPaths(IAndroidTarget) |
| */ |
| private static IClasspathEntry[] createClasspathEntriesFromPaths(String[] paths, |
| IAndroidTarget target) { |
| 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 androidLib = new Path(paths[CACHE_INDEX_JAR]); |
| |
| IPath androidSrc = null; |
| String androidSrcOsPath = null; |
| IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); |
| if (target != null) { |
| androidSrcOsPath = |
| ProjectHelper.loadStringProperty(root, getAndroidSourceProperty(target)); |
| } |
| if (androidSrcOsPath != null && androidSrcOsPath.trim().length() > 0) { |
| androidSrc = new Path(androidSrcOsPath); |
| } |
| if (androidSrc == null) { |
| androidSrc = new Path(paths[CACHE_INDEX_SRC]); |
| File androidSrcFile = new File(paths[CACHE_INDEX_SRC]); |
| if (!androidSrcFile.isDirectory()) { |
| androidSrc = null; |
| } |
| } |
| |
| if (androidSrc == null && target != null) { |
| Bundle bundle = getSourceBundle(); |
| |
| if (bundle != null) { |
| AndroidVersion version = target.getVersion(); |
| String apiString = version.getApiString(); |
| String sourcePath = apiString + SOURCES_ZIP; |
| URL sourceURL = bundle.getEntry(sourcePath); |
| if (sourceURL != null) { |
| URL url = null; |
| try { |
| url = FileLocator.resolve(sourceURL); |
| } catch (IOException ignore) { |
| } |
| if (url != null) { |
| androidSrcOsPath = url.getFile(); |
| if (new File(androidSrcOsPath).isFile()) { |
| androidSrc = new Path(androidSrcOsPath); |
| } |
| } |
| } |
| } |
| } |
| |
| // create the java doc link. |
| String androidApiURL = ProjectHelper.loadStringProperty(root, PROPERTY_ANDROID_API); |
| String apiURL = null; |
| if (androidApiURL != null && testURL(androidApiURL)) { |
| apiURL = androidApiURL; |
| } else { |
| if (testURL(paths[CACHE_INDEX_DOCS_URI])) { |
| apiURL = paths[CACHE_INDEX_DOCS_URI]; |
| } else if (testURL(ANDROID_API_REFERENCE)) { |
| apiURL = ANDROID_API_REFERENCE; |
| } |
| } |
| |
| IClasspathAttribute[] attributes = null; |
| if (apiURL != null && !NULL_API_URL.equals(apiURL)) { |
| IClasspathAttribute cpAttribute = JavaCore.newClasspathAttribute( |
| IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME, apiURL); |
| attributes = new IClasspathAttribute[] { |
| cpAttribute |
| }; |
| } |
| // 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(androidLib, |
| androidSrc, // source attachment path |
| null, // default source attachment root path. |
| new IAccessRule[] { accessRule }, |
| attributes, |
| 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++]); |
| |
| 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); |
| } |
| } |
| |
| if (apiURL != null) { |
| ProjectHelper.saveStringProperty(root, PROPERTY_ANDROID_API, apiURL); |
| } |
| if (androidSrc != null && target != null) { |
| ProjectHelper.saveStringProperty(root, getAndroidSourceProperty(target), |
| androidSrc.toOSString()); |
| } |
| return list.toArray(new IClasspathEntry[list.size()]); |
| } |
| |
| private static Bundle getSourceBundle() { |
| String bundleId = System.getProperty(COM_ANDROID_IDE_ECLIPSE_ADT_SOURCE, |
| COM_ANDROID_IDE_ECLIPSE_ADT_SOURCE); |
| Bundle bundle = Platform.getBundle(bundleId); |
| return bundle; |
| } |
| |
| private static String getAndroidSourceProperty(IAndroidTarget target) { |
| if (target == null) { |
| return null; |
| } |
| String androidSourceProperty = PROPERTY_ANDROID_SOURCE + "_" |
| + target.getVersion().getApiString(); |
| return androidSourceProperty; |
| } |
| |
| /** |
| * Cache results for testURL: Some are expensive to compute, and this is |
| * called repeatedly (perhaps for each open project) |
| */ |
| private static final Map<String, Boolean> sRecentUrlValidCache = |
| Maps.newHashMapWithExpectedSize(4); |
| |
| @SuppressWarnings("resource") // Eclipse does not handle Closeables#closeQuietly |
| private static boolean testURL(String androidApiURL) { |
| Boolean cached = sRecentUrlValidCache.get(androidApiURL); |
| if (cached != null) { |
| return cached.booleanValue(); |
| } |
| boolean valid = false; |
| InputStream is = null; |
| try { |
| URL testURL = new URL(androidApiURL); |
| URLConnection connection = testURL.openConnection(); |
| // Only try for 5 seconds (though some implementations ignore this flag) |
| connection.setConnectTimeout(5000); |
| connection.setReadTimeout(5000); |
| is = connection.getInputStream(); |
| valid = true; |
| } catch (Exception ignore) { |
| } finally { |
| Closeables.closeQuietly(is); |
| } |
| |
| sRecentUrlValidCache.put(androidApiURL, valid); |
| |
| return valid; |
| } |
| |
| /** |
| * Checks the projects' caches. If the cache was valid, the project is removed from the list. |
| * @param projects the list of projects to check. |
| */ |
| public static void checkProjectsCache(ArrayList<IJavaProject> projects) { |
| Sdk currentSdk = Sdk.getCurrent(); |
| 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; |
| } |
| |
| // project that have been resolved before the sdk was loaded |
| // will have a ProjectState where the IAndroidTarget is null |
| // so we load the target now that the SDK is loaded. |
| IAndroidTarget target = currentSdk.loadTargetAndBuildTools( |
| Sdk.getProjectState(iProject)); |
| if (target == null) { |
| // this is really not supposed to happen. This would mean there are cached paths, |
| // but project.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); |
| } |
| } |
| |
| /** |
| * Returns the paths necessary to create the {@link IClasspathEntry} for this targets. |
| * <p/>The paths are always in the same order. |
| * <ul> |
| * <li>Path to android.jar</li> |
| * <li>Path to the source code for android.jar</li> |
| * <li>Path to the javadoc for the android platform</li> |
| * </ul> |
| * Additionally, if there are optional libraries, the array will contain: |
| * <ul> |
| * <li>Path to the libraries javadoc</li> |
| * <li>Path to the first .jar file</li> |
| * <li>(more .jar as needed)</li> |
| * </ul> |
| */ |
| private static String[] getTargetPaths(IAndroidTarget target) { |
| 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()]); |
| } |
| |
| @Override |
| public boolean canUpdateClasspathContainer(IPath containerPath, IJavaProject project) { |
| return true; |
| } |
| |
| @Override |
| public void requestClasspathContainerUpdate(IPath containerPath, IJavaProject project, |
| IClasspathContainer containerSuggestion) throws CoreException { |
| AdtPlugin plugin = AdtPlugin.getDefault(); |
| |
| synchronized (Sdk.getLock()) { |
| boolean sdkIsLoaded = plugin.getSdkLoadStatus() == LoadStatus.LOADED; |
| |
| // check if the project has a valid target. |
| IAndroidTarget target = null; |
| if (sdkIsLoaded) { |
| target = Sdk.getCurrent().getTarget(project.getProject()); |
| } |
| if (sdkIsLoaded && target != null) { |
| String[] paths = getTargetPaths(target); |
| IPath android_lib = new Path(paths[CACHE_INDEX_JAR]); |
| IClasspathEntry[] entries = containerSuggestion.getClasspathEntries(); |
| for (int i = 0; i < entries.length; i++) { |
| IClasspathEntry entry = entries[i]; |
| if (entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) { |
| IPath entryPath = entry.getPath(); |
| |
| if (entryPath != null) { |
| if (entryPath.equals(android_lib)) { |
| IPath entrySrcPath = entry.getSourceAttachmentPath(); |
| IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); |
| if (entrySrcPath != null) { |
| ProjectHelper.saveStringProperty(root, |
| getAndroidSourceProperty(target), |
| entrySrcPath.toString()); |
| } else { |
| ProjectHelper.saveStringProperty(root, |
| getAndroidSourceProperty(target), null); |
| } |
| IClasspathAttribute[] extraAttributtes = entry.getExtraAttributes(); |
| if (extraAttributtes.length == 0) { |
| ProjectHelper.saveStringProperty(root, PROPERTY_ANDROID_API, |
| NULL_API_URL); |
| } |
| for (int j = 0; j < extraAttributtes.length; j++) { |
| IClasspathAttribute extraAttribute = extraAttributtes[j]; |
| String value = extraAttribute.getValue(); |
| if ((value == null || value.trim().length() == 0) |
| && IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME |
| .equals(extraAttribute.getName())) { |
| value = NULL_API_URL; |
| } |
| if (IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME |
| .equals(extraAttribute.getName())) { |
| ProjectHelper.saveStringProperty(root, |
| PROPERTY_ANDROID_API, value); |
| |
| } |
| } |
| } |
| } |
| } |
| } |
| rebindClasspathEntries(project.getJavaModel(), containerPath); |
| } |
| } |
| } |
| |
| private static void rebindClasspathEntries(IJavaModel model, IPath containerPath) |
| throws JavaModelException { |
| ArrayList<IJavaProject> affectedProjects = new ArrayList<IJavaProject>(); |
| |
| IJavaProject[] projects = model.getJavaProjects(); |
| for (int i = 0; i < projects.length; i++) { |
| IJavaProject project = projects[i]; |
| IClasspathEntry[] entries = project.getRawClasspath(); |
| for (int k = 0; k < entries.length; k++) { |
| IClasspathEntry curr = entries[k]; |
| if (curr.getEntryKind() == IClasspathEntry.CPE_CONTAINER |
| && containerPath.equals(curr.getPath())) { |
| affectedProjects.add(project); |
| } |
| } |
| } |
| if (!affectedProjects.isEmpty()) { |
| IJavaProject[] affected = affectedProjects |
| .toArray(new IJavaProject[affectedProjects.size()]); |
| updateProjects(affected); |
| } |
| } |
| } |