/*
* Copyright (c) 2010, 2013, 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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 jdk.nashorn.internal.runtime.linker;
import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.Permissions;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
A tuple of a class loader and a single class representative of the classes that can be loaded through it. Its
equals/hashCode is defined in terms of the identity of the class loader. The rationale for this class is that it
couples a class loader with a random representative class coming from that loader - this representative class is then
used to determine if one loader can see the other loader's classes.
/**
* A tuple of a class loader and a single class representative of the classes that can be loaded through it. Its
* equals/hashCode is defined in terms of the identity of the class loader. The rationale for this class is that it
* couples a class loader with a random representative class coming from that loader - this representative class is then
* used to determine if one loader can see the other loader's classes.
*/
final class ClassAndLoader {
static AccessControlContext createPermAccCtxt(final String... permNames) {
final Permissions perms = new Permissions();
for (final String permName : permNames) {
perms.add(new RuntimePermission(permName));
}
return new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, perms) });
}
private static final AccessControlContext GET_LOADER_ACC_CTXT = createPermAccCtxt("getClassLoader");
private final Class<?> representativeClass;
// Don't access this directly; most of the time, use getRetrievedLoader(), or if you know what you're doing,
// getLoader().
private ClassLoader loader;
// We have mild affinity against eagerly retrieving the loader, as we need to do it in a privileged block. For
// the most basic case of looking up an already-generated adapter info for a single type, we avoid it.
private boolean loaderRetrieved;
ClassAndLoader(final Class<?> representativeClass, final boolean retrieveLoader) {
this.representativeClass = representativeClass;
if(retrieveLoader) {
retrieveLoader();
}
}
Class<?> getRepresentativeClass() {
return representativeClass;
}
boolean canSee(final ClassAndLoader other) {
try {
final Class<?> otherClass = other.getRepresentativeClass();
return Class.forName(otherClass.getName(), false, getLoader()) == otherClass;
} catch (final ClassNotFoundException e) {
return false;
}
}
ClassLoader getLoader() {
if(!loaderRetrieved) {
retrieveLoader();
}
return getRetrievedLoader();
}
ClassLoader getRetrievedLoader() {
assert loaderRetrieved;
return loader;
}
private void retrieveLoader() {
loader = representativeClass.getClassLoader();
loaderRetrieved = true;
}
@Override
public boolean equals(final Object obj) {
return obj instanceof ClassAndLoader && ((ClassAndLoader)obj).getRetrievedLoader() == getRetrievedLoader();
}
@Override
public int hashCode() {
return System.identityHashCode(getRetrievedLoader());
}
Given a list of types that define the superclass/interfaces for an adapter class, returns a single type from the
list that will be used to attach the adapter to its ClassValue. The first type in the array that is defined in a
class loader that can also see all other types is returned. If there is no such loader, an exception is thrown.
Params: - types – the input types
Returns: the first type from the array that is defined in a class loader that can also see all other types.
/**
* Given a list of types that define the superclass/interfaces for an adapter class, returns a single type from the
* list that will be used to attach the adapter to its ClassValue. The first type in the array that is defined in a
* class loader that can also see all other types is returned. If there is no such loader, an exception is thrown.
* @param types the input types
* @return the first type from the array that is defined in a class loader that can also see all other types.
*/
static ClassAndLoader getDefiningClassAndLoader(final Class<?>[] types) {
// Short circuit the cheap case
if(types.length == 1) {
return new ClassAndLoader(types[0], false);
}
return AccessController.doPrivileged(new PrivilegedAction<ClassAndLoader>() {
@Override
public ClassAndLoader run() {
return getDefiningClassAndLoaderPrivileged(types);
}
}, GET_LOADER_ACC_CTXT);
}
static ClassAndLoader getDefiningClassAndLoaderPrivileged(final Class<?>[] types) {
final Collection<ClassAndLoader> maximumVisibilityLoaders = getMaximumVisibilityLoaders(types);
final Iterator<ClassAndLoader> it = maximumVisibilityLoaders.iterator();
if(maximumVisibilityLoaders.size() == 1) {
// Fortunate case - single maximally specific class loader; return its representative class.
return it.next();
}
// Ambiguity; throw an error.
assert maximumVisibilityLoaders.size() > 1; // basically, can't be zero
final StringBuilder b = new StringBuilder();
b.append(it.next().getRepresentativeClass().getCanonicalName());
while(it.hasNext()) {
b.append(", ").append(it.next().getRepresentativeClass().getCanonicalName());
}
throw typeError("extend.ambiguous.defining.class", b.toString());
}
Given an array of types, return a subset of their class loaders that are maximal according to the
"can see other loaders' classes" relation, which is presumed to be a partial ordering.
Params: - types – types
Returns: a collection of maximum visibility class loaders. It is guaranteed to have at least one element.
/**
* Given an array of types, return a subset of their class loaders that are maximal according to the
* "can see other loaders' classes" relation, which is presumed to be a partial ordering.
* @param types types
* @return a collection of maximum visibility class loaders. It is guaranteed to have at least one element.
*/
private static Collection<ClassAndLoader> getMaximumVisibilityLoaders(final Class<?>[] types) {
final List<ClassAndLoader> maximumVisibilityLoaders = new LinkedList<>();
outer: for(final ClassAndLoader maxCandidate: getClassLoadersForTypes(types)) {
final Iterator<ClassAndLoader> it = maximumVisibilityLoaders.iterator();
while(it.hasNext()) {
final ClassAndLoader existingMax = it.next();
final boolean candidateSeesExisting = maxCandidate.canSee(existingMax);
final boolean exitingSeesCandidate = existingMax.canSee(maxCandidate);
if(candidateSeesExisting) {
if(!exitingSeesCandidate) {
// The candidate sees the the existing maximum, so drop the existing one as it's no longer maximal.
it.remove();
}
// NOTE: there's also the anomalous case where both loaders see each other. Not sure what to do
// about that one, as two distinct class loaders both seeing each other's classes is weird and
// violates the assumption that the relation "sees others' classes" is a partial ordering. We'll
// just not do anything, and treat them as incomparable; hopefully some later class loader that
// comes along can eliminate both of them, if it can not, we'll end up with ambiguity anyway and
// throw an error at the end.
} else if(exitingSeesCandidate) {
// Existing sees the candidate, so drop the candidate.
continue outer;
}
}
// If we get here, no existing maximum visibility loader could see the candidate, so the candidate is a new
// maximum.
maximumVisibilityLoaders.add(maxCandidate);
}
return maximumVisibilityLoaders;
}
private static Collection<ClassAndLoader> getClassLoadersForTypes(final Class<?>[] types) {
final Map<ClassAndLoader, ClassAndLoader> classesAndLoaders = new LinkedHashMap<>();
for(final Class<?> c: types) {
final ClassAndLoader cl = new ClassAndLoader(c, true);
if(!classesAndLoaders.containsKey(cl)) {
classesAndLoaders.put(cl, cl);
}
}
return classesAndLoaders.keySet();
}
}