/*
* Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.max.vm.hosted;
import java.lang.reflect.*;
import java.util.*;
import com.sun.max.*;
import com.sun.max.program.*;
import com.sun.max.vm.actor.holder.*;
import com.sun.max.vm.classfile.*;
import com.sun.max.vm.jdk.*;
import com.sun.max.vm.reflection.*;
import com.sun.max.vm.type.*;
/**
* Parent hosted class loader for {@link HostedBootClassLoader} and {@link HostedVMClassLoader}.
*/
public abstract class HostedClassLoader extends ClassLoader {
/**
* The default classpath for loading classes.
*/
protected Classpath classpath;
/**
* A cache of loaded classes for fast lookup. This is equivalent to the map {@link ClassRegistry}
* but is in terms of {@link Class}, which is what {@link ClassLoader#loadClass} uses.
*/
protected Map<String, Class> definedClasses = new HashMap<String, Class>();
protected HostedClassLoader() {
super(null);
}
protected HostedClassLoader(ClassLoader parent) {
super(parent);
}
/**
* Sets the classpath to be used for any subsequent loading of classes through this loader. This should
* ideally only be called once per execution before any class loading is performed through this loader.
*
* @param classpath the classpath to use for this loader.
*/
public void setClasspath(Classpath classpath) {
ProgramWarning.check(this.classpath == null, "overriding hosted boot class loader's classpath: old value=\"" + this.classpath + "\", new value=\"" + classpath + "\"");
this.classpath = classpath;
}
/**
* Set the default classpath for the concrete subclass.
*/
protected abstract Classpath getDefaultClasspath();
/**
* Gets the classpath of the hosted boot classloader.
*
* @return an object representing the classpath of this loader
*/
public Classpath classpath() {
if (classpath == null) {
setClasspath(getDefaultClasspath());
}
return classpath;
}
/**
* Gets the contents of the class file corresponding to a given class, searching a given classpath.
*
* @param classpath the classpath to search
* @param name the name of the class to open
* @return the contents of the class file representation of the class named {@code name}
* @throws ClassNotFoundException if the class file cannot be found
*/
public static ClasspathFile readClassFile(Classpath classpath, String name) throws ClassNotFoundException {
ClasspathFile classpathFile = classpath.readClassFile(name);
if (classpathFile == null) {
classpathFile = ClassfileReader.findGeneratedClassfile(name);
}
if (classpathFile != null) {
return classpathFile;
}
throw new ClassNotFoundException(name);
}
/**
* Make a class actor for the specified type descriptor and fail with a program error
* if it cannot be done.
*
* @param typeDescriptor a well-formed descriptor of a class name
* @return the class actor for the specified type descriptor
*/
public ClassActor mustMakeClassActor(TypeDescriptor typeDescriptor) {
try {
// be careful about primitive types which can happen in some hosted code paths
// and can't be loaded with loadClass
if (!JavaTypeDescriptor.isPrimitive(typeDescriptor)) {
// this gets it into the correct registry
String javaName = typeDescriptor.toJavaString();
loadClass(javaName);
}
return ClassRegistry.getInBootOrVM(typeDescriptor);
} catch (ClassNotFoundException throwable) {
throw ProgramError.unexpected("could not make class Actor: " + typeDescriptor, throwable);
}
}
/**
* Define the class actor for a class successfully loaded by {@link ClassLoader#loadClass}.
* @param javaClass the {@code Class} that was loaded.
* @return the associated {@link ClassActor}
* @throws ClassNotFoundException
*/
private ClassActor defineLoadedClassActor(Class javaClass) throws ClassNotFoundException {
final TypeDescriptor typeDescriptor = JavaTypeDescriptor.forJavaClass(javaClass);
final ClassActor classActor = ClassRegistry.get(this, typeDescriptor, false);
// This check catches stub and array classes that are already defined in their unique way.
// It is easier catch them here this way than in {@link #loadClass}.
if (classActor != null) {
return classActor;
}
final String name = typeDescriptor.toJavaString();
final ClasspathFile classpathFile = readClassFile(classpath(), name);
definedClasses.put(name, javaClass);
return ClassfileReader.defineClassActor(name, this, classpathFile.contents, null, classpathFile.classpathEntry, false);
}
/**
* Array/Invocation stub classes require special treatment and are handled here for all subclasses.
* Since the classloader for the array/stub depends on the component/target type, our subclass will actually
* handle the loading of that.
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
TypeDescriptor arrayElementTypeDescriptor = checkArrayClass(name);
if (arrayElementTypeDescriptor != null) {
return findArrayClass(name, arrayElementTypeDescriptor);
} else if (isStubClass(name)) {
return defineStubClass(name);
} else {
return super.findClass(name);
}
}
/**
* Checks for an array class, returning the associated {@link TypeDescriptor} if so, {@code null} otherwise.
* @param name
*/
private TypeDescriptor checkArrayClass(String name) {
if (name.endsWith("[]")) {
return JavaTypeDescriptor.getDescriptorForJavaString(name).componentTypeDescriptor();
} else if (name.charAt(0) == '[') {
// make sure the name is slashified first
final String componentTypeName = name.substring(1).replace('.', '/');
return JavaTypeDescriptor.parseTypeDescriptor(componentTypeName);
} else {
return null;
}
}
protected boolean isStubClass(String name) {
return name.startsWith(InvocationStubGenerator.STUB_PACKAGE_PREFIX);
}
/**
* Define a stub class in this loader, if it was placed into our registry by {@link InvocationStubGenerator}.
* @param name
*/
protected Class<?> defineStubClass(String name) throws ClassNotFoundException {
TypeDescriptor typeDescriptor = JavaTypeDescriptor.getDescriptorForJavaString(name);
if (ClassRegistry.get(this, typeDescriptor, false) != null) {
ClasspathFile classpathFile = ClassfileReader.findGeneratedClassfile(name);
Class<?> stubClass = defineClass(name, classpathFile.contents, 0, classpathFile.contents.length);
definedClasses.put(name, stubClass);
return stubClass;
} else {
throw new ClassNotFoundException();
}
}
/**
* Attempts to find an array class for the specified element type descriptor, loading the
* element type class if necessary.
*
* @param elementTypeDescriptor the well-formed name of the element type
* @return the class for array type specified
* @throws ClassNotFoundException if the element type could not be found
*/
private Class<?> findArrayClass(final String name, final TypeDescriptor elementTypeDescriptor) throws ClassNotFoundException {
ClassActor elementClassActor = ClassRegistry.get(this, elementTypeDescriptor, false);
if (elementClassActor == null) {
final Class elementType = loadClass(elementTypeDescriptor.toJavaString());
elementClassActor = ClassActor.fromJava(elementType);
}
// Special case: Owing to HostBootClassLoader being able to access VM classes
// it is possible that we arrive here with elementClassActor being a VM class.
// We have to abort, otherwise the array will incorrectly end up in the boot class registry.
// HostVMClassLoader will define the array after HostBootClassLoader fails.
if (this == HostedBootClassLoader.HOSTED_BOOT_CLASS_LOADER && elementClassActor.classLoader == HostedVMClassLoader.HOSTED_VM_CLASS_LOADER) {
throw new ClassNotFoundException();
}
final ArrayClassActor arrayClassActor = ArrayClassActor.forComponentClassActor(elementClassActor);
Class<?> arrayClass = arrayClassActor.toJava();
definedClasses.put(name, arrayClass);
return arrayClass;
}
/**
* Loads the class with the specified name. Also creates the {@link ClassActor} and records proxy classes, unless
* prevent by subclass checks.
*/
@Override
protected synchronized Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException {
Class javaType = definedClasses.get(name);
if (javaType != null) {
return javaType;
}
try {
javaType = super.loadClass(name, resolve);
if (extraLoadClassChecks(javaType)) {
defineLoadedClassActor(javaType);
if (Proxy.isProxyClass(javaType)) {
JDK_java_lang_reflect_Proxy.bootProxyClasses.add(javaType);
}
}
return javaType;
} catch (Exception exception) {
throw Utils.cast(ClassNotFoundException.class, exception);
}
}
/**
* Hook for a subclass to add additional checks before/after loading.
* @param javaType {@code null} if the class is yet to be loaded, otherwise result of {@link #loadClass}
* @return {@code true} iff the class should be added to the associated {@link ClassRegistry}
* @throws ClassNotFoundException if the subclass wants to reject the class
*/
protected abstract boolean extraLoadClassChecks(Class<?> javaType) throws ClassNotFoundException;
/*
* Methods used exclusively by the Inspector.
* It is less strict in where it finds classes, for example stub classes
* can be found in the file system.
*/
/**
* Create a class actor with the specified name from the specified byte array.
*
* @param name the name of the class
* @param classfileBytes a byte array containing the encoded version of the class
*/
public ClassActor makeClassActor(final String name, byte[] classfileBytes) {
defineClass(name, classfileBytes, 0, classfileBytes.length);
return ClassfileReader.defineClassActor(name, this, classfileBytes, null, null, false);
}
public ClassActor makeClassActor(String name) throws ClassNotFoundException {
ClassActor classActor = ClassRegistry.getInBootOrVM(JavaTypeDescriptor.getDescriptorForJavaString(name));
if (classActor == null) {
// We handle arrays/stubs slightly differently in the Inspector as we are not concerned
// about VM/Boot registry issues as we are during boot image generation.
TypeDescriptor arrayElementTypeDescriptor = checkArrayClass(name);
if (arrayElementTypeDescriptor != null) {
return ArrayClassActor.forComponentClassActor(ClassActor.fromJava(loadClass(arrayElementTypeDescriptor.toJavaString())));
} else if (isStubClass(name)) {
ClasspathFile classpathFile = classpath.readClassFile(name);
if (classpathFile == null) {
classpathFile = ClassfileReader.findGeneratedClassfile(name);
}
if (classpathFile != null) {
return ClassfileReader.defineClassActor(name, this, classpathFile.contents, null, classpathFile.classpathEntry, false);
}
throw new ClassNotFoundException(name);
} else {
// everything else we find on the classpath
return ClassActor.fromJava(loadClass(name));
}
}
return classActor;
}
}