Home | History | Annotate | Download | only in project
      1 /*
      2  * Copyright (C) 2011 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.SdkConstants;
     20 import com.android.ide.eclipse.adt.AdtConstants;
     21 import com.android.ide.eclipse.adt.AdtPlugin;
     22 import com.android.ide.eclipse.adt.AndroidPrintStream;
     23 import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
     24 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     25 import com.android.sdklib.build.JarListSanitizer;
     26 import com.android.sdklib.build.JarListSanitizer.DifferentLibException;
     27 import com.android.sdklib.build.JarListSanitizer.Sha1Exception;
     28 
     29 import org.eclipse.core.resources.IFile;
     30 import org.eclipse.core.resources.IFolder;
     31 import org.eclipse.core.resources.IProject;
     32 import org.eclipse.core.resources.IResource;
     33 import org.eclipse.core.resources.IWorkspaceRoot;
     34 import org.eclipse.core.resources.ResourcesPlugin;
     35 import org.eclipse.core.runtime.CoreException;
     36 import org.eclipse.core.runtime.IPath;
     37 import org.eclipse.core.runtime.NullProgressMonitor;
     38 import org.eclipse.core.runtime.Path;
     39 import org.eclipse.jdt.core.IAccessRule;
     40 import org.eclipse.jdt.core.IClasspathAttribute;
     41 import org.eclipse.jdt.core.IClasspathContainer;
     42 import org.eclipse.jdt.core.IClasspathEntry;
     43 import org.eclipse.jdt.core.IJavaProject;
     44 import org.eclipse.jdt.core.JavaCore;
     45 import org.eclipse.jdt.core.JavaModelException;
     46 
     47 import java.io.File;
     48 import java.io.FileInputStream;
     49 import java.io.FileNotFoundException;
     50 import java.io.IOException;
     51 import java.io.InputStream;
     52 import java.net.MalformedURLException;
     53 import java.util.ArrayList;
     54 import java.util.HashSet;
     55 import java.util.List;
     56 import java.util.Properties;
     57 import java.util.Set;
     58 
     59 public class LibraryClasspathContainerInitializer extends BaseClasspathContainerInitializer {
     60 
     61     private final static String ATTR_SRC = "src"; //$NON-NLS-1$
     62     private final static String ATTR_DOC = "doc"; //$NON-NLS-1$
     63     private final static String DOT_PROPERTIES = ".properties"; //$NON-NLS-1$
     64 
     65     public LibraryClasspathContainerInitializer() {
     66     }
     67 
     68     /**
     69      * Updates the {@link IJavaProject} objects with new library.
     70      * @param androidProjects the projects to update.
     71      * @return <code>true</code> if success, <code>false</code> otherwise.
     72      */
     73     public static boolean updateProjects(IJavaProject[] androidProjects) {
     74         try {
     75             // Allocate a new AndroidClasspathContainer, and associate it to the library
     76             // container id for each projects.
     77             int projectCount = androidProjects.length;
     78 
     79             IClasspathContainer[] containers = new IClasspathContainer[projectCount];
     80             for (int i = 0 ; i < projectCount; i++) {
     81                 containers[i] = allocateLibraryContainer(androidProjects[i]);
     82             }
     83 
     84             // give each project their new container in one call.
     85             JavaCore.setClasspathContainer(
     86                     new Path(AdtConstants.CONTAINER_LIBRARIES),
     87                     androidProjects, containers, new NullProgressMonitor());
     88 
     89             return true;
     90         } catch (JavaModelException e) {
     91             return false;
     92         }
     93     }
     94 
     95     /**
     96      * Updates the {@link IJavaProject} objects with new library.
     97      * @param androidProjects the projects to update.
     98      * @return <code>true</code> if success, <code>false</code> otherwise.
     99      */
    100     public static boolean updateProject(List<ProjectState> projects) {
    101         List<IJavaProject> javaProjectList = new ArrayList<IJavaProject>(projects.size());
    102         for (ProjectState p : projects) {
    103             IJavaProject javaProject = JavaCore.create(p.getProject());
    104             if (javaProject != null) {
    105                 javaProjectList.add(javaProject);
    106             }
    107         }
    108 
    109         IJavaProject[] javaProjects = javaProjectList.toArray(
    110                 new IJavaProject[javaProjectList.size()]);
    111 
    112         return updateProjects(javaProjects);
    113     }
    114 
    115     @Override
    116     public void initialize(IPath containerPath, IJavaProject project) throws CoreException {
    117         if (AdtConstants.CONTAINER_LIBRARIES.equals(containerPath.toString())) {
    118             IClasspathContainer container = allocateLibraryContainer(project);
    119             if (container != null) {
    120                 JavaCore.setClasspathContainer(new Path(AdtConstants.CONTAINER_LIBRARIES),
    121                         new IJavaProject[] { project },
    122                         new IClasspathContainer[] { container },
    123                         new NullProgressMonitor());
    124             }
    125         }
    126     }
    127 
    128     private static IClasspathContainer allocateLibraryContainer(IJavaProject javaProject) {
    129         final IProject iProject = javaProject.getProject();
    130 
    131         AdtPlugin plugin = AdtPlugin.getDefault();
    132         if (plugin == null) { // This is totally weird, but I've seen it happen!
    133             return null;
    134         }
    135 
    136         // First check that the project has a library-type container.
    137         try {
    138             IClasspathEntry[] rawClasspath = javaProject.getRawClasspath();
    139             IClasspathEntry[] oldRawClasspath = rawClasspath;
    140 
    141             boolean foundLibrariesContainer = false;
    142             for (IClasspathEntry entry : rawClasspath) {
    143                 // get the entry and kind
    144                 int kind = entry.getEntryKind();
    145 
    146                 if (kind == IClasspathEntry.CPE_CONTAINER) {
    147                     String path = entry.getPath().toString();
    148                     if (AdtConstants.CONTAINER_LIBRARIES.equals(path)) {
    149                         foundLibrariesContainer = true;
    150                         break;
    151                     }
    152                 }
    153             }
    154 
    155             // if there isn't any, add it.
    156             if (foundLibrariesContainer == false) {
    157                 // add the android container to the array
    158                 rawClasspath = ProjectHelper.addEntryToClasspath(rawClasspath,
    159                         JavaCore.newContainerEntry(new Path(AdtConstants.CONTAINER_LIBRARIES),
    160                                 true /*isExported*/));
    161             }
    162 
    163             // set the new list of entries to the project
    164             if (rawClasspath != oldRawClasspath) {
    165                 javaProject.setRawClasspath(rawClasspath, new NullProgressMonitor());
    166             }
    167         } catch (JavaModelException e) {
    168             // This really shouldn't happen, but if it does, simply return null (the calling
    169             // method will fails as well)
    170             return null;
    171         }
    172 
    173         // check if the project has a valid target.
    174         ProjectState state = Sdk.getProjectState(iProject);
    175         if (state == null) {
    176             // getProjectState should already have logged an error. Just bail out.
    177             return null;
    178         }
    179 
    180         /*
    181          * At this point we're going to gather a list of all that need to go in the
    182          * dependency container.
    183          * - Library project outputs (direct and indirect)
    184          * - Java project output (those can be indirectly referenced through library projects
    185          *   or other other Java projects)
    186          * - Jar files:
    187          *    + inside this project's libs/
    188          *    + inside the library projects' libs/
    189          *    + inside the referenced Java projects' classpath
    190          */
    191 
    192         List<IClasspathEntry> entries = new ArrayList<IClasspathEntry>();
    193 
    194         IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
    195 
    196         // list of java project dependencies and jar files that will be built while
    197         // going through the library projects.
    198         Set<File> jarFiles = new HashSet<File>();
    199         Set<IProject> refProjects = new HashSet<IProject>();
    200 
    201         // process all the libraries
    202 
    203         List<IProject> libProjects = state.getFullLibraryProjects();
    204         for (IProject libProject : libProjects) {
    205             // get the project output
    206             IFolder outputFolder = BaseProjectHelper.getAndroidOutputFolder(libProject);
    207 
    208             if (outputFolder != null) { // can happen when closing/deleting a library)
    209                 IFile jarIFile = outputFolder.getFile(libProject.getName().toLowerCase() +
    210                         SdkConstants.DOT_JAR);
    211 
    212                 // get the source folder for the library project
    213                 List<IPath> srcs = BaseProjectHelper.getSourceClasspaths(libProject);
    214                 // find the first non-derived source folder.
    215                 IPath sourceFolder = null;
    216                 for (IPath src : srcs) {
    217                     IFolder srcFolder = workspaceRoot.getFolder(src);
    218                     if (srcFolder.isDerived() == false) {
    219                         sourceFolder = src;
    220                         break;
    221                     }
    222                 }
    223 
    224                 // we can directly add a CPE for this jar as there's no risk of a duplicate.
    225                 IClasspathEntry entry = JavaCore.newLibraryEntry(
    226                         jarIFile.getLocation(),
    227                         sourceFolder, // source attachment path
    228                         null,         // default source attachment root path.
    229                         true /*isExported*/);
    230 
    231                 entries.add(entry);
    232 
    233                 // process all of the library project's dependencies
    234                 getDependencyListFromClasspath(libProject, refProjects, jarFiles, true);
    235                 // and the content of its libs folder.
    236                 getJarListFromLibsFolder(libProject, jarFiles);
    237             }
    238         }
    239 
    240         // now process this projects' referenced projects only.
    241         processReferencedProjects(iProject, refProjects, jarFiles);
    242         // and the content of its libs folder
    243         getJarListFromLibsFolder(iProject, jarFiles);
    244 
    245         // annotations support for older version of android
    246         if (state.getTarget() != null && state.getTarget().getVersion().getApiLevel() <= 15) {
    247             File annotationsJar = new File(Sdk.getCurrent().getSdkLocation(),
    248                     SdkConstants.FD_TOOLS + File.separator + SdkConstants.FD_SUPPORT +
    249                     File.separator + SdkConstants.FN_ANNOTATIONS_JAR);
    250 
    251             jarFiles.add(annotationsJar);
    252         }
    253 
    254         // now add a classpath entry for each Java project (this is a set so dups are already
    255         // removed)
    256         for (IProject p : refProjects) {
    257             entries.add(JavaCore.newProjectEntry(p.getFullPath(), true /*isExported*/));
    258         }
    259 
    260         // and process the jar files list, but first sanitize it to remove dups.
    261         JarListSanitizer sanitizer = new JarListSanitizer(
    262                 iProject.getFolder(SdkConstants.FD_OUTPUT).getLocation().toFile(),
    263                 new AndroidPrintStream(iProject, null /*prefix*/,
    264                         AdtPlugin.getOutStream()));
    265 
    266         String errorMessage = null;
    267 
    268         try {
    269             List<File> sanitizedList = sanitizer.sanitize(jarFiles);
    270 
    271             for (File jarFile : sanitizedList) {
    272                 if (jarFile instanceof CPEFile) {
    273                     CPEFile cpeFile = (CPEFile) jarFile;
    274                     IClasspathEntry e = cpeFile.getClasspathEntry();
    275 
    276                     entries.add(JavaCore.newLibraryEntry(
    277                             e.getPath(),
    278                             e.getSourceAttachmentPath(),
    279                             e.getSourceAttachmentRootPath(),
    280                             e.getAccessRules(),
    281                             e.getExtraAttributes(),
    282                             true /*isExported*/));
    283                 } else {
    284                     String jarPath = jarFile.getAbsolutePath();
    285 
    286                     IPath sourceAttachmentPath = null;
    287                     IClasspathAttribute javaDocAttribute = null;
    288 
    289                     File jarProperties = new File(jarPath + DOT_PROPERTIES);
    290                     if (jarProperties.isFile()) {
    291                         Properties p = new Properties();
    292                         InputStream is = null;
    293                         try {
    294                             p.load(is = new FileInputStream(jarProperties));
    295 
    296                             String value = p.getProperty(ATTR_SRC);
    297                             if (value != null) {
    298                                 File srcPath = getFile(jarFile, value);
    299 
    300                                 if (srcPath.exists()) {
    301                                     sourceAttachmentPath = new Path(srcPath.getAbsolutePath());
    302                                 }
    303                             }
    304 
    305                             value = p.getProperty(ATTR_DOC);
    306                             if (value != null) {
    307                                 File docPath = getFile(jarFile, value);
    308                                 if (docPath.exists()) {
    309                                     try {
    310                                         javaDocAttribute = JavaCore.newClasspathAttribute(
    311                                                 IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME,
    312                                                 docPath.toURI().toURL().toString());
    313                                     } catch (MalformedURLException e) {
    314                                         AdtPlugin.log(e, "Failed to process 'doc' attribute for %s",
    315                                                 jarProperties.getAbsolutePath());
    316                                     }
    317                                 }
    318                             }
    319 
    320                         } catch (FileNotFoundException e) {
    321                             // shouldn't happen since we check upfront
    322                         } catch (IOException e) {
    323                             AdtPlugin.log(e, "Failed to read %s", jarProperties.getAbsolutePath());
    324                         } finally {
    325                             if (is != null) {
    326                                 try {
    327                                     is.close();
    328                                 } catch (IOException e) {
    329                                     // ignore
    330                                 }
    331                             }
    332                         }
    333                     }
    334 
    335                     if (javaDocAttribute != null) {
    336                         entries.add(JavaCore.newLibraryEntry(new Path(jarPath),
    337                                 sourceAttachmentPath, null /*sourceAttachmentRootPath*/,
    338                                 new IAccessRule[0],
    339                                 new IClasspathAttribute[] { javaDocAttribute },
    340                                 true /*isExported*/));
    341                     } else {
    342                         entries.add(JavaCore.newLibraryEntry(new Path(jarPath),
    343                                 sourceAttachmentPath, null /*sourceAttachmentRootPath*/,
    344                                 true /*isExported*/));
    345                     }
    346                 }
    347             }
    348         } catch (DifferentLibException e) {
    349             errorMessage = e.getMessage();
    350             AdtPlugin.printErrorToConsole(iProject, (Object[]) e.getDetails());
    351         } catch (Sha1Exception e) {
    352             errorMessage = e.getMessage();
    353         }
    354 
    355         processError(iProject, errorMessage, AdtConstants.MARKER_DEPENDENCY,
    356                 true /*outputToConsole*/);
    357 
    358         return new AndroidClasspathContainer(
    359                 entries.toArray(new IClasspathEntry[entries.size()]),
    360                 new Path(AdtConstants.CONTAINER_LIBRARIES),
    361                 "Android Dependencies",
    362                 IClasspathContainer.K_APPLICATION);
    363     }
    364 
    365     private static File getFile(File root, String value) {
    366         File file = new File(value);
    367         if (file.isAbsolute() == false) {
    368             file = new File(root.getParentFile(), value);
    369         }
    370 
    371         return file;
    372     }
    373 
    374     /**
    375      * Finds all the jar files inside a project's libs folder.
    376      * @param project
    377      * @param jarFiles
    378      */
    379     private static void getJarListFromLibsFolder(IProject project, Set<File> jarFiles) {
    380         IFolder libsFolder = project.getFolder(SdkConstants.FD_NATIVE_LIBS);
    381         if (libsFolder.exists()) {
    382             try {
    383                 IResource[] members = libsFolder.members();
    384                 for (IResource member : members) {
    385                     if (member.getType() == IResource.FILE &&
    386                             SdkConstants.EXT_JAR.equalsIgnoreCase(member.getFileExtension())) {
    387                         IPath location = member.getLocation();
    388                         if (location != null) {
    389                             jarFiles.add(location.toFile());
    390                         }
    391                     }
    392                 }
    393             } catch (CoreException e) {
    394                 // can't get the list? ignore this folder.
    395             }
    396         }
    397     }
    398 
    399     /**
    400      * Process reference projects from the main projects to add indirect dependencies coming
    401      * from Java project.
    402      * @param project the main project
    403      * @param projects the project list to add to
    404      * @param jarFiles the jar list to add to.
    405      */
    406     private static void processReferencedProjects(IProject project,
    407             Set<IProject> projects, Set<File> jarFiles) {
    408         try {
    409             IProject[] refs = project.getReferencedProjects();
    410             for (IProject p : refs) {
    411                 // ignore if it's an Android project, or if it's not a Java
    412                 // Project
    413                 if (p.hasNature(JavaCore.NATURE_ID)
    414                         && p.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
    415 
    416                     // process this project's dependencies
    417                     getDependencyListFromClasspath(p, projects, jarFiles, true /*includeJarFiles*/);
    418                 }
    419             }
    420         } catch (CoreException e) {
    421             // can't get the referenced projects? ignore
    422         }
    423     }
    424 
    425     /**
    426      * Finds all the dependencies of a given project and add them to a project list and
    427      * a jar list.
    428      * Only classpath entries that are exported are added, and only Java project (not Android
    429      * project) are added.
    430      *
    431      * @param project the project to query
    432      * @param projects the referenced project list to add to
    433      * @param jarFiles the jar list to add to
    434      * @param includeJarFiles whether to include jar files or just projects. This is useful when
    435      *           calling on an Android project (value should be <code>false</code>)
    436      */
    437     private static void getDependencyListFromClasspath(IProject project, Set<IProject> projects,
    438             Set<File> jarFiles, boolean includeJarFiles) {
    439         IJavaProject javaProject = JavaCore.create(project);
    440         IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();
    441 
    442         // we could use IJavaProject.getResolvedClasspath directly, but we actually
    443         // want to see the containers themselves.
    444         IClasspathEntry[] classpaths = javaProject.readRawClasspath();
    445         if (classpaths != null) {
    446             for (IClasspathEntry e : classpaths) {
    447                 // ignore entries that are not exported
    448                 if (e.isExported()) {
    449                     processCPE(e, javaProject, wsRoot, projects, jarFiles, includeJarFiles);
    450                 }
    451             }
    452         }
    453     }
    454 
    455     /**
    456      * Processes a {@link IClasspathEntry} and add it to one of the list if applicable.
    457      * @param entry the entry to process
    458      * @param javaProject the {@link IJavaProject} from which this entry came.
    459      * @param wsRoot the {@link IWorkspaceRoot}
    460      * @param projects the project list to add to
    461      * @param jarFiles the jar list to add to
    462      * @param includeJarFiles whether to include jar files or just projects. This is useful when
    463      *           calling on an Android project (value should be <code>false</code>)
    464      */
    465     private static void processCPE(IClasspathEntry entry, IJavaProject javaProject,
    466             IWorkspaceRoot wsRoot,
    467             Set<IProject> projects, Set<File> jarFiles, boolean includeJarFiles) {
    468 
    469         // if this is a classpath variable reference, we resolve it.
    470         if (entry.getEntryKind() == IClasspathEntry.CPE_VARIABLE) {
    471             entry = JavaCore.getResolvedClasspathEntry(entry);
    472         }
    473 
    474         if (entry.getEntryKind() == IClasspathEntry.CPE_PROJECT) {
    475             IProject refProject = wsRoot.getProject(entry.getPath().lastSegment());
    476             try {
    477                 // ignore if it's an Android project, or if it's not a Java Project
    478                 if (refProject.hasNature(JavaCore.NATURE_ID) &&
    479                         refProject.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
    480                     // add this project to the list
    481                     projects.add(refProject);
    482 
    483                     // also get the dependency from this project.
    484                     getDependencyListFromClasspath(refProject, projects, jarFiles,
    485                             true /*includeJarFiles*/);
    486                 }
    487             } catch (CoreException exception) {
    488                 // can't query the project nature? ignore
    489             }
    490         } else if (entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) {
    491             if (includeJarFiles) {
    492                 handleClasspathLibrary(entry, wsRoot, jarFiles);
    493             }
    494         } else if (entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) {
    495             // get the container and its content
    496             try {
    497                 IClasspathContainer container = JavaCore.getClasspathContainer(
    498                         entry.getPath(), javaProject);
    499                 // ignore the system and default_system types as they represent
    500                 // libraries that are part of the runtime.
    501                 if (container != null &&
    502                         container.getKind() == IClasspathContainer.K_APPLICATION) {
    503                     IClasspathEntry[] entries = container.getClasspathEntries();
    504                     for (IClasspathEntry cpe : entries) {
    505                         processCPE(cpe, javaProject, wsRoot, projects, jarFiles, includeJarFiles);
    506                     }
    507                 }
    508             } catch (JavaModelException jme) {
    509                 // can't resolve the container? ignore it.
    510                 AdtPlugin.log(jme, "Failed to resolve ClasspathContainer: %s", entry.getPath());
    511             }
    512         }
    513     }
    514 
    515     private static final class CPEFile extends File {
    516         private static final long serialVersionUID = 1L;
    517 
    518         private final IClasspathEntry mClasspathEntry;
    519 
    520         public CPEFile(String pathname, IClasspathEntry classpathEntry) {
    521             super(pathname);
    522             mClasspathEntry = classpathEntry;
    523         }
    524 
    525         public CPEFile(File file, IClasspathEntry classpathEntry) {
    526             super(file.getAbsolutePath());
    527             mClasspathEntry = classpathEntry;
    528         }
    529 
    530         public IClasspathEntry getClasspathEntry() {
    531             return mClasspathEntry;
    532         }
    533     }
    534 
    535     private static void handleClasspathLibrary(IClasspathEntry e, IWorkspaceRoot wsRoot,
    536             Set<File> jarFiles) {
    537         // get the IPath
    538         IPath path = e.getPath();
    539 
    540         IResource resource = wsRoot.findMember(path);
    541 
    542         if (SdkConstants.EXT_JAR.equalsIgnoreCase(path.getFileExtension())) {
    543             // case of a jar file (which could be relative to the workspace or a full path)
    544             if (resource != null && resource.exists() &&
    545                     resource.getType() == IResource.FILE) {
    546                 jarFiles.add(new CPEFile(resource.getLocation().toFile(), e));
    547             } else {
    548                 // if the jar path doesn't match a workspace resource,
    549                 // then we get an OSString and check if this links to a valid file.
    550                 String osFullPath = path.toOSString();
    551 
    552                 File f = new CPEFile(osFullPath, e);
    553                 if (f.isFile()) {
    554                     jarFiles.add(f);
    555                 }
    556             }
    557         }
    558     }
    559 }
    560