Home | History | Annotate | Download | only in project
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.eclipse.adt.internal.project;
     18 
     19 import com.android.ide.common.sdk.LoadStatus;
     20 import com.android.ide.eclipse.adt.AdtConstants;
     21 import com.android.ide.eclipse.adt.AdtPlugin;
     22 import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
     23 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     24 import com.android.sdklib.AndroidVersion;
     25 import com.android.sdklib.IAndroidTarget;
     26 import com.android.sdklib.SdkConstants;
     27 import com.android.sdklib.IAndroidTarget.IOptionalLibrary;
     28 
     29 import org.eclipse.core.resources.IMarker;
     30 import org.eclipse.core.resources.IProject;
     31 import org.eclipse.core.resources.IResource;
     32 import org.eclipse.core.resources.IWorkspaceRoot;
     33 import org.eclipse.core.resources.ResourcesPlugin;
     34 import org.eclipse.core.runtime.CoreException;
     35 import org.eclipse.core.runtime.FileLocator;
     36 import org.eclipse.core.runtime.IPath;
     37 import org.eclipse.core.runtime.IProgressMonitor;
     38 import org.eclipse.core.runtime.IStatus;
     39 import org.eclipse.core.runtime.NullProgressMonitor;
     40 import org.eclipse.core.runtime.Path;
     41 import org.eclipse.core.runtime.Platform;
     42 import org.eclipse.core.runtime.Status;
     43 import org.eclipse.core.runtime.jobs.Job;
     44 import org.eclipse.jdt.core.ClasspathContainerInitializer;
     45 import org.eclipse.jdt.core.IAccessRule;
     46 import org.eclipse.jdt.core.IClasspathAttribute;
     47 import org.eclipse.jdt.core.IClasspathContainer;
     48 import org.eclipse.jdt.core.IClasspathEntry;
     49 import org.eclipse.jdt.core.IJavaModel;
     50 import org.eclipse.jdt.core.IJavaProject;
     51 import org.eclipse.jdt.core.JavaCore;
     52 import org.eclipse.jdt.core.JavaModelException;
     53 import org.osgi.framework.Bundle;
     54 
     55 import java.io.File;
     56 import java.io.IOException;
     57 import java.io.InputStream;
     58 import java.net.URI;
     59 import java.net.URISyntaxException;
     60 import java.net.URL;
     61 import java.util.ArrayList;
     62 import java.util.HashSet;
     63 import java.util.regex.Pattern;
     64 
     65 /**
     66  * Classpath container initializer responsible for binding {@link AndroidClasspathContainer} to
     67  * {@link IProject}s. This removes the hard-coded path to the android.jar.
     68  */
     69 public class AndroidClasspathContainerInitializer extends ClasspathContainerInitializer {
     70 
     71     public static final String NULL_API_URL = "<null>"; //$NON-NLS-1$
     72 
     73     public static final String SOURCES_ZIP = "/sources.zip"; //$NON-NLS-1$
     74 
     75     public static final String COM_ANDROID_IDE_ECLIPSE_ADT_SOURCE =
     76         "com.android.ide.eclipse.source"; //$NON-NLS-1$
     77 
     78     private static final String ANDROID_API_REFERENCE =
     79         "http://developer.android.com/reference/"; //$NON-NLS-1$
     80 
     81     private final static String PROPERTY_ANDROID_API = "androidApi"; //$NON-NLS-1$
     82 
     83     private final static String PROPERTY_ANDROID_SOURCE = "androidSource"; //$NON-NLS-1$
     84 
     85     /** path separator to store multiple paths in a single property. This is guaranteed to not
     86      * be in a path.
     87      */
     88     private final static String PATH_SEPARATOR = "\u001C"; //$NON-NLS-1$
     89 
     90     private final static String PROPERTY_CONTAINER_CACHE = "androidContainerCache"; //$NON-NLS-1$
     91     private final static String PROPERTY_TARGET_NAME = "androidTargetCache"; //$NON-NLS-1$
     92     private final static String CACHE_VERSION = "01"; //$NON-NLS-1$
     93     private final static String CACHE_VERSION_SEP = CACHE_VERSION + PATH_SEPARATOR;
     94 
     95     private final static int CACHE_INDEX_JAR = 0;
     96     private final static int CACHE_INDEX_SRC = 1;
     97     private final static int CACHE_INDEX_DOCS_URI = 2;
     98     private final static int CACHE_INDEX_OPT_DOCS_URI = 3;
     99     private final static int CACHE_INDEX_ADD_ON_START = CACHE_INDEX_OPT_DOCS_URI;
    100 
    101     public AndroidClasspathContainerInitializer() {
    102         // pass
    103     }
    104 
    105     /**
    106      * Binds a classpath container  to a {@link IClasspathContainer} for a given project,
    107      * or silently fails if unable to do so.
    108      * @param containerPath the container path that is the container id.
    109      * @param project the project to bind
    110      */
    111     @Override
    112     public void initialize(IPath containerPath, IJavaProject project) throws CoreException {
    113         if (AdtConstants.CONTAINER_FRAMEWORK.equals(containerPath.toString())) {
    114             IClasspathContainer container = allocateAndroidContainer(project);
    115             if (container != null) {
    116                 JavaCore.setClasspathContainer(new Path(AdtConstants.CONTAINER_FRAMEWORK),
    117                         new IJavaProject[] { project },
    118                         new IClasspathContainer[] { container },
    119                         new NullProgressMonitor());
    120             }
    121         }
    122     }
    123 
    124     /**
    125      * Updates the {@link IJavaProject} objects with new android framework container. This forces
    126      * JDT to recompile them.
    127      * @param androidProjects the projects to update.
    128      * @return <code>true</code> if success, <code>false</code> otherwise.
    129      */
    130     public static boolean updateProjects(IJavaProject[] androidProjects) {
    131         try {
    132             // Allocate a new AndroidClasspathContainer, and associate it to the android framework
    133             // container id for each projects.
    134             // By providing a new association between a container id and a IClasspathContainer,
    135             // this forces the JDT to query the IClasspathContainer for new IClasspathEntry (with
    136             // IClasspathContainer#getClasspathEntries()), and therefore force recompilation of
    137             // the projects.
    138             int projectCount = androidProjects.length;
    139 
    140             IClasspathContainer[] containers = new IClasspathContainer[projectCount];
    141             for (int i = 0 ; i < projectCount; i++) {
    142                 containers[i] = allocateAndroidContainer(androidProjects[i]);
    143             }
    144 
    145             // give each project their new container in one call.
    146             JavaCore.setClasspathContainer(
    147                     new Path(AdtConstants.CONTAINER_FRAMEWORK),
    148                     androidProjects, containers, new NullProgressMonitor());
    149 
    150             return true;
    151         } catch (JavaModelException e) {
    152             return false;
    153         }
    154     }
    155 
    156     /**
    157      * Allocates and returns an {@link AndroidClasspathContainer} object with the proper
    158      * path to the framework jar file.
    159      * @param javaProject The java project that will receive the container.
    160      */
    161     private static IClasspathContainer allocateAndroidContainer(IJavaProject javaProject) {
    162         final IProject iProject = javaProject.getProject();
    163 
    164         String markerMessage = null;
    165         boolean outputToConsole = true;
    166         IAndroidTarget target = null;
    167 
    168         try {
    169             AdtPlugin plugin = AdtPlugin.getDefault();
    170             if (plugin == null) { // This is totally weird, but I've seen it happen!
    171                 return null;
    172             }
    173 
    174             synchronized (Sdk.getLock()) {
    175                 boolean sdkIsLoaded = plugin.getSdkLoadStatus() == LoadStatus.LOADED;
    176 
    177                 // check if the project has a valid target.
    178                 ProjectState state = Sdk.getProjectState(iProject);
    179                 if (state == null) {
    180                     // looks like the project state (project.properties) couldn't be read!
    181                     markerMessage = String.format(
    182                             "Project has no %1$s file! Edit the project properties to set one.",
    183                             SdkConstants.FN_PROJECT_PROPERTIES);
    184                 } else {
    185                     // this might be null if the sdk is not yet loaded.
    186                     target = state.getTarget();
    187 
    188                     // if we are loaded and the target is non null, we create a valid
    189                     // ClassPathContainer
    190                     if (sdkIsLoaded && target != null) {
    191                         // first make sure the target has loaded its data
    192                         Sdk.getCurrent().checkAndLoadTargetData(target, null /*project*/);
    193 
    194                         String targetName = target.getClasspathName();
    195 
    196                         return new AndroidClasspathContainer(
    197                                 createClasspathEntries(iProject, target, targetName),
    198                                 new Path(AdtConstants.CONTAINER_FRAMEWORK),
    199                                 targetName,
    200                                 IClasspathContainer.K_DEFAULT_SYSTEM);
    201                     }
    202 
    203                     // In case of error, we'll try different thing to provide the best error message
    204                     // possible.
    205                     // Get the project's target's hash string (if it exists)
    206                     String hashString = state.getTargetHashString();
    207 
    208                     if (hashString == null || hashString.length() == 0) {
    209                         // if there is no hash string we only show this if the SDK is loaded.
    210                         // For a project opened at start-up with no target, this would be displayed
    211                         // twice, once when the project is opened, and once after the SDK has
    212                         // finished loading.
    213                         // By testing the sdk is loaded, we only show this once in the console.
    214                         if (sdkIsLoaded) {
    215                             markerMessage = String.format(
    216                                     "Project has no target set. Edit the project properties to set one.");
    217                         }
    218                     } else if (sdkIsLoaded) {
    219                         markerMessage = String.format(
    220                                 "Unable to resolve target '%s'", hashString);
    221                     } else {
    222                         // this is the case where there is a hashString but the SDK is not yet
    223                         // loaded and therefore we can't get the target yet.
    224                         // We check if there is a cache of the needed information.
    225                         AndroidClasspathContainer container = getContainerFromCache(iProject, target);
    226 
    227                         if (container == null) {
    228                             // either the cache was wrong (ie folder does not exists anymore), or
    229                             // there was no cache. In this case we need to make sure the project
    230                             // is resolved again after the SDK is loaded.
    231                             plugin.setProjectToResolve(javaProject);
    232 
    233                             markerMessage = String.format(
    234                                     "Unable to resolve target '%s' until the SDK is loaded.",
    235                                     hashString);
    236 
    237                             // let's not log this one to the console as it will happen at
    238                             // every boot, and it's expected. (we do keep the error marker though).
    239                             outputToConsole = false;
    240 
    241                         } else {
    242                             // we created a container from the cache, so we register the project
    243                             // to be checked for cache validity once the SDK is loaded
    244                             plugin.setProjectToCheck(javaProject);
    245 
    246                             // and return the container
    247                             return container;
    248                         }
    249                     }
    250                 }
    251 
    252                 // return a dummy container to replace the one we may have had before.
    253                 // It'll be replaced by the real when if/when the target is resolved if/when the
    254                 // SDK finishes loading.
    255                 return new IClasspathContainer() {
    256                     public IClasspathEntry[] getClasspathEntries() {
    257                         return new IClasspathEntry[0];
    258                     }
    259 
    260                     public String getDescription() {
    261                         return "Unable to get system library for the project";
    262                     }
    263 
    264                     public int getKind() {
    265                         return IClasspathContainer.K_DEFAULT_SYSTEM;
    266                     }
    267 
    268                     public IPath getPath() {
    269                         return null;
    270                     }
    271                 };
    272             }
    273         } finally {
    274             if (markerMessage != null) {
    275                 // log the error and put the marker on the project if we can.
    276                 if (outputToConsole) {
    277                     AdtPlugin.printErrorToConsole(iProject, markerMessage);
    278                 }
    279 
    280                 try {
    281                     BaseProjectHelper.markProject(iProject, AdtConstants.MARKER_TARGET,
    282                             markerMessage, IMarker.SEVERITY_ERROR, IMarker.PRIORITY_HIGH);
    283                 } catch (CoreException e) {
    284                     // In some cases, the workspace may be locked for modification when we
    285                     // pass here.
    286                     // We schedule a new job to put the marker after.
    287                     final String fmessage = markerMessage;
    288                     Job markerJob = new Job("Android SDK: Resolving error markers") {
    289                         @Override
    290                         protected IStatus run(IProgressMonitor monitor) {
    291                             try {
    292                                 BaseProjectHelper.markProject(iProject,
    293                                         AdtConstants.MARKER_TARGET,
    294                                         fmessage, IMarker.SEVERITY_ERROR,
    295                                         IMarker.PRIORITY_HIGH);
    296                             } catch (CoreException e2) {
    297                                 return e2.getStatus();
    298                             }
    299 
    300                             return Status.OK_STATUS;
    301                         }
    302                     };
    303 
    304                     // build jobs are run after other interactive jobs
    305                     markerJob.setPriority(Job.BUILD);
    306                     markerJob.schedule();
    307                 }
    308             } else {
    309                 // no error, remove potential MARKER_TARGETs.
    310                 try {
    311                     if (iProject.exists()) {
    312                         iProject.deleteMarkers(AdtConstants.MARKER_TARGET, true,
    313                                 IResource.DEPTH_INFINITE);
    314                     }
    315                 } catch (CoreException ce) {
    316                     // In some cases, the workspace may be locked for modification when we pass
    317                     // here, so we schedule a new job to put the marker after.
    318                     Job markerJob = new Job("Android SDK: Resolving error markers") {
    319                         @Override
    320                         protected IStatus run(IProgressMonitor monitor) {
    321                             try {
    322                                 iProject.deleteMarkers(AdtConstants.MARKER_TARGET, true,
    323                                         IResource.DEPTH_INFINITE);
    324                             } catch (CoreException e2) {
    325                                 return e2.getStatus();
    326                             }
    327 
    328                             return Status.OK_STATUS;
    329                         }
    330                     };
    331 
    332                     // build jobs are run after other interactive jobs
    333                     markerJob.setPriority(Job.BUILD);
    334                     markerJob.schedule();
    335                 }
    336             }
    337         }
    338     }
    339 
    340     /**
    341      * Creates and returns an array of {@link IClasspathEntry} objects for the android
    342      * framework and optional libraries.
    343      * <p/>This references the OS path to the android.jar and the
    344      * java doc directory. This is dynamically created when a project is opened,
    345      * and never saved in the project itself, so there's no risk of storing an
    346      * obsolete path.
    347      * The method also stores the paths used to create the entries in the project persistent
    348      * properties. A new {@link AndroidClasspathContainer} can be created from the stored path
    349      * using the {@link #getContainerFromCache(IProject)} method.
    350      * @param project
    351      * @param target The target that contains the libraries.
    352      * @param targetName
    353      */
    354     private static IClasspathEntry[] createClasspathEntries(IProject project,
    355             IAndroidTarget target, String targetName) {
    356 
    357         // get the path from the target
    358         String[] paths = getTargetPaths(target);
    359 
    360         // create the classpath entry from the paths
    361         IClasspathEntry[] entries = createClasspathEntriesFromPaths(paths, target);
    362 
    363         // paths now contains all the path required to recreate the IClasspathEntry with no
    364         // target info. We encode them in a single string, with each path separated by
    365         // OS path separator.
    366         StringBuilder sb = new StringBuilder(CACHE_VERSION);
    367         for (String p : paths) {
    368             sb.append(PATH_SEPARATOR);
    369             sb.append(p);
    370         }
    371 
    372         // store this in a project persistent property
    373         ProjectHelper.saveStringProperty(project, PROPERTY_CONTAINER_CACHE, sb.toString());
    374         ProjectHelper.saveStringProperty(project, PROPERTY_TARGET_NAME, targetName);
    375 
    376         return entries;
    377     }
    378 
    379     /**
    380      * Generates an {@link AndroidClasspathContainer} from the project cache, if possible.
    381      */
    382     private static AndroidClasspathContainer getContainerFromCache(IProject project,
    383             IAndroidTarget target) {
    384         // get the cached info from the project persistent properties.
    385         String cache = ProjectHelper.loadStringProperty(project, PROPERTY_CONTAINER_CACHE);
    386         String targetNameCache = ProjectHelper.loadStringProperty(project, PROPERTY_TARGET_NAME);
    387         if (cache == null || targetNameCache == null) {
    388             return null;
    389         }
    390 
    391         // the first 2 chars must match CACHE_VERSION. The 3rd char is the normal separator.
    392         if (cache.startsWith(CACHE_VERSION_SEP) == false) {
    393             return null;
    394         }
    395 
    396         cache = cache.substring(CACHE_VERSION_SEP.length());
    397 
    398         // the cache contains multiple paths, separated by a character guaranteed to not be in
    399         // the path (\u001C).
    400         // The first 3 are for android.jar (jar, source, doc), the rest are for the optional
    401         // libraries and should contain at least one doc and a jar (if there are any libraries).
    402         // Therefore, the path count should be 3 or 5+
    403         String[] paths = cache.split(Pattern.quote(PATH_SEPARATOR));
    404         if (paths.length < 3 || paths.length == 4) {
    405             return null;
    406         }
    407 
    408         // now we check the paths actually exist.
    409         // There's an exception: If the source folder for android.jar does not exist, this is
    410         // not a problem, so we skip it.
    411         // Also paths[CACHE_INDEX_DOCS_URI] is a URI to the javadoc, so we test it a
    412         // bit differently.
    413         try {
    414             if (new File(paths[CACHE_INDEX_JAR]).exists() == false ||
    415                     new File(new URI(paths[CACHE_INDEX_DOCS_URI])).exists() == false) {
    416                 return null;
    417             }
    418 
    419             // check the path for the add-ons, if they exist.
    420             if (paths.length > CACHE_INDEX_ADD_ON_START) {
    421 
    422                 // check the docs path separately from the rest of the paths as it's a URI.
    423                 if (new File(new URI(paths[CACHE_INDEX_OPT_DOCS_URI])).exists() == false) {
    424                     return null;
    425                 }
    426 
    427                 // now just check the remaining paths.
    428                 for (int i = CACHE_INDEX_ADD_ON_START + 1; i < paths.length; i++) {
    429                     String path = paths[i];
    430                     if (path.length() > 0) {
    431                         File f = new File(path);
    432                         if (f.exists() == false) {
    433                             return null;
    434                         }
    435                     }
    436                 }
    437             }
    438         } catch (URISyntaxException e) {
    439             return null;
    440         }
    441 
    442         IClasspathEntry[] entries = createClasspathEntriesFromPaths(paths, target);
    443 
    444         return new AndroidClasspathContainer(entries,
    445                 new Path(AdtConstants.CONTAINER_FRAMEWORK),
    446                 targetNameCache, IClasspathContainer.K_DEFAULT_SYSTEM);
    447     }
    448 
    449     /**
    450      * Generates an array of {@link IClasspathEntry} from a set of paths.
    451      * @see #getTargetPaths(IAndroidTarget)
    452      */
    453     private static IClasspathEntry[] createClasspathEntriesFromPaths(String[] paths,
    454             IAndroidTarget target) {
    455         ArrayList<IClasspathEntry> list = new ArrayList<IClasspathEntry>();
    456 
    457         // First, we create the IClasspathEntry for the framework.
    458         // now add the android framework to the class path.
    459         // create the path object.
    460         IPath androidLib = new Path(paths[CACHE_INDEX_JAR]);
    461 
    462         IPath androidSrc = null;
    463         String androidSrcOsPath = null;
    464         IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
    465         if (target != null) {
    466             androidSrcOsPath =
    467                 ProjectHelper.loadStringProperty(root, getAndroidSourceProperty(target));
    468         }
    469         if (androidSrcOsPath != null && androidSrcOsPath.trim().length() > 0) {
    470             androidSrc = new Path(androidSrcOsPath);
    471         }
    472         if (androidSrc == null) {
    473             androidSrc = new Path(paths[CACHE_INDEX_SRC]);
    474             File androidSrcFile = new File(paths[CACHE_INDEX_SRC]);
    475             if (!androidSrcFile.isDirectory()) {
    476                 androidSrc = null;
    477             }
    478         }
    479 
    480         if (androidSrc == null && target != null) {
    481             Bundle bundle = getSourceBundle();
    482 
    483             if (bundle != null) {
    484                 AndroidVersion version = target.getVersion();
    485                 String apiString = version.getApiString();
    486                 String sourcePath = apiString + SOURCES_ZIP;
    487                 URL sourceURL = bundle.getEntry(sourcePath);
    488                 if (sourceURL != null) {
    489                     URL url = null;
    490                     try {
    491                         url = FileLocator.resolve(sourceURL);
    492                     } catch (IOException ignore) {
    493                     }
    494                     if (url != null) {
    495                         androidSrcOsPath = url.getFile();
    496                         if (new File(androidSrcOsPath).isFile()) {
    497                             androidSrc = new Path(androidSrcOsPath);
    498                         }
    499                     }
    500                 }
    501             }
    502         }
    503 
    504         // create the java doc link.
    505         String androidApiURL = ProjectHelper.loadStringProperty(root, PROPERTY_ANDROID_API);
    506         String apiURL = null;
    507         if (androidApiURL != null && testURL(androidApiURL)) {
    508             apiURL = androidApiURL;
    509         } else {
    510             if (testURL(paths[CACHE_INDEX_DOCS_URI])) {
    511                 apiURL = paths[CACHE_INDEX_DOCS_URI];
    512             } else if (testURL(ANDROID_API_REFERENCE)) {
    513                 apiURL = ANDROID_API_REFERENCE;
    514             }
    515         }
    516 
    517         IClasspathAttribute[] attributes = null;
    518         if (apiURL != null && !NULL_API_URL.equals(apiURL)) {
    519             IClasspathAttribute cpAttribute = JavaCore.newClasspathAttribute(
    520                     IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME, apiURL);
    521             attributes = new IClasspathAttribute[] {
    522                 cpAttribute
    523             };
    524         }
    525         // create the access rule to restrict access to classes in
    526         // com.android.internal
    527         IAccessRule accessRule = JavaCore.newAccessRule(new Path("com/android/internal/**"), //$NON-NLS-1$
    528                 IAccessRule.K_NON_ACCESSIBLE);
    529 
    530         IClasspathEntry frameworkClasspathEntry = JavaCore.newLibraryEntry(androidLib,
    531                 androidSrc, // source attachment path
    532                 null, // default source attachment root path.
    533                 new IAccessRule[] { accessRule },
    534                 attributes,
    535                 false // not exported.
    536                 );
    537 
    538         list.add(frameworkClasspathEntry);
    539 
    540         // now deal with optional libraries
    541         if (paths.length >= 5) {
    542             String docPath = paths[CACHE_INDEX_OPT_DOCS_URI];
    543             int i = 4;
    544             while (i < paths.length) {
    545                 Path jarPath = new Path(paths[i++]);
    546 
    547                 attributes = null;
    548                 if (docPath.length() > 0) {
    549                     attributes = new IClasspathAttribute[] {
    550                         JavaCore.newClasspathAttribute(
    551                                 IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME, docPath)
    552                     };
    553                 }
    554 
    555                 IClasspathEntry entry = JavaCore.newLibraryEntry(
    556                         jarPath,
    557                         null, // source attachment path
    558                         null, // default source attachment root path.
    559                         null,
    560                         attributes,
    561                         false // not exported.
    562                         );
    563                 list.add(entry);
    564             }
    565         }
    566 
    567         if (apiURL != null) {
    568             ProjectHelper.saveStringProperty(root, PROPERTY_ANDROID_API, apiURL);
    569         }
    570         if (androidSrc != null && target != null) {
    571             ProjectHelper.saveStringProperty(root, getAndroidSourceProperty(target),
    572                     androidSrc.toOSString());
    573         }
    574         return list.toArray(new IClasspathEntry[list.size()]);
    575     }
    576 
    577     private static Bundle getSourceBundle() {
    578         String bundleId = System.getProperty(COM_ANDROID_IDE_ECLIPSE_ADT_SOURCE,
    579                 COM_ANDROID_IDE_ECLIPSE_ADT_SOURCE);
    580         Bundle bundle = Platform.getBundle(bundleId);
    581         return bundle;
    582     }
    583 
    584     private static String getAndroidSourceProperty(IAndroidTarget target) {
    585         if (target == null) {
    586             return null;
    587         }
    588         String androidSourceProperty = PROPERTY_ANDROID_SOURCE + "_"
    589                 + target.getVersion().getApiString();
    590         return androidSourceProperty;
    591     }
    592 
    593     private static boolean testURL(String androidApiURL) {
    594         boolean valid = false;
    595         InputStream is = null;
    596         try {
    597             URL testURL = new URL(androidApiURL);
    598             is = testURL.openStream();
    599             valid = true;
    600         } catch (Exception ignore) {
    601         } finally {
    602             if (is != null) {
    603                 try {
    604                     is.close();
    605                 } catch (IOException ignore) {
    606                 }
    607             }
    608         }
    609         return valid;
    610     }
    611 
    612     /**
    613      * Checks the projects' caches. If the cache was valid, the project is removed from the list.
    614      * @param projects the list of projects to check.
    615      */
    616     public static void checkProjectsCache(ArrayList<IJavaProject> projects) {
    617         Sdk currentSdk = Sdk.getCurrent();
    618         int i = 0;
    619         projectLoop: while (i < projects.size()) {
    620             IJavaProject javaProject = projects.get(i);
    621             IProject iProject = javaProject.getProject();
    622 
    623             // check if the project is opened
    624             if (iProject.isOpen() == false) {
    625                 // remove from the list
    626                 // we do not increment i in this case.
    627                 projects.remove(i);
    628 
    629                 continue;
    630             }
    631 
    632             // project that have been resolved before the sdk was loaded
    633             // will have a ProjectState where the IAndroidTarget is null
    634             // so we load the target now that the SDK is loaded.
    635             IAndroidTarget target = currentSdk.loadTarget(Sdk.getProjectState(iProject));
    636             if (target == null) {
    637                 // this is really not supposed to happen. This would mean there are cached paths,
    638                 // but project.properties was deleted. Keep the project in the list to force
    639                 // a resolve which will display the error.
    640                 i++;
    641                 continue;
    642             }
    643 
    644             String[] targetPaths = getTargetPaths(target);
    645 
    646             // now get the cached paths
    647             String cache = ProjectHelper.loadStringProperty(iProject, PROPERTY_CONTAINER_CACHE);
    648             if (cache == null) {
    649                 // this should not happen. We'll force resolve again anyway.
    650                 i++;
    651                 continue;
    652             }
    653 
    654             String[] cachedPaths = cache.split(Pattern.quote(PATH_SEPARATOR));
    655             if (cachedPaths.length < 3 || cachedPaths.length == 4) {
    656                 // paths length is wrong. simply resolve the project again
    657                 i++;
    658                 continue;
    659             }
    660 
    661             // Now we compare the paths. The first 4 can be compared directly.
    662             // because of case sensitiveness we need to use File objects
    663 
    664             if (targetPaths.length != cachedPaths.length) {
    665                 // different paths, force resolve again.
    666                 i++;
    667                 continue;
    668             }
    669 
    670             // compare the main paths (android.jar, main sources, main javadoc)
    671             if (new File(targetPaths[CACHE_INDEX_JAR]).equals(
    672                             new File(cachedPaths[CACHE_INDEX_JAR])) == false ||
    673                     new File(targetPaths[CACHE_INDEX_SRC]).equals(
    674                             new File(cachedPaths[CACHE_INDEX_SRC])) == false ||
    675                     new File(targetPaths[CACHE_INDEX_DOCS_URI]).equals(
    676                             new File(cachedPaths[CACHE_INDEX_DOCS_URI])) == false) {
    677                 // different paths, force resolve again.
    678                 i++;
    679                 continue;
    680             }
    681 
    682             if (cachedPaths.length > CACHE_INDEX_OPT_DOCS_URI) {
    683                 // compare optional libraries javadoc
    684                 if (new File(targetPaths[CACHE_INDEX_OPT_DOCS_URI]).equals(
    685                         new File(cachedPaths[CACHE_INDEX_OPT_DOCS_URI])) == false) {
    686                     // different paths, force resolve again.
    687                     i++;
    688                     continue;
    689                 }
    690 
    691                 // testing the optional jar files is a little bit trickier.
    692                 // The order is not guaranteed to be identical.
    693                 // From a previous test, we do know however that there is the same number.
    694                 // The number of libraries should be low enough that we can simply go through the
    695                 // lists manually.
    696                 targetLoop: for (int tpi = 4 ; tpi < targetPaths.length; tpi++) {
    697                     String targetPath = targetPaths[tpi];
    698 
    699                     // look for a match in the other array
    700                     for (int cpi = 4 ; cpi < cachedPaths.length; cpi++) {
    701                         if (new File(targetPath).equals(new File(cachedPaths[cpi]))) {
    702                             // found a match. Try the next targetPath
    703                             continue targetLoop;
    704                         }
    705                     }
    706 
    707                     // if we stop here, we haven't found a match, which means there's a
    708                     // discrepancy in the libraries. We force a resolve.
    709                     i++;
    710                     continue projectLoop;
    711                 }
    712             }
    713 
    714             // at the point the check passes, and we can remove the project from the list.
    715             // we do not increment i in this case.
    716             projects.remove(i);
    717         }
    718     }
    719 
    720     /**
    721      * Returns the paths necessary to create the {@link IClasspathEntry} for this targets.
    722      * <p/>The paths are always in the same order.
    723      * <ul>
    724      * <li>Path to android.jar</li>
    725      * <li>Path to the source code for android.jar</li>
    726      * <li>Path to the javadoc for the android platform</li>
    727      * </ul>
    728      * Additionally, if there are optional libraries, the array will contain:
    729      * <ul>
    730      * <li>Path to the libraries javadoc</li>
    731      * <li>Path to the first .jar file</li>
    732      * <li>(more .jar as needed)</li>
    733      * </ul>
    734      */
    735     private static String[] getTargetPaths(IAndroidTarget target) {
    736         ArrayList<String> paths = new ArrayList<String>();
    737 
    738         // first, we get the path for android.jar
    739         // The order is: android.jar, source folder, docs folder
    740         paths.add(target.getPath(IAndroidTarget.ANDROID_JAR));
    741         paths.add(target.getPath(IAndroidTarget.SOURCES));
    742         paths.add(AdtPlugin.getUrlDoc());
    743 
    744         // now deal with optional libraries.
    745         IOptionalLibrary[] libraries = target.getOptionalLibraries();
    746         if (libraries != null) {
    747             // all the optional libraries use the same javadoc, so we start with this
    748             String targetDocPath = target.getPath(IAndroidTarget.DOCS);
    749             if (targetDocPath != null) {
    750                 paths.add(ProjectHelper.getJavaDocPath(targetDocPath));
    751             } else {
    752                 // we add an empty string, to always have the same count.
    753                 paths.add("");
    754             }
    755 
    756             // because different libraries could use the same jar file, we make sure we add
    757             // each jar file only once.
    758             HashSet<String> visitedJars = new HashSet<String>();
    759             for (IOptionalLibrary library : libraries) {
    760                 String jarPath = library.getJarPath();
    761                 if (visitedJars.contains(jarPath) == false) {
    762                     visitedJars.add(jarPath);
    763                     paths.add(jarPath);
    764                 }
    765             }
    766         }
    767 
    768         return paths.toArray(new String[paths.size()]);
    769     }
    770 
    771     @Override
    772     public boolean canUpdateClasspathContainer(IPath containerPath, IJavaProject project) {
    773         return true;
    774     }
    775 
    776     @Override
    777     public void requestClasspathContainerUpdate(IPath containerPath, IJavaProject project,
    778             IClasspathContainer containerSuggestion) throws CoreException {
    779         AdtPlugin plugin = AdtPlugin.getDefault();
    780 
    781         synchronized (Sdk.getLock()) {
    782             boolean sdkIsLoaded = plugin.getSdkLoadStatus() == LoadStatus.LOADED;
    783 
    784             // check if the project has a valid target.
    785             IAndroidTarget target = null;
    786             if (sdkIsLoaded) {
    787                 target = Sdk.getCurrent().getTarget(project.getProject());
    788             }
    789             if (sdkIsLoaded && target != null) {
    790                 String[] paths = getTargetPaths(target);
    791                 IPath android_lib = new Path(paths[CACHE_INDEX_JAR]);
    792                 IClasspathEntry[] entries = containerSuggestion.getClasspathEntries();
    793                 for (int i = 0; i < entries.length; i++) {
    794                     IClasspathEntry entry = entries[i];
    795                     if (entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) {
    796                         IPath entryPath = entry.getPath();
    797 
    798                         if (entryPath != null) {
    799                             if (entryPath.equals(android_lib)) {
    800                                 IPath entrySrcPath = entry.getSourceAttachmentPath();
    801                                 IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
    802                                 if (entrySrcPath != null) {
    803                                     ProjectHelper.saveStringProperty(root,
    804                                             getAndroidSourceProperty(target),
    805                                             entrySrcPath.toString());
    806                                 } else {
    807                                     ProjectHelper.saveStringProperty(root,
    808                                             getAndroidSourceProperty(target), null);
    809                                 }
    810                                 IClasspathAttribute[] extraAttributtes = entry.getExtraAttributes();
    811                                 if (extraAttributtes.length == 0) {
    812                                     ProjectHelper.saveStringProperty(root, PROPERTY_ANDROID_API,
    813                                             NULL_API_URL);
    814                                 }
    815                                 for (int j = 0; j < extraAttributtes.length; j++) {
    816                                     IClasspathAttribute extraAttribute = extraAttributtes[j];
    817                                     String value = extraAttribute.getValue();
    818                                     if ((value == null || value.trim().length() == 0)
    819                                             && IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME
    820                                                     .equals(extraAttribute.getName())) {
    821                                         value = NULL_API_URL;
    822                                     }
    823                                     if (IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME
    824                                             .equals(extraAttribute.getName())) {
    825                                         ProjectHelper.saveStringProperty(root,
    826                                                 PROPERTY_ANDROID_API, value);
    827 
    828                                     }
    829                                 }
    830                             }
    831                         }
    832                     }
    833                 }
    834                 rebindClasspathEntries(project.getJavaModel(), containerPath);
    835             }
    836         }
    837     }
    838 
    839     private static void rebindClasspathEntries(IJavaModel model, IPath containerPath)
    840             throws JavaModelException {
    841         ArrayList<IJavaProject> affectedProjects = new ArrayList<IJavaProject>();
    842 
    843         IJavaProject[] projects = model.getJavaProjects();
    844         for (int i = 0; i < projects.length; i++) {
    845             IJavaProject project = projects[i];
    846             IClasspathEntry[] entries = project.getRawClasspath();
    847             for (int k = 0; k < entries.length; k++) {
    848                 IClasspathEntry curr = entries[k];
    849                 if (curr.getEntryKind() == IClasspathEntry.CPE_CONTAINER
    850                         && containerPath.equals(curr.getPath())) {
    851                     affectedProjects.add(project);
    852                 }
    853             }
    854         }
    855         if (!affectedProjects.isEmpty()) {
    856             IJavaProject[] affected = affectedProjects
    857                     .toArray(new IJavaProject[affectedProjects.size()]);
    858             updateProjects(affected);
    859         }
    860     }
    861 
    862 }
    863