
org.apache.commons.javaflow.providers.asm3.ClassHierarchy Maven / Gradle / Ivy
/**
* Copyright 2013-2022 Valery Silaev (http://vsilaev.com)
*
* Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.commons.javaflow.providers.asm3;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.WeakHashMap;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.apache.commons.javaflow.spi.ResourceLoader;
/**
* A class that computes the common super class of two classes without
* actually loading them with a ClassLoader.
*
* @author vsilaev
*/
public class ClassHierarchy {
private final ResourceLoader loader;
private final Map lookupCache;
private final Map> typesCache;
public ClassHierarchy(ResourceLoader loader) {
this.loader = loader;
this.lookupCache = new HashMap();
this.typesCache = new WeakHashMap>();
// Next will never be removed from the cache
// while there is a hard-reference
for (TypeInfo ti : SPECIAL_CLASSES) {
typesCache.put(ti, new SoftReference(ti));
}
}
private ClassHierarchy(ResourceLoader loader,
Map lookupCache,
Map> typesCache) {
this.loader = loader;
this.lookupCache = lookupCache;
this.typesCache = typesCache;
}
public ClassHierarchy shareWith(ResourceLoader resourceLoader) {
if (resourceLoader == this.loader) {
return this;
}
return new ClassHierarchy(resourceLoader, lookupCache, typesCache);
}
public boolean isSubClass(String type1, String type2) {
String commonSuperClass = getCommonSuperClass(type1, type2);
return type2.equals(commonSuperClass);
}
public boolean isSuperClass(String type1, String type2) {
// Biased towards isSublass logic while
// calculateCommonSuperClass is optimized this way
return isSubClass(type2, type1);
}
public String getCommonSuperClass(String type1, String type2) {
Key key = new Key(type1, type2);
String result;
synchronized (lookupCache) {
result = lookupCache.get(key);
if (null == result) {
result = calculateCommonSuperClass(type1, type2);
lookupCache.put(key, result);
}
}
return result;
}
Type getCommonSuperType(Type type1, Type type2) {
return Type.getObjectType(getCommonSuperClass(type1.getInternalName(), type2.getInternalName()));
}
private String calculateCommonSuperClass(final String type1, final String type2) {
try {
TypeInfo info1 = getTypeInfo(type1);
TypeInfo info2 = getTypeInfo(type2);
// Fast check without deep loading of info2
if (info1.isSubclassOf(info2)) {
return type2;
}
// The reverse, now both will be loaded
if (info2.isSubclassOf(info1)) {
return type1;
}
// Generic (worst) case -- flattening hierarchies
List supers1 = info1.flattenHierarchy();
List supers2 = info2.flattenHierarchy();
// Matching from the most specific to least specific
for (TypeInfo a : supers1) {
for (TypeInfo b : supers2) {
if (a.equals(b)) {
return a.name;
}
}
}
return OBJECT.name;
} catch (IOException e) {
throw new RuntimeException(e.toString());
}
}
TypeInfo getTypeInfo(String type) throws IOException {
TypeInfo key = new TypeInfo(type, null, null, false);
synchronized (typesCache) {
Reference reference = typesCache.get(key);
TypeInfo value = null != reference ? reference.get() : null;
if (null == value) {
value = loadTypeInfo(type);
// Same key & value
typesCache.put(value, new SoftReference(value));
}
return value;
}
}
/**
* Returns a ClassReader corresponding to the given class or interface.
*
* @param type
* the internal name of a class or interface.
* @return the ClassReader corresponding to 'type'.
* @throws IOException
* if the bytecode of 'type' cannot be loaded.
*/
private TypeInfo loadTypeInfo(String type) throws IOException {
InputStream is = loader.getResourceAsStream(type + ".class");
try {
ClassReader info = new ClassReader(is);
return new TypeInfo(info.getClassName(),
info.getSuperName(),
info.getInterfaces(),
(info.getAccess() & Opcodes.ACC_INTERFACE) != 0);
} finally {
is.close();
}
}
class TypeInfo {
final String name;
final boolean isInterface;
private String superClassName;
private TypeInfo superClass;
private String[] interfaceNames;
private TypeInfo[] interfaces;
TypeInfo(String name, String superClassName, String[] interfaceNames, boolean isInterface) {
this.name = name;
this.isInterface = isInterface;
this.superClassName = superClassName;
this.interfaceNames = null != interfaceNames ? interfaceNames : EMPTY_STRINGS;
}
synchronized TypeInfo superClass() throws IOException {
if (null != superClassName) {
// Not loaded yet
superClass = getTypeInfo(superClassName);
superClassName = null;
}
return superClass;
}
synchronized TypeInfo[] interfaces() throws IOException {
if (null != interfaceNames) {
// Not loaded yet
int size = interfaceNames.length;
if (size == 0) {
interfaces = EMPTY_TYPE_INFOS;
} else {
interfaces = new TypeInfo[size];
for (int i = size - 1; i >= 0; i--) {
interfaces[i] = getTypeInfo(interfaceNames[i]);
}
}
interfaceNames = null;
}
return interfaces;
}
boolean isSubclassOf(TypeInfo base) throws IOException {
String targetName = base.name;
// Check names first to avoid loading hierarchy
if (name.equals(targetName)) {
return true;
}
synchronized (this) {
if ((!base.isInterface) &&
null != superClassName &&
superClassName.equals(targetName)) {
return true;
}
if (base.isInterface && null != interfaceNames) {
for (int i = interfaceNames.length - 1; i >= 0; i--) {
if (interfaceNames[i].equals(targetName)) {
return true;
}
}
}
}
TypeInfo t = superClass();
if (null != t && t.isSubclassOf(base)) {
return true;
}
// If base is interface then check interfaces
if (base.isInterface) {
TypeInfo[] tt = interfaces();
for (int i = tt.length - 1; i >= 0; i--) {
if (tt[i].isSubclassOf(base)) {
return true;
}
}
}
return false;
}
List flattenHierarchy() throws IOException {
Queue superclasses = new LinkedList();
SortedSet interfaces = new TreeSet();
flattenHierarchy(superclasses, interfaces, new HashSet(), 0);
List result = new ArrayList(superclasses.size() +
interfaces.size() +
1);
result.addAll(superclasses);
result.addAll(narrow(interfaces));
result.add(OBJECT);
return result;
}
int flattenHierarchy(Queue superclasses,
SortedSet interfaces,
Set ivisited,
int depth) throws IOException {
int strength = initialStrength();
if (!isInterface) {
superclasses.add(this);
}
// Process superclass
TypeInfo stype = superClass();
if (null != stype) {
stype.flattenHierarchy(superclasses, interfaces, ivisited, depth + 1);
}
// Process interfaces;
TypeInfo[] itypes = interfaces();
int size = itypes.length;
for (int i = size - 1; i >= 0; i--) {
TypeInfo itype = itypes[i];
// From bottom to top, so append children
strength += itype.flattenHierarchy(null, interfaces, ivisited, depth + 1);
}
if (isInterface) {
if (!ivisited.contains(name)) {
// skip if re-implemented on higher level
// and first appears on lower (base) level
interfaces.add(new InterfaceEntry(this, strength, depth));
ivisited.add(name);
}
return strength;
} else {
return 0;
}
}
int initialStrength() {
return isInterface ? 1 : 0;
}
@Override
public String toString() {
return name;
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public boolean equals(Object other) {
if ((null == other) || !(other instanceof TypeInfo)) {
return false;
}
return this == other || name.equals(((TypeInfo)other).name);
}
}
private final TypeInfo OBJECT = new TypeInfo("java/lang/Object", null, null, false) {
@Override
TypeInfo superClass() {
return null;
}
@Override
TypeInfo[] interfaces() {
return EMPTY_TYPE_INFOS;
}
@Override
boolean isSubclassOf(TypeInfo base) {
return equals(base);
}
@Override
List flattenHierarchy() {
return Collections.singletonList(this);
}
@Override
int flattenHierarchy(Queue s, SortedSet i, Set v, int d) {
return 0;
}
};
class SpecialInterfaceInfo extends TypeInfo {
SpecialInterfaceInfo(String name, String[] interfaceNames) {
super(name, null, interfaceNames, true);
}
@Override
TypeInfo superClass() {
return null;
}
@Override
int initialStrength() {
return 0;
}
}
private static List narrow(SortedSet entries) {
int size = entries.size();
List result = new ArrayList(size);
for (InterfaceEntry ie : entries) {
result.add(ie.typeInfo);
}
return result;
}
static class InterfaceEntry implements Comparable {
final TypeInfo typeInfo;
final int strength;
final int depth;
InterfaceEntry(TypeInfo typeInfo, int strength, int depth) {
this.typeInfo = typeInfo;
this.strength = strength;
this.depth = depth;
}
@Override
public int compareTo(InterfaceEntry other) {
int delta = other.strength - this.strength;
if (delta != 0) {
return delta;
}
delta = this.depth - other.depth;
if (delta != 0) {
return delta;
}
return this.typeInfo.name.compareTo(other.typeInfo.name);
}
}
static class Key extends SymmetricalPair {
Key(String a, String b) {
super(a, b);
}
}
static class SymmetricalPair {
private final T a;
private final T b;
SymmetricalPair(T a, T b) {
this.a = a;
this.b = b;
}
@Override
public int hashCode() {
int hA = null == a ? 0 : a.hashCode();
int hB = null == b ? 0 : b.hashCode();
return Math.min(hA, hB) * 37 + Math.max(hA, hB);
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (other.getClass() != this.getClass()) {
return false;
}
@SuppressWarnings("unchecked")
SymmetricalPair that = (SymmetricalPair)other;
return same(this.a, that.a) && same(this.b, that.b) ||
same(this.a, that.b) && same(this.b, that.a);
}
private static boolean same(T a, T b) {
return a == null ? b == null : a.equals(b);
}
}
static final String[] EMPTY_STRINGS = new String[0];
static final TypeInfo[] EMPTY_TYPE_INFOS = new TypeInfo[0];
private final TypeInfo[] SPECIAL_CLASSES = {
OBJECT,
// These "special" technical interfaces are somewhat
// messed into interface hierarchies when implemented
// on concrete classes.
// So instead of Map you get Serializable, or instead of
// Iterable collection you get Serializable or Cloneable.
// This is not an error for this class usage, but nevertheless
// something annoying - shift them to the end of hierarchy
new SpecialInterfaceInfo("java/io/Externalizable", new String[] {"java/io/Serializable"}),
new SpecialInterfaceInfo("java/io/Closeable", new String[] {"java/lang/AutoCloseable"}),
new SpecialInterfaceInfo("java/io/Serializable", EMPTY_STRINGS),
new SpecialInterfaceInfo("java/lang/AutoCloseable", EMPTY_STRINGS),
new SpecialInterfaceInfo("java/lang/Cloneable", EMPTY_STRINGS),
};
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy