• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.eclipse.adt.AdtPlugin;
20 import com.android.ide.eclipse.adt.AndroidConstants;
21 import com.android.ide.eclipse.adt.internal.sdk.LoadStatus;
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.IAndroidTarget;
25 import com.android.sdklib.SdkConstants;
26 import com.android.sdklib.IAndroidTarget.IOptionalLibrary;
27 
28 import org.eclipse.core.resources.IMarker;
29 import org.eclipse.core.resources.IProject;
30 import org.eclipse.core.resources.IResource;
31 import org.eclipse.core.runtime.CoreException;
32 import org.eclipse.core.runtime.IPath;
33 import org.eclipse.core.runtime.IProgressMonitor;
34 import org.eclipse.core.runtime.IStatus;
35 import org.eclipse.core.runtime.NullProgressMonitor;
36 import org.eclipse.core.runtime.Path;
37 import org.eclipse.core.runtime.Status;
38 import org.eclipse.core.runtime.jobs.Job;
39 import org.eclipse.jdt.core.ClasspathContainerInitializer;
40 import org.eclipse.jdt.core.IAccessRule;
41 import org.eclipse.jdt.core.IClasspathAttribute;
42 import org.eclipse.jdt.core.IClasspathContainer;
43 import org.eclipse.jdt.core.IClasspathEntry;
44 import org.eclipse.jdt.core.IJavaProject;
45 import org.eclipse.jdt.core.JavaCore;
46 import org.eclipse.jdt.core.JavaModelException;
47 
48 import java.io.File;
49 import java.net.URI;
50 import java.net.URISyntaxException;
51 import java.util.ArrayList;
52 import java.util.HashSet;
53 import java.util.regex.Pattern;
54 
55 /**
56  * Classpath container initializer responsible for binding {@link AndroidClasspathContainer} to
57  * {@link IProject}s. This removes the hard-coded path to the android.jar.
58  */
59 public class AndroidClasspathContainerInitializer extends ClasspathContainerInitializer {
60     /** The container id for the android framework jar file */
61     private final static String CONTAINER_ID =
62         "com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"; //$NON-NLS-1$
63 
64     /** path separator to store multiple paths in a single property. This is guaranteed to not
65      * be in a path.
66      */
67     private final static String PATH_SEPARATOR = "\u001C"; //$NON-NLS-1$
68 
69     private final static String PROPERTY_CONTAINER_CACHE = "androidContainerCache"; //$NON-NLS-1$
70     private final static String PROPERTY_TARGET_NAME = "androidTargetCache"; //$NON-NLS-1$
71     private final static String CACHE_VERSION = "01"; //$NON-NLS-1$
72     private final static String CACHE_VERSION_SEP = CACHE_VERSION + PATH_SEPARATOR;
73 
74     private final static int CACHE_INDEX_JAR = 0;
75     private final static int CACHE_INDEX_SRC = 1;
76     private final static int CACHE_INDEX_DOCS_URI = 2;
77     private final static int CACHE_INDEX_OPT_DOCS_URI = 3;
78     private final static int CACHE_INDEX_ADD_ON_START = CACHE_INDEX_OPT_DOCS_URI;
79 
AndroidClasspathContainerInitializer()80     public AndroidClasspathContainerInitializer() {
81         // pass
82     }
83 
84     /**
85      * Binds a classpath container  to a {@link IClasspathContainer} for a given project,
86      * or silently fails if unable to do so.
87      * @param containerPath the container path that is the container id.
88      * @param project the project to bind
89      */
90     @Override
initialize(IPath containerPath, IJavaProject project)91     public void initialize(IPath containerPath, IJavaProject project) throws CoreException {
92         if (CONTAINER_ID.equals(containerPath.toString())) {
93             JavaCore.setClasspathContainer(new Path(CONTAINER_ID),
94                     new IJavaProject[] { project },
95                     new IClasspathContainer[] { allocateAndroidContainer(project) },
96                     new NullProgressMonitor());
97         }
98     }
99 
100     /**
101      * Creates a new {@link IClasspathEntry} of type {@link IClasspathEntry#CPE_CONTAINER}
102      * linking to the Android Framework.
103      */
getContainerEntry()104     public static IClasspathEntry getContainerEntry() {
105         return JavaCore.newContainerEntry(new Path(CONTAINER_ID));
106     }
107 
108     /**
109      * Checks the {@link IPath} objects against the android framework container id and
110      * returns <code>true</code> if they are identical.
111      * @param path the <code>IPath</code> to check.
112      */
checkPath(IPath path)113     public static boolean checkPath(IPath path) {
114         return CONTAINER_ID.equals(path.toString());
115     }
116 
117     /**
118      * Updates the {@link IJavaProject} objects with new android framework container. This forces
119      * JDT to recompile them.
120      * @param androidProjects the projects to update.
121      * @return <code>true</code> if success, <code>false</code> otherwise.
122      */
updateProjects(IJavaProject[] androidProjects)123     public static boolean updateProjects(IJavaProject[] androidProjects) {
124         try {
125             // Allocate a new AndroidClasspathContainer, and associate it to the android framework
126             // container id for each projects.
127             // By providing a new association between a container id and a IClasspathContainer,
128             // this forces the JDT to query the IClasspathContainer for new IClasspathEntry (with
129             // IClasspathContainer#getClasspathEntries()), and therefore force recompilation of
130             // the projects.
131             int projectCount = androidProjects.length;
132 
133             IClasspathContainer[] containers = new IClasspathContainer[projectCount];
134             for (int i = 0 ; i < projectCount; i++) {
135                 containers[i] = allocateAndroidContainer(androidProjects[i]);
136             }
137 
138             // give each project their new container in one call.
139             JavaCore.setClasspathContainer(
140                     new Path(CONTAINER_ID),
141                     androidProjects, containers, new NullProgressMonitor());
142 
143             return true;
144         } catch (JavaModelException e) {
145             return false;
146         }
147     }
148 
149     /**
150      * Allocates and returns an {@link AndroidClasspathContainer} object with the proper
151      * path to the framework jar file.
152      * @param javaProject The java project that will receive the container.
153      */
allocateAndroidContainer(IJavaProject javaProject)154     private static IClasspathContainer allocateAndroidContainer(IJavaProject javaProject) {
155         final IProject iProject = javaProject.getProject();
156 
157         String markerMessage = null;
158         boolean outputToConsole = true;
159 
160         try {
161             AdtPlugin plugin = AdtPlugin.getDefault();
162 
163             synchronized (Sdk.getLock()) {
164                 boolean sdkIsLoaded = plugin.getSdkLoadStatus() == LoadStatus.LOADED;
165 
166                 // check if the project has a valid target.
167                 ProjectState state = Sdk.getProjectState(iProject);
168                 if (state == null) {
169                     // looks like the project state (default.properties) couldn't be read!
170                     markerMessage = String.format(
171                             "Project has no default.properties file! Edit the project properties to set one.");
172                 } else {
173                     // this might be null if the sdk is not yet loaded.
174                     IAndroidTarget target = state.getTarget();
175 
176                     // if we are loaded and the target is non null, we create a valid
177                     // ClassPathContainer
178                     if (sdkIsLoaded && target != null) {
179                         // first make sure the target has loaded its data
180                         Sdk.getCurrent().checkAndLoadTargetData(target, null /*project*/);
181 
182                         // now do a quick check to make sure the project's target is compatible
183                         // with library (if applicable).
184                         if (state.hasLibraries() &&
185                                 target.getProperty(
186                                         SdkConstants.PROP_SDK_SUPPORT_LIBRARY, false) == false) {
187                             AdtPlugin.printErrorToConsole(iProject, String.format(
188                                     "Target '%1$s' does not support building project with libraries.",
189                                     target.getFullName()));
190                         }
191 
192                         String targetName = target.getClasspathName();
193 
194                         return new AndroidClasspathContainer(
195                                 createClasspathEntries(iProject, target, targetName),
196                                 new Path(CONTAINER_ID), targetName);
197                     }
198 
199                     // In case of error, we'll try different thing to provide the best error message
200                     // possible.
201                     // Get the project's target's hash string (if it exists)
202                     String hashString = state.getTargetHashString();
203 
204                     if (hashString == null || hashString.length() == 0) {
205                         // if there is no hash string we only show this if the SDK is loaded.
206                         // For a project opened at start-up with no target, this would be displayed
207                         // twice, once when the project is opened, and once after the SDK has
208                         // finished loading.
209                         // By testing the sdk is loaded, we only show this once in the console.
210                         if (sdkIsLoaded) {
211                             markerMessage = String.format(
212                                     "Project has no target set. Edit the project properties to set one.");
213                         }
214                     } else if (sdkIsLoaded) {
215                         markerMessage = String.format(
216                                 "Unable to resolve target '%s'", hashString);
217                     } else {
218                         // this is the case where there is a hashString but the SDK is not yet
219                         // loaded and therefore we can't get the target yet.
220                         // We check if there is a cache of the needed information.
221                         AndroidClasspathContainer container = getContainerFromCache(iProject);
222 
223                         if (container == null) {
224                             // either the cache was wrong (ie folder does not exists anymore), or
225                             // there was no cache. In this case we need to make sure the project
226                             // is resolved again after the SDK is loaded.
227                             plugin.setProjectToResolve(javaProject);
228 
229                             markerMessage = String.format(
230                                     "Unable to resolve target '%s' until the SDK is loaded.",
231                                     hashString);
232 
233                             // let's not log this one to the console as it will happen at
234                             // every boot, and it's expected. (we do keep the error marker though).
235                             outputToConsole = false;
236 
237                         } else {
238                             // we created a container from the cache, so we register the project
239                             // to be checked for cache validity once the SDK is loaded
240                             plugin.setProjectToCheck(javaProject);
241 
242                             // and return the container
243                             return container;
244                         }
245                     }
246                 }
247 
248                 // return a dummy container to replace the one we may have had before.
249                 // It'll be replaced by the real when if/when the target is resolved if/when the
250                 // SDK finishes loading.
251                 return new IClasspathContainer() {
252                     public IClasspathEntry[] getClasspathEntries() {
253                         return new IClasspathEntry[0];
254                     }
255 
256                     public String getDescription() {
257                         return "Unable to get system library for the project";
258                     }
259 
260                     public int getKind() {
261                         return IClasspathContainer.K_DEFAULT_SYSTEM;
262                     }
263 
264                     public IPath getPath() {
265                         return null;
266                     }
267                 };
268             }
269         } finally {
270             if (markerMessage != null) {
271                 // log the error and put the marker on the project if we can.
272                 if (outputToConsole) {
273                     AdtPlugin.printErrorToConsole(iProject, markerMessage);
274                 }
275 
276                 try {
277                     BaseProjectHelper.markProject(iProject, AndroidConstants.MARKER_TARGET,
278                             markerMessage, IMarker.SEVERITY_ERROR, IMarker.PRIORITY_HIGH);
279                 } catch (CoreException e) {
280                     // In some cases, the workspace may be locked for modification when we
281                     // pass here.
282                     // We schedule a new job to put the marker after.
283                     final String fmessage = markerMessage;
284                     Job markerJob = new Job("Android SDK: Resolving error markers") {
285                         @Override
286                         protected IStatus run(IProgressMonitor monitor) {
287                             try {
288                                 BaseProjectHelper.markProject(iProject,
289                                         AndroidConstants.MARKER_TARGET,
290                                         fmessage, IMarker.SEVERITY_ERROR,
291                                         IMarker.PRIORITY_HIGH);
292                             } catch (CoreException e2) {
293                                 return e2.getStatus();
294                             }
295 
296                             return Status.OK_STATUS;
297                         }
298                     };
299 
300                     // build jobs are run after other interactive jobs
301                     markerJob.setPriority(Job.BUILD);
302                     markerJob.schedule();
303                 }
304             } else {
305                 // no error, remove potential MARKER_TARGETs.
306                 try {
307                     if (iProject.exists()) {
308                         iProject.deleteMarkers(AndroidConstants.MARKER_TARGET, true,
309                                 IResource.DEPTH_INFINITE);
310                     }
311                 } catch (CoreException ce) {
312                     // In some cases, the workspace may be locked for modification when we pass
313                     // here, so we schedule a new job to put the marker after.
314                     Job markerJob = new Job("Android SDK: Resolving error markers") {
315                         @Override
316                         protected IStatus run(IProgressMonitor monitor) {
317                             try {
318                                 iProject.deleteMarkers(AndroidConstants.MARKER_TARGET, true,
319                                         IResource.DEPTH_INFINITE);
320                             } catch (CoreException e2) {
321                                 return e2.getStatus();
322                             }
323 
324                             return Status.OK_STATUS;
325                         }
326                     };
327 
328                     // build jobs are run after other interactive jobs
329                     markerJob.setPriority(Job.BUILD);
330                     markerJob.schedule();
331                 }
332             }
333         }
334     }
335 
336     /**
337      * Creates and returns an array of {@link IClasspathEntry} objects for the android
338      * framework and optional libraries.
339      * <p/>This references the OS path to the android.jar and the
340      * java doc directory. This is dynamically created when a project is opened,
341      * and never saved in the project itself, so there's no risk of storing an
342      * obsolete path.
343      * The method also stores the paths used to create the entries in the project persistent
344      * properties. A new {@link AndroidClasspathContainer} can be created from the stored path
345      * using the {@link #getContainerFromCache(IProject)} method.
346      * @param project
347      * @param target The target that contains the libraries.
348      * @param targetName
349      */
350     private static IClasspathEntry[] createClasspathEntries(IProject project,
351             IAndroidTarget target, String targetName) {
352 
353         // get the path from the target
354         String[] paths = getTargetPaths(target);
355 
356         // create the classpath entry from the paths
357         IClasspathEntry[] entries = createClasspathEntriesFromPaths(paths);
358 
359         // paths now contains all the path required to recreate the IClasspathEntry with no
360         // target info. We encode them in a single string, with each path separated by
361         // OS path separator.
362         StringBuilder sb = new StringBuilder(CACHE_VERSION);
363         for (String p : paths) {
364             sb.append(PATH_SEPARATOR);
365             sb.append(p);
366         }
367 
368         // store this in a project persistent property
369         ProjectHelper.saveStringProperty(project, PROPERTY_CONTAINER_CACHE, sb.toString());
370         ProjectHelper.saveStringProperty(project, PROPERTY_TARGET_NAME, targetName);
371 
372         return entries;
373     }
374 
375     /**
376      * Generates an {@link AndroidClasspathContainer} from the project cache, if possible.
377      */
378     private static AndroidClasspathContainer getContainerFromCache(IProject project) {
379         // get the cached info from the project persistent properties.
380         String cache = ProjectHelper.loadStringProperty(project, PROPERTY_CONTAINER_CACHE);
381         String targetNameCache = ProjectHelper.loadStringProperty(project, PROPERTY_TARGET_NAME);
382         if (cache == null || targetNameCache == null) {
383             return null;
384         }
385 
386         // the first 2 chars must match CACHE_VERSION. The 3rd char is the normal separator.
387         if (cache.startsWith(CACHE_VERSION_SEP) == false) {
388             return null;
389         }
390 
391         cache = cache.substring(CACHE_VERSION_SEP.length());
392 
393         // the cache contains multiple paths, separated by a character guaranteed to not be in
394         // the path (\u001C).
395         // The first 3 are for android.jar (jar, source, doc), the rest are for the optional
396         // libraries and should contain at least one doc and a jar (if there are any libraries).
397         // Therefore, the path count should be 3 or 5+
398         String[] paths = cache.split(Pattern.quote(PATH_SEPARATOR));
399         if (paths.length < 3 || paths.length == 4) {
400             return null;
401         }
402 
403         // now we check the paths actually exist.
404         // There's an exception: If the source folder for android.jar does not exist, this is
405         // not a problem, so we skip it.
406         // Also paths[CACHE_INDEX_DOCS_URI] is a URI to the javadoc, so we test it a
407         // bit differently.
408         try {
409             if (new File(paths[CACHE_INDEX_JAR]).exists() == false ||
410                     new File(new URI(paths[CACHE_INDEX_DOCS_URI])).exists() == false) {
411                 return null;
412             }
413 
414             // check the path for the add-ons, if they exist.
415             if (paths.length > CACHE_INDEX_ADD_ON_START) {
416 
417                 // check the docs path separately from the rest of the paths as it's a URI.
418                 if (new File(new URI(paths[CACHE_INDEX_OPT_DOCS_URI])).exists() == false) {
419                     return null;
420                 }
421 
422                 // now just check the remaining paths.
423                 for (int i = CACHE_INDEX_ADD_ON_START + 1; i < paths.length; i++) {
424                     String path = paths[i];
425                     if (path.length() > 0) {
426                         File f = new File(path);
427                         if (f.exists() == false) {
428                             return null;
429                         }
430                     }
431                 }
432             }
433         } catch (URISyntaxException e) {
434             return null;
435         }
436 
437         IClasspathEntry[] entries = createClasspathEntriesFromPaths(paths);
438 
439         return new AndroidClasspathContainer(entries,
440                 new Path(CONTAINER_ID), targetNameCache);
441     }
442 
443     /**
444      * Generates an array of {@link IClasspathEntry} from a set of paths.
445      * @see #getTargetPaths(IAndroidTarget)
446      */
447     private static IClasspathEntry[] createClasspathEntriesFromPaths(String[] paths) {
448         ArrayList<IClasspathEntry> list = new ArrayList<IClasspathEntry>();
449 
450         // First, we create the IClasspathEntry for the framework.
451         // now add the android framework to the class path.
452         // create the path object.
453         IPath android_lib = new Path(paths[CACHE_INDEX_JAR]);
454         IPath android_src = new Path(paths[CACHE_INDEX_SRC]);
455 
456         // create the java doc link.
457         IClasspathAttribute cpAttribute = JavaCore.newClasspathAttribute(
458                 IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME,
459                 paths[CACHE_INDEX_DOCS_URI]);
460 
461         // create the access rule to restrict access to classes in com.android.internal
462         IAccessRule accessRule = JavaCore.newAccessRule(
463                 new Path("com/android/internal/**"), //$NON-NLS-1$
464                 IAccessRule.K_NON_ACCESSIBLE);
465 
466         IClasspathEntry frameworkClasspathEntry = JavaCore.newLibraryEntry(android_lib,
467                 android_src, // source attachment path
468                 null,        // default source attachment root path.
469                 new IAccessRule[] { accessRule },
470                 new IClasspathAttribute[] { cpAttribute },
471                 false // not exported.
472                 );
473 
474         list.add(frameworkClasspathEntry);
475 
476         // now deal with optional libraries
477         if (paths.length >= 5) {
478             String docPath = paths[CACHE_INDEX_OPT_DOCS_URI];
479             int i = 4;
480             while (i < paths.length) {
481                 Path jarPath = new Path(paths[i++]);
482 
483                 IClasspathAttribute[] attributes = null;
484                 if (docPath.length() > 0) {
485                     attributes = new IClasspathAttribute[] {
486                             JavaCore.newClasspathAttribute(
487                                     IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME,
488                                     docPath)
489                     };
490                 }
491 
492                 IClasspathEntry entry = JavaCore.newLibraryEntry(
493                         jarPath,
494                         null, // source attachment path
495                         null, // default source attachment root path.
496                         null,
497                         attributes,
498                         false // not exported.
499                         );
500                 list.add(entry);
501             }
502         }
503 
504         return list.toArray(new IClasspathEntry[list.size()]);
505     }
506 
507     /**
508      * Checks the projects' caches. If the cache was valid, the project is removed from the list.
509      * @param projects the list of projects to check.
510      */
511     public static void checkProjectsCache(ArrayList<IJavaProject> projects) {
512         Sdk currentSdk = Sdk.getCurrent();
513         int i = 0;
514         projectLoop: while (i < projects.size()) {
515             IJavaProject javaProject = projects.get(i);
516             IProject iProject = javaProject.getProject();
517 
518             // check if the project is opened
519             if (iProject.isOpen() == false) {
520                 // remove from the list
521                 // we do not increment i in this case.
522                 projects.remove(i);
523 
524                 continue;
525             }
526 
527             // project that have been resolved before the sdk was loaded
528             // will have a ProjectState where the IAndroidTarget is null
529             // so we load the target now that the SDK is loaded.
530             IAndroidTarget target = currentSdk.loadTarget(Sdk.getProjectState(iProject));
531             if (target == null) {
532                 // this is really not supposed to happen. This would mean there are cached paths,
533                 // but default.properties was deleted. Keep the project in the list to force
534                 // a resolve which will display the error.
535                 i++;
536                 continue;
537             }
538 
539             String[] targetPaths = getTargetPaths(target);
540 
541             // now get the cached paths
542             String cache = ProjectHelper.loadStringProperty(iProject, PROPERTY_CONTAINER_CACHE);
543             if (cache == null) {
544                 // this should not happen. We'll force resolve again anyway.
545                 i++;
546                 continue;
547             }
548 
549             String[] cachedPaths = cache.split(Pattern.quote(PATH_SEPARATOR));
550             if (cachedPaths.length < 3 || cachedPaths.length == 4) {
551                 // paths length is wrong. simply resolve the project again
552                 i++;
553                 continue;
554             }
555 
556             // Now we compare the paths. The first 4 can be compared directly.
557             // because of case sensitiveness we need to use File objects
558 
559             if (targetPaths.length != cachedPaths.length) {
560                 // different paths, force resolve again.
561                 i++;
562                 continue;
563             }
564 
565             // compare the main paths (android.jar, main sources, main javadoc)
566             if (new File(targetPaths[CACHE_INDEX_JAR]).equals(
567                             new File(cachedPaths[CACHE_INDEX_JAR])) == false ||
568                     new File(targetPaths[CACHE_INDEX_SRC]).equals(
569                             new File(cachedPaths[CACHE_INDEX_SRC])) == false ||
570                     new File(targetPaths[CACHE_INDEX_DOCS_URI]).equals(
571                             new File(cachedPaths[CACHE_INDEX_DOCS_URI])) == false) {
572                 // different paths, force resolve again.
573                 i++;
574                 continue;
575             }
576 
577             if (cachedPaths.length > CACHE_INDEX_OPT_DOCS_URI) {
578                 // compare optional libraries javadoc
579                 if (new File(targetPaths[CACHE_INDEX_OPT_DOCS_URI]).equals(
580                         new File(cachedPaths[CACHE_INDEX_OPT_DOCS_URI])) == false) {
581                     // different paths, force resolve again.
582                     i++;
583                     continue;
584                 }
585 
586                 // testing the optional jar files is a little bit trickier.
587                 // The order is not guaranteed to be identical.
588                 // From a previous test, we do know however that there is the same number.
589                 // The number of libraries should be low enough that we can simply go through the
590                 // lists manually.
591                 targetLoop: for (int tpi = 4 ; tpi < targetPaths.length; tpi++) {
592                     String targetPath = targetPaths[tpi];
593 
594                     // look for a match in the other array
595                     for (int cpi = 4 ; cpi < cachedPaths.length; cpi++) {
596                         if (new File(targetPath).equals(new File(cachedPaths[cpi]))) {
597                             // found a match. Try the next targetPath
598                             continue targetLoop;
599                         }
600                     }
601 
602                     // if we stop here, we haven't found a match, which means there's a
603                     // discrepancy in the libraries. We force a resolve.
604                     i++;
605                     continue projectLoop;
606                 }
607             }
608 
609             // at the point the check passes, and we can remove the project from the list.
610             // we do not increment i in this case.
611             projects.remove(i);
612         }
613     }
614 
615     /**
616      * Returns the paths necessary to create the {@link IClasspathEntry} for this targets.
617      * <p/>The paths are always in the same order.
618      * <ul>
619      * <li>Path to android.jar</li>
620      * <li>Path to the source code for android.jar</li>
621      * <li>Path to the javadoc for the android platform</li>
622      * </ul>
623      * Additionally, if there are optional libraries, the array will contain:
624      * <ul>
625      * <li>Path to the libraries javadoc</li>
626      * <li>Path to the first .jar file</li>
627      * <li>(more .jar as needed)</li>
628      * </ul>
629      */
630     private static String[] getTargetPaths(IAndroidTarget target) {
631         ArrayList<String> paths = new ArrayList<String>();
632 
633         // first, we get the path for android.jar
634         // The order is: android.jar, source folder, docs folder
635         paths.add(target.getPath(IAndroidTarget.ANDROID_JAR));
636         paths.add(target.getPath(IAndroidTarget.SOURCES));
637         paths.add(AdtPlugin.getUrlDoc());
638 
639         // now deal with optional libraries.
640         IOptionalLibrary[] libraries = target.getOptionalLibraries();
641         if (libraries != null) {
642             // all the optional libraries use the same javadoc, so we start with this
643             String targetDocPath = target.getPath(IAndroidTarget.DOCS);
644             if (targetDocPath != null) {
645                 paths.add(ProjectHelper.getJavaDocPath(targetDocPath));
646             } else {
647                 // we add an empty string, to always have the same count.
648                 paths.add("");
649             }
650 
651             // because different libraries could use the same jar file, we make sure we add
652             // each jar file only once.
653             HashSet<String> visitedJars = new HashSet<String>();
654             for (IOptionalLibrary library : libraries) {
655                 String jarPath = library.getJarPath();
656                 if (visitedJars.contains(jarPath) == false) {
657                     visitedJars.add(jarPath);
658                     paths.add(jarPath);
659                 }
660             }
661         }
662 
663         return paths.toArray(new String[paths.size()]);
664     }
665 }
666