aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java
blob: 8fbee4089eb7ca9df101b6db546617415217c513 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Eclipse Public License, Version 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.eclipse.org/org/documents/epl-v10.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.ide.eclipse.adt.internal.project;

import static com.android.ide.eclipse.adt.AdtConstants.CONTAINER_DEPENDENCIES;

import com.android.SdkConstants;
import com.android.ide.common.sdk.LoadStatus;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AndroidPrintStream;
import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.sdklib.BuildToolInfo;
import com.android.sdklib.build.JarListSanitizer;
import com.android.sdklib.build.JarListSanitizer.DifferentLibException;
import com.android.sdklib.build.JarListSanitizer.Sha1Exception;
import com.android.sdklib.build.RenderScriptProcessor;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IAccessRule;
import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.IClasspathContainer;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;

public class LibraryClasspathContainerInitializer extends BaseClasspathContainerInitializer {

    private final static String ATTR_SRC = "src"; //$NON-NLS-1$
    private final static String ATTR_DOC = "doc"; //$NON-NLS-1$
    private final static String DOT_PROPERTIES = ".properties"; //$NON-NLS-1$

    public LibraryClasspathContainerInitializer() {
    }

    /**
     * Updates the {@link IJavaProject} objects with new library.
     * @param androidProjects the projects to update.
     * @return <code>true</code> if success, <code>false</code> otherwise.
     */
    public static boolean updateProjects(IJavaProject[] androidProjects) {
        try {
            // Allocate a new AndroidClasspathContainer, and associate it to the library
            // container id for each projects.
            int projectCount = androidProjects.length;

            IClasspathContainer[] libraryContainers = new IClasspathContainer[projectCount];
            IClasspathContainer[] dependencyContainers = new IClasspathContainer[projectCount];
            for (int i = 0 ; i < projectCount; i++) {
                libraryContainers[i] = allocateLibraryContainer(androidProjects[i]);
                dependencyContainers[i] = allocateDependencyContainer(androidProjects[i]);
            }

            // give each project their new container in one call.
            JavaCore.setClasspathContainer(
                    new Path(AdtConstants.CONTAINER_PRIVATE_LIBRARIES),
                    androidProjects, libraryContainers, new NullProgressMonitor());

            JavaCore.setClasspathContainer(
                    new Path(AdtConstants.CONTAINER_DEPENDENCIES),
                    androidProjects, dependencyContainers, new NullProgressMonitor());
            return true;
        } catch (JavaModelException e) {
            return false;
        }
    }

    /**
     * Updates the {@link IJavaProject} objects with new library.
     * @param androidProjects the projects to update.
     * @return <code>true</code> if success, <code>false</code> otherwise.
     */
    public static boolean updateProject(List<ProjectState> projects) {
        List<IJavaProject> javaProjectList = new ArrayList<IJavaProject>(projects.size());
        for (ProjectState p : projects) {
            IJavaProject javaProject = JavaCore.create(p.getProject());
            if (javaProject != null) {
                javaProjectList.add(javaProject);
            }
        }

        IJavaProject[] javaProjects = javaProjectList.toArray(
                new IJavaProject[javaProjectList.size()]);

        return updateProjects(javaProjects);
    }

    @Override
    public void initialize(IPath containerPath, IJavaProject project) throws CoreException {
        if (AdtConstants.CONTAINER_PRIVATE_LIBRARIES.equals(containerPath.toString())) {
            IClasspathContainer libraries = allocateLibraryContainer(project);
            if (libraries != null) {
                JavaCore.setClasspathContainer(new Path(AdtConstants.CONTAINER_PRIVATE_LIBRARIES),
                        new IJavaProject[] { project },
                        new IClasspathContainer[] { libraries },
                        new NullProgressMonitor());
            }

        } else if(AdtConstants.CONTAINER_DEPENDENCIES.equals(containerPath.toString())) {
            IClasspathContainer dependencies = allocateDependencyContainer(project);
            if (dependencies != null) {
                JavaCore.setClasspathContainer(new Path(AdtConstants.CONTAINER_DEPENDENCIES),
                        new IJavaProject[] { project },
                        new IClasspathContainer[] { dependencies },
                        new NullProgressMonitor());
            }
        }
    }

