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 ProjectClassLoader(ClassLoader parentClassLoader, IProject project)60 public ProjectClassLoader(ClassLoader parentClassLoader, IProject project) { 61 super(parentClassLoader); 62 mJavaProject = JavaCore.create(project); 63 } 64 65 @Override findClass(String name)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 */ loadFromProject(IJavaProject project, String name)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 */ rewriteClass(byte[] classData, final int maxVersion, final int minVersion)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 */ getFile(File parent, String[] segments, int index)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 */ loadClassFromJar(String name)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 */ getExternalJars()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 handleClassPathEntry(IClasspathEntry e, ArrayList<URL> oslibraryList)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