Home | History | Annotate | Download | only in manager
      1 /*
      2  * Copyright (C) 2008 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.resources.manager;
     18 
     19 import com.android.ide.eclipse.adt.AdtConstants;
     20 import com.android.ide.eclipse.adt.AdtPlugin;
     21 import com.android.ide.eclipse.adt.internal.build.BuildHelper;
     22 import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
     23 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     24 
     25 import org.eclipse.core.resources.IProject;
     26 import org.eclipse.core.resources.IResource;
     27 import org.eclipse.core.resources.IWorkspaceRoot;
     28 import org.eclipse.core.resources.ResourcesPlugin;
     29 import org.eclipse.core.runtime.CoreException;
     30 import org.eclipse.core.runtime.IPath;
     31 import org.eclipse.jdt.core.IClasspathContainer;
     32 import org.eclipse.jdt.core.IClasspathEntry;
     33 import org.eclipse.jdt.core.IJavaProject;
     34 import org.eclipse.jdt.core.JavaCore;
     35 import org.eclipse.jdt.core.JavaModelException;
     36 
     37 import java.io.File;
     38 import java.io.FileInputStream;
     39 import java.io.FileNotFoundException;
     40 import java.io.IOException;
     41 import java.net.MalformedURLException;
     42 import java.net.URL;
     43 import java.net.URLClassLoader;
     44 import java.util.ArrayList;
     45 import java.util.List;
     46 
     47 /**
     48  * ClassLoader able to load class from output of an Eclipse project.
     49  */
     50 public final class ProjectClassLoader extends ClassLoader {
     51 
     52     private final IJavaProject mJavaProject;
     53     private URLClassLoader mJarClassLoader;
     54     private boolean mInsideJarClassLoader = false;
     55 
     56     public ProjectClassLoader(ClassLoader parentClassLoader, IProject project) {
     57         super(parentClassLoader);
     58         mJavaProject = JavaCore.create(project);
     59     }
     60 
     61     @Override
     62     protected Class<?> findClass(String name) throws ClassNotFoundException {
     63         // if we are here through a child classloader, throw an exception.
     64         if (mInsideJarClassLoader) {
     65             throw new ClassNotFoundException(name);
     66         }
     67 
     68         // attempt to load the class from the main project
     69         Class<?> clazz = loadFromProject(mJavaProject, name);
     70 
     71         if (clazz != null) {
     72             return clazz;
     73         }
     74 
     75         // attempt to load the class from the jar dependencies
     76         clazz = loadClassFromJar(name);
     77         if (clazz != null) {
     78             return clazz;
     79         }
     80 
     81         // attempt to load the class from the libraries
     82         try {
     83             // get the project info
     84             ProjectState projectState = Sdk.getProjectState(mJavaProject.getProject());
     85 
     86             // this can happen if the project has no project.properties.
     87             if (projectState != null) {
     88 
     89                 List<IProject> libProjects = projectState.getFullLibraryProjects();
     90                 List<IJavaProject> referencedJavaProjects = BuildHelper.getJavaProjects(
     91                         libProjects);
     92 
     93                 for (IJavaProject javaProject : referencedJavaProjects) {
     94                     clazz = loadFromProject(javaProject, name);
     95 
     96                     if (clazz != null) {
     97                         return clazz;
     98                     }
     99                 }
    100             }
    101         } catch (CoreException e) {
    102             // log exception?
    103         }
    104 
    105         throw new ClassNotFoundException(name);
    106     }
    107 
    108     /**
    109      * Attempts to load a class from a project output folder.
    110      * @param project the project to load the class from
    111      * @param name the name of the class
    112      * @return a class object if found, null otherwise.
    113      */
    114     private Class<?> loadFromProject(IJavaProject project, String name) {
    115         try {
    116             // get the project output folder.
    117             IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
    118             IPath outputLocation = project.getOutputLocation();
    119             IResource outRes = root.findMember(outputLocation);
    120             if (outRes == null) {
    121                 return null;
    122             }
    123 
    124             File outFolder = new File(outRes.getLocation().toOSString());
    125 
    126             // get the class name segments
    127             String[] segments = name.split("\\."); //$NON-NLS-1$
    128 
    129             // try to load the class from the bin folder of the project.
    130             File classFile = getFile(outFolder, segments, 0);
    131             if (classFile == null) {
    132                 return null;
    133             }
    134 
    135             // load the content of the file and create the class.
    136             FileInputStream fis = new FileInputStream(classFile);
    137             byte[] data = new byte[(int)classFile.length()];
    138             int read = 0;
    139             try {
    140                 read = fis.read(data);
    141             } catch (IOException e) {
    142                 data = null;
    143             }
    144             fis.close();
    145 
    146             if (data != null) {
    147                 Class<?> clazz = defineClass(null, data, 0, read);
    148                 if (clazz != null) {
    149                     return clazz;
    150                 }
    151             }
    152         } catch (Exception e) {
    153             // log the exception?
    154         }
    155 
    156         return null;
    157     }
    158 
    159     /**
    160      * Returns the File matching the a certain path from a root {@link File}.
    161      * <p/>The methods checks that the file ends in .class even though the last segment
    162      * does not.
    163      * @param parent the root of the file.
    164      * @param segments the segments containing the path of the file
    165      * @param index the offset at which to start looking into segments.
    166      * @throws FileNotFoundException
    167      */
    168     private File getFile(File parent, String[] segments, int index)
    169             throws FileNotFoundException {
    170         // reached the end with no match?
    171         if (index == segments.length) {
    172             throw new FileNotFoundException();
    173         }
    174 
    175         String toMatch = segments[index];
    176         File[] files = parent.listFiles();
    177 
    178         // we're at the last segments. we look for a matching <file>.class
    179         if (index == segments.length - 1) {
    180             toMatch = toMatch + ".class";
    181 
    182             if (files != null) {
    183                 for (File file : files) {
    184                     if (file.isFile() && file.getName().equals(toMatch)) {
    185                         return file;
    186                     }
    187                 }
    188             }
    189 
    190             // no match? abort.
    191             throw new FileNotFoundException();
    192         }
    193 
    194         String innerClassName = null;
    195 
    196         if (files != null) {
    197             for (File file : files) {
    198                 if (file.isDirectory()) {
    199                     if (toMatch.equals(file.getName())) {
    200                         return getFile(file, segments, index+1);
    201                     }
    202                 } else if (file.getName().startsWith(toMatch)) {
    203                     if (innerClassName == null) {
    204                         StringBuilder sb = new StringBuilder(segments[index]);
    205                         for (int i = index + 1 ; i < segments.length ; i++) {
    206                             sb.append('$');
    207                             sb.append(segments[i]);
    208                         }
    209                         sb.append(".class");
    210 
    211                         innerClassName = sb.toString();
    212                     }
    213 
    214                     if (file.getName().equals(innerClassName)) {
    215                         return file;
    216                     }
    217                 }
    218             }
    219         }
    220 
    221         return null;
    222     }
    223 
    224     /**
    225      * Loads a class from the 3rd party jar present in the project
    226      *
    227      * @return the class loader or null if not found.
    228      */
    229     private Class<?> loadClassFromJar(String name) {
    230         if (mJarClassLoader == null) {
    231             // get the OS path to all the external jars
    232             URL[] jars = getExternalJars();
    233 
    234             mJarClassLoader = new URLClassLoader(jars, this /* parent */);
    235         }
    236 
    237         try {
    238             // because a class loader always look in its parent loader first, we need to know
    239             // that we are querying the jar classloader. This will let us know to not query
    240             // it again for classes we don't find, or this would create an infinite loop.
    241             mInsideJarClassLoader = true;
    242             return mJarClassLoader.loadClass(name);
    243         } catch (ClassNotFoundException e) {
    244             // not found? return null.
    245             return null;
    246         } finally {
    247             mInsideJarClassLoader = false;
    248         }
    249     }
    250 
    251     /**
    252      * Returns an array of external jar files used by the project.
    253      * @return an array of OS-specific absolute file paths
    254      */
    255     private final URL[] getExternalJars() {
    256         // get a java project from it
    257         IJavaProject javaProject = JavaCore.create(mJavaProject.getProject());
    258 
    259         ArrayList<URL> oslibraryList = new ArrayList<URL>();
    260         IClasspathEntry[] classpaths = javaProject.readRawClasspath();
    261         if (classpaths != null) {
    262             for (IClasspathEntry e : classpaths) {
    263                 if (e.getEntryKind() == IClasspathEntry.CPE_LIBRARY ||
    264                         e.getEntryKind() == IClasspathEntry.CPE_VARIABLE) {
    265                     // if this is a classpath variable reference, we resolve it.
    266                     if (e.getEntryKind() == IClasspathEntry.CPE_VARIABLE) {
    267                         e = JavaCore.getResolvedClasspathEntry(e);
    268                     }
    269 
    270                     handleClassPathEntry(e, oslibraryList);
    271                 } else if (e.getEntryKind() == IClasspathEntry.CPE_CONTAINER) {
    272                     // get the container.
    273                     try {
    274                         IClasspathContainer container = JavaCore.getClasspathContainer(
    275                                 e.getPath(), javaProject);
    276                         // ignore the system and default_system types as they represent
    277                         // libraries that are part of the runtime.
    278                         if (container != null &&
    279                                 container.getKind() == IClasspathContainer.K_APPLICATION) {
    280                             IClasspathEntry[] entries = container.getClasspathEntries();
    281                             for (IClasspathEntry entry : entries) {
    282                                 // TODO: Xav -- is this necessary?
    283                                 if (entry.getEntryKind() == IClasspathEntry.CPE_VARIABLE) {
    284                                     entry = JavaCore.getResolvedClasspathEntry(entry);
    285                                 }
    286 
    287                                 handleClassPathEntry(entry, oslibraryList);
    288                             }
    289                         }
    290                     } catch (JavaModelException jme) {
    291                         // can't resolve the container? ignore it.
    292                         AdtPlugin.log(jme, "Failed to resolve ClasspathContainer: %s",
    293                                 e.getPath());
    294                     }
    295                 }
    296             }
    297         }
    298 
    299         return oslibraryList.toArray(new URL[oslibraryList.size()]);
    300     }
    301 
    302     private void handleClassPathEntry(IClasspathEntry e, ArrayList<URL> oslibraryList) {
    303         // get the IPath
    304         IPath path = e.getPath();
    305 
    306         // check the name ends with .jar
    307         if (AdtConstants.EXT_JAR.equalsIgnoreCase(path.getFileExtension())) {
    308             boolean local = false;
    309             IResource resource = ResourcesPlugin.getWorkspace().getRoot().findMember(path);
    310             if (resource != null && resource.exists() &&
    311                     resource.getType() == IResource.FILE) {
    312                 local = true;
    313                 try {
    314                     oslibraryList.add(new File(resource.getLocation().toOSString())
    315                             .toURI().toURL());
    316                 } catch (MalformedURLException mue) {
    317                     // pass
    318                 }
    319             }
    320 
    321             if (local == false) {
    322                 // if the jar path doesn't match a workspace resource,
    323                 // then we get an OSString and check if this links to a valid file.
    324                 String osFullPath = path.toOSString();
    325 
    326                 File f = new File(osFullPath);
    327                 if (f.exists()) {
    328                     try {
    329                         oslibraryList.add(f.toURI().toURL());
    330                     } catch (MalformedURLException mue) {
    331                         // pass
    332                     }
    333                 }
    334             }
    335         }
    336     }
    337 }
    338