    private static IClasspathContainer allocateLibraryContainer(IJavaProject javaProject) {
        final IProject iProject = javaProject.getProject();

        // check if the project has a valid target.
        ProjectState state = Sdk.getProjectState(iProject);
        if (state == null) {
            // getProjectState should already have logged an error. Just bail out.
            return null;
        }

        /*
         * At this point we're going to gather a list of all that need to go in the
         * dependency container.
         * - Library project outputs (direct and indirect)
         * - Java project output (those can be indirectly referenced through library projects
         *   or other other Java projects)
         * - Jar files:
         *    + inside this project's libs/
         *    + inside the library projects' libs/
         *    + inside the referenced Java projects' classpath
         */
        List<IClasspathEntry> entries = new ArrayList<IClasspathEntry>();

        // list of java project dependencies and jar files that will be built while
        // going through the library projects.
        Set<File> jarFiles = new HashSet<File>();
        Set<IProject> refProjects = new HashSet<IProject>();

        // process all the libraries

        List<IProject> libProjects = state.getFullLibraryProjects();
        for (IProject libProject : libProjects) {
            // process all of the library project's dependencies
            getDependencyListFromClasspath(libProject, refProjects, jarFiles, true);
        }

        // now process this projects' referenced projects only.
        processReferencedProjects(iProject, refProjects, jarFiles);

        // and the content of its libs folder
        getJarListFromLibsFolder(iProject, jarFiles);

        // now add a classpath entry for each Java project (this is a set so dups are already
        // removed)
        for (IProject p : refProjects) {
            entries.add(JavaCore.newProjectEntry(p.getFullPath(), true /*isExported*/));
        }

        entries.addAll(convertJarsToClasspathEntries(iProject, jarFiles));

        return allocateContainer(javaProject, entries, new Path(AdtConstants.CONTAINER_PRIVATE_LIBRARIES),
                "Android Private Libraries");
    }

    private static List<IClasspathEntry> convertJarsToClasspathEntries(final IProject iProject,
            Set<File> jarFiles) {
        List<IClasspathEntry> entries = new ArrayList<IClasspathEntry>(jarFiles.size());

        // and process the jar files list, but first sanitize it to remove dups.
        JarListSanitizer sanitizer = new JarListSanitizer(
                iProject.getFolder(SdkConstants.FD_OUTPUT).getLocation().toFile(),
                new AndroidPrintStream(iProject, null /*prefix*/,
                        AdtPlugin.getOutStream()));

        String errorMessage = null;

        try {
            List<File> sanitizedList = sanitizer.sanitize(jarFiles);

            for (File jarFile : sanitizedList) {
                if (jarFile instanceof CPEFile) {
                    CPEFile cpeFile = (CPEFile) jarFile;
                    IClasspathEntry e = cpeFile.getClasspathEntry();

                    entries.add(JavaCore.newLibraryEntry(
                            e.getPath(),
                            e.getSourceAttachmentPath(),
                            e.getSourceAttachmentRootPath(),
                            e.getAccessRules(),
                            e.getExtraAttributes(),
                            true /*isExported*/));
                } else {
                    String jarPath = jarFile.getAbsolutePath();

                    IPath sourceAttachmentPath = null;
                    IClasspathAttribute javaDocAttribute = null;

                    File jarProperties = new File(jarPath + DOT_PROPERTIES);
                    if (jarProperties.isFile()) {
                        Properties p = new Properties();
                        InputStream is = null;
                        try {
                            p.load(is = new FileInputStream(jarProperties));

                            String value = p.getProperty(ATTR_SRC);
                            if (value != null) {
                                File srcPath = getFile(jarFile, value);

                                if (srcPath.exists()) {
                                    sourceAttachmentPath = new Path(srcPath.getAbsolutePath());
                                }
                            }

                            value = p.getProperty(ATTR_DOC);
                            if (value != null) {
                                File docPath = getFile(jarFile, value);
                                if (docPath.exists()) {
                                    try {
                                        javaDocAttribute = JavaCore.newClasspathAttribute(
                                                IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME,
                                                docPath.toURI().toURL().toString());
                                    } catch (MalformedURLException e) {
                                        AdtPlugin.log(e, "Failed to process 'doc' attribute for %s",
                                                jarProperties.getAbsolutePath());
                                    }
                                }
                            }

                        } catch (FileNotFoundException e) {
                            // shouldn't happen since we check upfront
                        } catch (IOException e) {
                            AdtPlugin.log(e, "Failed to read %s", jarProperties.getAbsolutePath());
                        } finally {
                            if (is != null) {
                                try {
                                    is.close();
                                } catch (IOException e) {
                                    // ignore
                                }
                            }
                        }
                    }

                    if (javaDocAttribute != null) {
                        entries.add(JavaCore.newLibraryEntry(new Path(jarPath),
                                sourceAttachmentPath, null /*sourceAttachmentRootPath*/,
                                new IAccessRule[0],
                                new IClasspathAttribute[] { javaDocAttribute },
                                true /*isExported*/));
                    } else {
                        entries.add(JavaCore.newLibraryEntry(new Path(jarPath),
                                sourceAttachmentPath, null /*sourceAttachmentRootPath*/,
                                true /*isExported*/));
                    }
                }
            }
        } catch (DifferentLibException e) {
            errorMessage = e.getMessage();
            AdtPlugin.printErrorToConsole(iProject, (Object[]) e.getDetails());
        } catch (Sha1Exception e) {
            errorMessage = e.getMessage();
        }

        processError(iProject, errorMessage, AdtConstants.MARKER_DEPENDENCY,
                true /*outputToConsole*/);

        return entries;
    }

