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