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