    private static IClasspathContainer allocateDependencyContainer(IJavaProject javaProject) {
        final IProject iProject = javaProject.getProject();
        final List<IClasspathEntry> entries = new ArrayList<IClasspathEntry>();
        final Set<File> jarFiles = new HashSet<File>();
        final IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();

        AdtPlugin plugin = AdtPlugin.getDefault();
        if (plugin == null) { // This is totally weird, but I've seen it happen!
            return null;
        }

        synchronized (Sdk.getLock()) {
            boolean sdkIsLoaded = plugin.getSdkLoadStatus() == LoadStatus.LOADED;

            // check if the project has a valid target.
            final ProjectState state = Sdk.getProjectState(iProject);
            if (state == null) {
                // getProjectState should already have logged an error. Just bail out.
                return null;
            }

            // annotations support for older version of android
            if (state.getTarget() != null && state.getTarget().getVersion().getApiLevel() <= 15) {
                File annotationsJar = new File(Sdk.getCurrent().getSdkOsLocation(),
                        SdkConstants.FD_TOOLS + File.separator + SdkConstants.FD_SUPPORT +
                        File.separator + SdkConstants.FN_ANNOTATIONS_JAR);

                jarFiles.add(annotationsJar);
            }

            if (state.getRenderScriptSupportMode()) {
                if (!sdkIsLoaded) {
                    return null;
                }
                BuildToolInfo buildToolInfo = state.getBuildToolInfo();
                if (buildToolInfo == null) {
                    buildToolInfo = Sdk.getCurrent().getLatestBuildTool();

                    if (buildToolInfo == null) {
                        return null;
                    }
                }

                File renderScriptSupportJar = RenderScriptProcessor.getSupportJar(
                        buildToolInfo.getLocation().getAbsolutePath());

                jarFiles.add(renderScriptSupportJar);
            }

            // process all the libraries

            List<IProject> libProjects = state.getFullLibraryProjects();
            for (IProject libProject : libProjects) {
                // get the project output
                IFolder outputFolder = BaseProjectHelper.getAndroidOutputFolder(libProject);

                if (outputFolder != null) { // can happen when closing/deleting a library)
                    IFile jarIFile = outputFolder.getFile(libProject.getName().toLowerCase() +
                           SdkConstants.DOT_JAR);

                    // get the source folder for the library project
                    List<IPath> srcs = BaseProjectHelper.getSourceClasspaths(libProject);
                    // find the first non-derived source folder.
                    IPath sourceFolder = null;
                    for (IPath src : srcs) {
                        IFolder srcFolder = workspaceRoot.getFolder(src);
                        if (srcFolder.isDerived() == false) {
                            sourceFolder = src;
                            break;
                        }
                    }

                    // we can directly add a CPE for this jar as there's no risk of a duplicate.
                    IClasspathEntry entry = JavaCore.newLibraryEntry(
                            jarIFile.getLocation(),
                            sourceFolder, // source attachment path
                            null,         // default source attachment root path.
                            true /*isExported*/);

                    entries.add(entry);
                }
            }

            entries.addAll(convertJarsToClasspathEntries(iProject, jarFiles));

            return allocateContainer(javaProject, entries, new Path(CONTAINER_DEPENDENCIES),
                    "Android Dependencies");
        }
    }

