blob: e07f09927b9452827eae454bddad7b1e331f2b9b [file] [log] [blame]
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.internal.resources.manager;
import com.android.SdkConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.build.BuildHelper;
import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jdt.core.IClasspathContainer;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
/**
* ClassLoader able to load class from output of an Eclipse project.
*/
public final class ProjectClassLoader extends ClassLoader {
private final IJavaProject mJavaProject;
private URLClassLoader mJarClassLoader;
private boolean mInsideJarClassLoader = false;
public ProjectClassLoader(ClassLoader parentClassLoader, IProject project) {
super(parentClassLoader);
mJavaProject = JavaCore.create(project);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// if we are here through a child classloader, throw an exception.
if (mInsideJarClassLoader) {
throw new ClassNotFoundException(name);
}
// attempt to load the class from the main project
Class<?> clazz = loadFromProject(mJavaProject, name);
if (clazz != null) {
return clazz;
}
// attempt to load the class from the jar dependencies
clazz = loadClassFromJar(name);
if (clazz != null) {
return clazz;
}
// attempt to load the class from the libraries
try {
// get the project info
ProjectState projectState = Sdk.getProjectState(mJavaProject.getProject());
// this can happen if the project has no project.properties.
if (projectState != null) {
List<IProject> libProjects = projectState.getFullLibraryProjects();
List<IJavaProject> referencedJavaProjects = BuildHelper.getJavaProjects(
libProjects);
for (IJavaProject javaProject : referencedJavaProjects) {
clazz = loadFromProject(javaProject, name);
if (clazz != null) {
return clazz;
}
}
}
} catch (CoreException e) {
// log exception?
}
throw new ClassNotFoundException(name);
}
/**
* Attempts to load a class from a project output folder.
* @param project the project to load the class from
* @param name the name of the class
* @return a class object if found, null otherwise.
*/
private Class<?> loadFromProject(IJavaProject project, String name) {
try {
// get the project output folder.
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
IPath outputLocation = project.getOutputLocation();
IResource outRes = root.findMember(outputLocation);
if (outRes == null) {
return null;
}
File outFolder = new File(outRes.getLocation().toOSString());
// get the class name segments
String[] segments = name.split("\\."); //$NON-NLS-1$
// try to load the class from the bin folder of the project.
File classFile = getFile(outFolder, segments, 0);
if (classFile == null) {
return null;
}
// load the content of the file and create the class.
FileInputStream fis = new FileInputStream(classFile);
byte[] data = new byte[(int)classFile.length()];
int read = 0;
try {
read = fis.read(data);
} catch (IOException e) {
data = null;
}
fis.close();
if (data != null) {
try {
Class<?> clazz = defineClass(null, data, 0, read);
if (clazz != null) {
return clazz;
}
} catch (UnsupportedClassVersionError e) {
// Attempt to reload on lower version
int maxVersion = 50; // JDK 1.6
try {
byte[] rewritten = rewriteClass(data, maxVersion, 0);
return defineClass(null, rewritten, 0, rewritten.length);
} catch (UnsupportedClassVersionError e2) {
throw e; // throw *original* exception, not attempt to rewrite
}
}
}
} catch (Exception e) {
// log the exception?
}
return null;
}
/**
* Rewrites the given class to the given target class file version.
*/
public static byte[] rewriteClass(byte[] classData, final int maxVersion, final int minVersion) {
assert maxVersion >= minVersion;
ClassWriter classWriter = new ClassWriter(0);
ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM5, classWriter) {
@Override
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
if (version > maxVersion) {
version = maxVersion;
}
if (version < minVersion) {
version = minVersion;
}
super.visit(version, access, name, signature, superName, interfaces);
}
};
ClassReader reader = new ClassReader(classData);
reader.accept(classVisitor, 0);
return classWriter.toByteArray();
}
/**
* Returns the File matching the a certain path from a root {@link File}.
* <p/>The methods checks that the file ends in .class even though the last segment
* does not.
* @param parent the root of the file.
* @param segments the segments containing the path of the file
* @param index the offset at which to start looking into segments.
* @throws FileNotFoundException
*/
private File getFile(File parent, String[] segments, int index)
throws FileNotFoundException {
// reached the end with no match?
if (index == segments.length) {
throw new FileNotFoundException();
}
String toMatch = segments[index];
File[] files = parent.listFiles();
// we're at the last segments. we look for a matching <file>.class
if (index == segments.length - 1) {
toMatch = toMatch + ".class";
if (files != null) {
for (File file : files) {
if (file.isFile() && file.getName().equals(toMatch)) {
return file;
}
}
}
// no match? abort.
throw new FileNotFoundException();
}
String innerClassName = null;
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
if (toMatch.equals(file.getName())) {
return getFile(file, segments, index+1);
}
} else if (file.getName().startsWith(toMatch)) {
if (innerClassName == null) {
StringBuilder sb = new StringBuilder(segments[index]);
for (int i = index + 1 ; i < segments.length ; i++) {
sb.append('$');
sb.append(segments[i]);
}
sb.append(".class");
innerClassName = sb.toString();
}
if (file.getName().equals(innerClassName)) {
return file;
}
}
}
}
return null;
}
/**
* Loads a class from the 3rd party jar present in the project
*
* @return the class loader or null if not found.
*/
private Class<?> loadClassFromJar(String name) {
if (mJarClassLoader == null) {
// get the OS path to all the external jars
URL[] jars = getExternalJars();
mJarClassLoader = new URLClassLoader(jars, this /* parent */);
}
try {
// because a class loader always look in its parent loader first, we need to know
// that we are querying the jar classloader. This will let us know to not query
// it again for classes we don't find, or this would create an infinite loop.
mInsideJarClassLoader = true;
return mJarClassLoader.loadClass(name);
} catch (ClassNotFoundException e) {
// not found? return null.
return null;
} finally {
mInsideJarClassLoader = false;
}
}
/**
* Returns an array of external jar files used by the project.
* @return an array of OS-specific absolute file paths
*/
private final URL[] getExternalJars() {
// get a java project from it
IJavaProject javaProject = JavaCore.create(mJavaProject.getProject());
ArrayList<URL> oslibraryList = new ArrayList<URL>();
IClasspathEntry[] classpaths = javaProject.readRawClasspath();
if (classpaths != null) {
for (IClasspathEntry e : classpaths) {
if (e.getEntryKind() == IClasspathEntry.CPE_LIBRARY ||
e.getEntryKind() == IClasspathEntry.CPE_VARIABLE) {
// if this is a classpath variable reference, we resolve it.
if (e.getEntryKind() == IClasspathEntry.CPE_VARIABLE) {
e = JavaCore.getResolvedClasspathEntry(e);
}
handleClassPathEntry(e, oslibraryList);
} else if (e.getEntryKind() == IClasspathEntry.CPE_CONTAINER) {
// get the container.
try {
IClasspathContainer container = JavaCore.getClasspathContainer(
e.getPath(), javaProject);
// ignore the system and default_system types as they represent
// libraries that are part of the runtime.
if (container != null &&
container.getKind() == IClasspathContainer.K_APPLICATION) {
IClasspathEntry[] entries = container.getClasspathEntries();
for (IClasspathEntry entry : entries) {
// TODO: Xav -- is this necessary?
if (entry.getEntryKind() == IClasspathEntry.CPE_VARIABLE) {
entry = JavaCore.getResolvedClasspathEntry(entry);
}
handleClassPathEntry(entry, oslibraryList);
}
}
} catch (JavaModelException jme) {
// can't resolve the container? ignore it.
AdtPlugin.log(jme, "Failed to resolve ClasspathContainer: %s",
e.getPath());
}
}
}
}
return oslibraryList.toArray(new URL[oslibraryList.size()]);
}
private void handleClassPathEntry(IClasspathEntry e, ArrayList<URL> oslibraryList) {
// get the IPath
IPath path = e.getPath();
// check the name ends with .jar
if (SdkConstants.EXT_JAR.equalsIgnoreCase(path.getFileExtension())) {
boolean local = false;
IResource resource = ResourcesPlugin.getWorkspace().getRoot().findMember(path);
if (resource != null && resource.exists() &&
resource.getType() == IResource.FILE) {
local = true;
try {
oslibraryList.add(new File(resource.getLocation().toOSString())
.toURI().toURL());
} catch (MalformedURLException mue) {
// pass
}
}
if (local == false) {
// if the jar path doesn't match a workspace resource,
// then we get an OSString and check if this links to a valid file.
String osFullPath = path.toOSString();
File f = new File(osFullPath);
if (f.exists()) {
try {
oslibraryList.add(f.toURI().toURL());
} catch (MalformedURLException mue) {
// pass
}
}
}
}
}
}