OSDN Git Service

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