    private static IClasspathContainer allocateContainer(IJavaProject javaProject,
            List<IClasspathEntry> entries, IPath id, String description) {

        if (AdtPlugin.getDefault() == null) { // This is totally weird, but I've seen it happen!
            return null;
        }

        // First check that the project has a library-type container.
        try {
            IClasspathEntry[] rawClasspath = javaProject.getRawClasspath();
            final IClasspathEntry[] oldRawClasspath = rawClasspath;

            boolean foundContainer = false;
            for (IClasspathEntry entry : rawClasspath) {
                // get the entry and kind
                final int kind = entry.getEntryKind();

                if (kind == IClasspathEntry.CPE_CONTAINER) {
                    String path = entry.getPath().toString();
                    String idString = id.toString();
                    if (idString.equals(path)) {
                        foundContainer = true;
                        break;
                    }
                }
            }

            // if there isn't any, add it.
            if (foundContainer == false) {
                // add the android container to the array
                rawClasspath = ProjectHelper.addEntryToClasspath(rawClasspath,
                        JavaCore.newContainerEntry(id, true /*isExported*/));
            }

            // set the new list of entries to the project
            if (rawClasspath != oldRawClasspath) {
                javaProject.setRawClasspath(rawClasspath, new NullProgressMonitor());
            }
        } catch (JavaModelException e) {
            // This really shouldn't happen, but if it does, simply return null (the calling
            // method will fails as well)
            return null;
        }

        return new AndroidClasspathContainer(
                entries.toArray(new IClasspathEntry[entries.size()]),
                id,
                description,
                IClasspathContainer.K_APPLICATION);
    }

    private static File getFile(File root, String value) {
        File file = new File(value);
        if (file.isAbsolute() == false) {
            file = new File(root.getParentFile(), value);
        }

        return file;
    }

    /**
     * Finds all the jar files inside a project's libs folder.
     * @param project
     * @param jarFiles
     */
    private static void getJarListFromLibsFolder(IProject project, Set<File> jarFiles) {
        IFolder libsFolder = project.getFolder(SdkConstants.FD_NATIVE_LIBS);
        if (libsFolder.exists()) {
            try {
                IResource[] members = libsFolder.members();
                for (IResource member : members) {
                    if (member.getType() == IResource.FILE &&
                            SdkConstants.EXT_JAR.equalsIgnoreCase(member.getFileExtension())) {
                        IPath location = member.getLocation();
                        if (location != null) {
                            jarFiles.add(location.toFile());
                        }
                    }
                }
            } catch (CoreException e) {
                // can't get the list? ignore this folder.
            }
        }
    }

    /**
     * Process reference projects from the main projects to add indirect dependencies coming
     * from Java project.
     * @param project the main project
     * @param projects the project list to add to
     * @param jarFiles the jar list to add to.
     */
    private static void processReferencedProjects(IProject project,
            Set<IProject> projects, Set<File> jarFiles) {
        try {
            IProject[] refs = project.getReferencedProjects();
            for (IProject p : refs) {
                // ignore if it's an Android project, or if it's not a Java
                // Project
                if (p.hasNature(JavaCore.NATURE_ID)
                        && p.hasNature(AdtConstants.NATURE_DEFAULT) == false) {

                    // process this project's dependencies
                    getDependencyListFromClasspath(p, projects, jarFiles, true /*includeJarFiles*/);
                }
            }
        } catch (CoreException e) {
            // can't get the referenced projects? ignore
        }
    }

    /**
     * Finds all the dependencies of a given project and add them to a project list and
     * a jar list.
     * Only classpath entries that are exported are added, and only Java project (not Android
     * project) are added.
     *
     * @param project the project to query
     * @param projects the referenced project list to add to
     * @param jarFiles the jar list to add to
     * @param includeJarFiles whether to include jar files or just projects. This is useful when
     *           calling on an Android project (value should be <code>false</code>)
     */
    private static void getDependencyListFromClasspath(IProject project, Set<IProject> projects,
            Set<File> jarFiles, boolean includeJarFiles) {
        IJavaProject javaProject = JavaCore.create(project);
        IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();

        // we could use IJavaProject.getResolvedClasspath directly, but we actually
        // want to see the containers themselves.
        IClasspathEntry[] classpaths = javaProject.readRawClasspath();
        if (classpaths != null) {
            for (IClasspathEntry e : classpaths) {
                // ignore entries that are not exported
                if (!e.getPath().toString().equals(CONTAINER_DEPENDENCIES) && e.isExported()) {
                    processCPE(e, javaProject, wsRoot, projects, jarFiles, includeJarFiles);
                }
            }
        }
    }

    /**
     * Processes a {@link IClasspathEntry} and add it to one of the list if applicable.
     * @param entry the entry to process
     * @param javaProject the {@link IJavaProject} from which this entry came.
     * @param wsRoot the {@link IWorkspaceRoot}
     * @param projects the project list to add to
     * @param jarFiles the jar list to add to
     * @param includeJarFiles whether to include jar files or just projects. This is useful when
     *           calling on an Android project (value should be <code>false</code>)
     */
    private static void processCPE(IClasspathEntry entry, IJavaProject javaProject,
            IWorkspaceRoot wsRoot,
            Set<IProject> projects, Set<File> jarFiles, boolean includeJarFiles) {

        // if this is a classpath variable reference, we resolve it.
        if (entry.getEntryKind() == IClasspathEntry.CPE_VARIABLE) {
            entry = JavaCore.getResolvedClasspathEntry(entry);
        }

        if (entry.getEntryKind() == IClasspathEntry.CPE_PROJECT) {
            IProject refProject = wsRoot.getProject(entry.getPath().lastSegment());
            try {
                // ignore if it's an Android project, or if it's not a Java Project
                if (refProject.hasNature(JavaCore.NATURE_ID) &&
                        refProject.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
                    // add this project to the list
                    projects.add(refProject);

                    // also get the dependency from this project.
                    getDependencyListFromClasspath(refProject, projects, jarFiles,
                            true /*includeJarFiles*/);
                }
            } catch (CoreException exception) {
                // can't query the project nature? ignore
            }
        } else if (entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) {
            if (includeJarFiles) {
                handleClasspathLibrary(entry, wsRoot, jarFiles);
            }
        } else if (entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) {
            // get the container and its content
            try {
                IClasspathContainer container = JavaCore.getClasspathContainer(
                        entry.getPath(), javaProject);
                // ignore the system and default_system types as they represent
                // libraries that are part of the runtime.
                if (container != null &&
                        container.getKind() == IClasspathContainer.K_APPLICATION) {
                    IClasspathEntry[] entries = container.getClasspathEntries();
                    for (IClasspathEntry cpe : entries) {
                        processCPE(cpe, javaProject, wsRoot, projects, jarFiles, includeJarFiles);
                    }
                }
            } catch (JavaModelException jme) {
                // can't resolve the container? ignore it.
                AdtPlugin.log(jme, "Failed to resolve ClasspathContainer: %s", entry.getPath());
            }
        }
    }

    private static final class CPEFile extends File {
        private static final long serialVersionUID = 1L;

        private final IClasspathEntry mClasspathEntry;

        public CPEFile(String pathname, IClasspathEntry classpathEntry) {
            super(pathname);
            mClasspathEntry = classpathEntry;
        }

        public CPEFile(File file, IClasspathEntry classpathEntry) {
            super(file.getAbsolutePath());
            mClasspathEntry = classpathEntry;
        }

        public IClasspathEntry getClasspathEntry() {
            return mClasspathEntry;
        }
    }

    private static void handleClasspathLibrary(IClasspathEntry e, IWorkspaceRoot wsRoot,
            Set<File> jarFiles) {
        // get the IPath
        IPath path = e.getPath();

        IResource resource = wsRoot.findMember(path);

        if (SdkConstants.EXT_JAR.equalsIgnoreCase(path.getFileExtension())) {
            // case of a jar file (which could be relative to the workspace or a full path)
            if (resource != null && resource.exists() &&
                    resource.getType() == IResource.FILE) {
                jarFiles.add(new CPEFile(resource.getLocation().toFile(), e));
            } else {
                // if the jar path doesn't match a workspace resource,
                // then we get an OSString and check if this links to a valid file.
                String osFullPath = path.toOSString();

                File f = new CPEFile(osFullPath, e);
                if (f.isFile()) {
                    jarFiles.add(f);
                }
            }
        }
    }
}