org.testifyproject.bytebuddy.dynamic.scaffold.MethodGraph Maven / Gradle / Ivy
package org.testifyproject.bytebuddy.dynamic.scaffold;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import lombok.EqualsAndHashCode;
import org.testifyproject.bytebuddy.description.method.MethodDescription;
import org.testifyproject.bytebuddy.description.method.MethodList;
import org.testifyproject.bytebuddy.description.modifier.Visibility;
import org.testifyproject.bytebuddy.description.type.TypeDefinition;
import org.testifyproject.bytebuddy.description.type.TypeDescription;
import org.testifyproject.bytebuddy.matcher.ElementMatcher;
import org.testifyproject.bytebuddy.matcher.FilterableList;
import org.testifyproject.bytebuddy.jar.asm.Opcodes;
import java.util.*;
import static org.testifyproject.bytebuddy.matcher.ElementMatchers.*;
/**
* A method graph represents a view on a set of methods as they are seen from a given type. Any method is represented as a node that represents
* a method, its bridge methods, its resolution state and information on if it was made visible by a visibility bridge.
*/
public interface MethodGraph {
/**
* Locates a node in this graph which represents the provided method token.
*
* @param token A method token that represents the method to be located.
* @return The node representing the given token.
*/
Node locate(MethodDescription.SignatureToken token);
/**
* Lists all nodes of this method graph.
*
* @return A list of all nodes of this method graph.
*/
NodeList listNodes();
/**
* A canonical implementation of an empty method graph.
*/
enum Empty implements MethodGraph.Linked, MethodGraph.Compiler {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public Node locate(MethodDescription.SignatureToken token) {
return Node.Unresolved.INSTANCE;
}
@Override
public NodeList listNodes() {
return new NodeList(Collections.emptyList());
}
@Override
public MethodGraph getSuperClassGraph() {
return this;
}
@Override
public MethodGraph getInterfaceGraph(TypeDescription typeDescription) {
return this;
}
@Override
public Linked compile(TypeDescription typeDescription) {
return this;
}
@Override
public Linked compile(TypeDefinition typeDefinition, TypeDescription viewPoint) {
return this;
}
}
/**
* A linked method graph represents a view that additionally exposes information of a given type's super type view and a
* view on this graph's directly implemented interfaces.
*/
interface Linked extends MethodGraph {
/**
* Returns a graph representing the view on this represented type's super type.
*
* @return A graph representing the view on this represented type's super type.
*/
MethodGraph getSuperClassGraph();
/**
* Returns a graph representing the view on this represented type's directly implemented interface type.
*
* @param typeDescription The interface type for which a view is to be returned.
* @return A graph representing the view on this represented type's directly implemented interface type.
*/
MethodGraph getInterfaceGraph(TypeDescription typeDescription);
/**
* A simple implementation of a linked method graph that exposes views by delegation to given method graphs.
*/
@EqualsAndHashCode
class Delegation implements Linked {
/**
* The represented type's method graph.
*/
private final MethodGraph methodGraph;
/**
* The super class's method graph.
*/
private final MethodGraph superClassGraph;
/**
* A mapping of method graphs of the represented type's directly implemented interfaces to their graph representatives.
*/
private final Map interfaceGraphs;
/**
* Creates a new delegation method graph.
*
* @param methodGraph The represented type's method graph.
* @param superClassGraph The super class's method graph.
* @param interfaceGraphs A mapping of method graphs of the represented type's directly implemented interfaces to their graph representatives.
*/
public Delegation(MethodGraph methodGraph, MethodGraph superClassGraph, Map interfaceGraphs) {
this.methodGraph = methodGraph;
this.superClassGraph = superClassGraph;
this.interfaceGraphs = interfaceGraphs;
}
@Override
public MethodGraph getSuperClassGraph() {
return superClassGraph;
}
@Override
public MethodGraph getInterfaceGraph(TypeDescription typeDescription) {
MethodGraph interfaceGraph = interfaceGraphs.get(typeDescription);
return interfaceGraph == null
? Empty.INSTANCE
: interfaceGraph;
}
@Override
public Node locate(MethodDescription.SignatureToken token) {
return methodGraph.locate(token);
}
@Override
public NodeList listNodes() {
return methodGraph.listNodes();
}
}
}
/**
* Represents a node within a method graph.
*/
interface Node {
/**
* Returns the sort of this node.
*
* @return The sort of this node.
*/
Sort getSort();
/**
* Returns the method that is represented by this node.
*
* @return The method that is represented by this node.
*/
MethodDescription getRepresentative();
/**
* Returns a set of type tokens that this method represents. This set contains the actual method's type including the
* types of all bridge methods.
*
* @return A set of type tokens that this method represents.
*/
Set getMethodTypes();
/**
* Returns the minimal method visibility of all methods that are represented by this node.
*
* @return The minimal method visibility of all methods that are represented by this node.
*/
Visibility getVisibility();
/**
* Represents a {@link org.testifyproject.bytebuddy.dynamic.scaffold.MethodGraph.Node}'s state.
*/
enum Sort {
/**
* Represents a resolved node that was made visible by a visibility bridge.
*/
VISIBLE(true, true, true),
/**
* Represents a resolved node that was not made visible by a visibility bridge.
*/
RESOLVED(true, true, false),
/**
* Represents an ambiguous node, i.e. a node that might refer to several methods.
*/
AMBIGUOUS(true, false, false),
/**
* Represents an unresolved node.
*/
UNRESOLVED(false, false, false);
/**
* {@code true} if this sort represents a resolved node.
*/
private final boolean resolved;
/**
* {@code true} if this sort represents a non-ambiguous node.
*/
private final boolean unique;
/**
* {@code true} if this sort represents a node that was made by a visibility bridge.
*/
private final boolean madeVisible;
/**
* Creates a new sort.
*
* @param resolved {@code true} if this sort represents a resolved node.
* @param unique {@code true} if this sort represents a non-ambiguous node.
* @param madeVisible {@code true} if this sort represents a node that was made by a visibility bridge.
*/
Sort(boolean resolved, boolean unique, boolean madeVisible) {
this.resolved = resolved;
this.unique = unique;
this.madeVisible = madeVisible;
}
/**
* Verifies if this sort represents a resolved node.
*
* @return {@code true} if this sort represents a resolved node.
*/
public boolean isResolved() {
return resolved;
}
/**
* Verifies if this sort represents a non-ambiguous node.
*
* @return {@code true} if this sort represents a non-ambiguous node.
*/
public boolean isUnique() {
return unique;
}
/**
* Verifies if this sort represents a node that was made visible by a visibility bridge.
*
* @return {@code true} if this sort represents a node that was made visible by a visibility bridge.
*/
public boolean isMadeVisible() {
return madeVisible;
}
}
/**
* A canonical implementation of an unresolved node.
*/
enum Unresolved implements Node {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public Sort getSort() {
return Sort.UNRESOLVED;
}
@Override
public MethodDescription getRepresentative() {
throw new IllegalStateException("Cannot resolve the method of an illegal node");
}
@Override
public Set getMethodTypes() {
throw new IllegalStateException("Cannot resolve bridge method of an illegal node");
}
@Override
public Visibility getVisibility() {
throw new IllegalStateException("Cannot resolve visibility of an illegal node");
}
}
/**
* A simple implementation of a resolved node of a method without bridges.
*/
@EqualsAndHashCode
class Simple implements Node {
/**
* The represented method.
*/
private final MethodDescription methodDescription;
/**
* Creates a simple node.
*
* @param methodDescription The represented method.
*/
public Simple(MethodDescription methodDescription) {
this.methodDescription = methodDescription;
}
@Override
public Sort getSort() {
return Sort.RESOLVED;
}
@Override
public MethodDescription getRepresentative() {
return methodDescription;
}
@Override
public Set getMethodTypes() {
return Collections.emptySet();
}
@Override
public Visibility getVisibility() {
return methodDescription.getVisibility();
}
}
}
/**
* A compiler to produce a {@link MethodGraph} from a given type.
*/
@SuppressFBWarnings(value = "IC_SUPERCLASS_USES_SUBCLASS_DURING_INITIALIZATION", justification = "Safe initialization is implied")
interface Compiler {
/**
* The default compiler for compiling Java methods.
*/
Compiler DEFAULT = MethodGraph.Compiler.Default.forJavaHierarchy();
/**
* Compiles the given type into a method graph considering the type to be the viewpoint.
*
* @param typeDescription The type to be compiled.
* @return A linked method graph representing the given type.
*/
MethodGraph.Linked compile(TypeDescription typeDescription);
/**
* Compiles the given type into a method graph.
*
* @param typeDefinition The type to be compiled.
* @param viewPoint The view point that determines the method's visibility.
* @return A linked method graph representing the given type.
*/
MethodGraph.Linked compile(TypeDefinition typeDefinition, TypeDescription viewPoint);
/**
* A flat compiler that simply returns the methods that are declared by the instrumented type.
*/
enum ForDeclaredMethods implements Compiler {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public Linked compile(TypeDescription typeDescription) {
return compile(typeDescription, typeDescription);
}
@Override
public Linked compile(TypeDefinition typeDefinition, TypeDescription viewPoint) {
LinkedHashMap nodes = new LinkedHashMap();
for (MethodDescription methodDescription : typeDefinition.getDeclaredMethods().filter(isVirtual().and(not(isBridge())).and(isVisibleTo(viewPoint)))) {
nodes.put(methodDescription.asSignatureToken(), new Node.Simple(methodDescription));
}
return new Linked.Delegation(new MethodGraph.Simple(nodes), Empty.INSTANCE, Collections.emptyMap());
}
}
/**
* An abstract base implementation of a method graph compiler.
*/
abstract class AbstractBase implements Compiler {
@Override
public Linked compile(TypeDescription typeDescription) {
return compile(typeDescription, typeDescription);
}
}
/**
* A default implementation of a method graph.
*
* @param The type of the harmonizer token to be used for linking methods of different types.
*/
@EqualsAndHashCode(callSuper = false)
class Default extends AbstractBase {
/**
* The harmonizer to be used.
*/
private final Harmonizer harmonizer;
/**
* The merger to be used.
*/
private final Merger merger;
/**
* A visitor to apply to all type descriptions before analyzing their methods or resolving super types.
*/
private final TypeDescription.Generic.Visitor extends TypeDescription.Generic> visitor;
/**
* Creates a new default method graph compiler.
*
* @param harmonizer The harmonizer to be used.
* @param merger The merger to be used.
* @param visitor A visitor to apply to all type descriptions before analyzing their methods or resolving super types.
*/
protected Default(Harmonizer harmonizer, Merger merger, TypeDescription.Generic.Visitor extends TypeDescription.Generic> visitor) {
this.harmonizer = harmonizer;
this.merger = merger;
this.visitor = visitor;
}
/**
* Creates a default compiler using the given harmonizer and merger. All raw types are reified before analyzing their properties.
*
* @param harmonizer The harmonizer to be used for creating tokens that uniquely identify a method hierarchy.
* @param merger The merger to be used for identifying a method to represent an ambiguous method resolution.
* @param The type of the harmonizer token.
* @return A default compiler for the given harmonizer and merger.
*/
public static Compiler of(Harmonizer harmonizer, Merger merger) {
return new Default(harmonizer, merger, TypeDescription.Generic.Visitor.Reifying.INITIATING);
}
/**
* Creates a default compiler using the given harmonizer and merger.
*
* @param harmonizer The harmonizer to be used for creating tokens that uniquely identify a method hierarchy.
* @param merger The merger to be used for identifying a method to represent an ambiguous method resolution.
* @param visitor A visitor to apply to all type descriptions before analyzing their methods or resolving super types.
* @param The type of the harmonizer token.
* @return A default compiler for the given harmonizer and merger.
*/
public static Compiler of(Harmonizer harmonizer, Merger merger, TypeDescription.Generic.Visitor extends TypeDescription.Generic> visitor) {
return new Default(harmonizer, merger, visitor);
}
/**
*
* Creates a default compiler for a method hierarchy following the rules of the Java programming language. According
* to these rules, two methods of the same name are only different if their parameter types represent different raw
* types. The return type is not considered as a part of the signature.
*
*
* Ambiguous methods are merged by considering the method that was discovered first.
*
*
* @return A compiler for resolving a method hierarchy following the rules of the Java programming language.
*/
public static Compiler forJavaHierarchy() {
return of(Harmonizer.ForJavaMethod.INSTANCE, Merger.Directional.LEFT);
}
/**
*
* Creates a default compiler for a method hierarchy following the rules of the Java virtual machine. According
* to these rules, two methods of the same name are different if their parameter types and return types represent
* different type erasures.
*
*
* Ambiguous methods are merged by considering the method that was discovered first.
*
*
* @return A compiler for resolving a method hierarchy following the rules of the Java programming language.
*/
public static Compiler forJVMHierarchy() {
return of(Harmonizer.ForJVMMethod.INSTANCE, Merger.Directional.LEFT);
}
@Override
public MethodGraph.Linked compile(TypeDefinition typeDefinition, TypeDescription viewPoint) {
Map> snapshots = new HashMap>();
Key.Store> rootStore = doAnalyze(typeDefinition, snapshots, isVirtual().and(isVisibleTo(viewPoint)));
TypeDescription.Generic superClass = typeDefinition.getSuperClass();
List interfaceTypes = typeDefinition.getInterfaces();
Map interfaceGraphs = new HashMap();
for (TypeDescription.Generic interfaceType : interfaceTypes) {
interfaceGraphs.put(interfaceType.asErasure(), snapshots.get(interfaceType).asGraph(merger));
}
return new Linked.Delegation(rootStore.asGraph(merger),
superClass == null
? Empty.INSTANCE
: snapshots.get(superClass).asGraph(merger),
interfaceGraphs);
}
/**
* Analyzes the given type description without checking if the end of the type hierarchy was reached.
*
* @param typeDefinition The type to analyze.
* @param key The type in its original form before applying the visitor.
* @param snapshots A map containing snapshots of key stores for previously analyzed types.
* @param relevanceMatcher A matcher for filtering methods that should be included in the graph.
* @return A key store describing the provided type.
*/
protected Key.Store analyze(TypeDefinition typeDefinition,
TypeDefinition key,
Map> snapshots,
ElementMatcher super MethodDescription> relevanceMatcher) {
Key.Store store = snapshots.get(key);
if (store == null) {
store = doAnalyze(typeDefinition, snapshots, relevanceMatcher);
snapshots.put(key, store);
}
return store;
}
/**
* Analyzes the given type description.
*
* @param typeDescription The type to analyze.
* @param snapshots A map containing snapshots of key stores for previously analyzed types.
* @param relevanceMatcher A matcher for filtering methods that should be included in the graph.
* @return A key store describing the provided type.
*/
protected Key.Store analyzeNullable(TypeDescription.Generic typeDescription,
Map> snapshots,
ElementMatcher super MethodDescription> relevanceMatcher) {
return typeDescription == null
? new Key.Store()
: analyze(typeDescription.accept(visitor), typeDescription, snapshots, relevanceMatcher);
}
/**
* Analyzes the given type description without checking if it is already presented in the key store.
*
* @param typeDefinition The type to analyze.
* @param snapshots A map containing snapshots of key stores for previously analyzed types.
* @param relevanceMatcher A matcher for filtering methods that should be included in the graph.
* @return A key store describing the provided type.
*/
protected Key.Store doAnalyze(TypeDefinition typeDefinition,
Map> snapshots,
ElementMatcher super MethodDescription> relevanceMatcher) {
Key.Store store = analyzeNullable(typeDefinition.getSuperClass(), snapshots, relevanceMatcher);
Key.Store interfaceStore = new Key.Store();
for (TypeDescription.Generic interfaceType : typeDefinition.getInterfaces()) {
interfaceStore = interfaceStore.combineWith(analyze(interfaceType.accept(visitor), interfaceType, snapshots, relevanceMatcher));
}
store = store.inject(interfaceStore);
for (MethodDescription methodDescription : typeDefinition.getDeclaredMethods().filter(relevanceMatcher)) {
store = store.registerTopLevel(methodDescription, harmonizer);
}
return store;
}
/**
* A harmonizer is responsible for creating a token that identifies a method's relevant attributes for considering
* two methods of being equal or not.
*
* @param The type of the token that is created by the implementing harmonizer.
*/
public interface Harmonizer {
/**
* Harmonizes the given type token.
*
* @param typeToken The type token to harmonize.
* @return A token representing the given type token.
*/
S harmonize(MethodDescription.TypeToken typeToken);
/**
* A harmonizer for the Java programming language that identifies a method by its parameter types only.
*/
enum ForJavaMethod implements Harmonizer {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public Token harmonize(MethodDescription.TypeToken typeToken) {
return new Token(typeToken);
}
/**
* A token that identifies a Java method's type by its parameter types only.
*/
protected static class Token {
/**
* The represented type token.
*/
private final MethodDescription.TypeToken typeToken;
/**
* Creates a new type token for a Java method.
*
* @param typeToken The represented type token.
*/
protected Token(MethodDescription.TypeToken typeToken) {
this.typeToken = typeToken;
}
@Override
public boolean equals(Object other) {
return this == other || (other instanceof Token && typeToken.getParameterTypes().equals(((Token) other).typeToken.getParameterTypes()));
}
@Override
public int hashCode() {
return typeToken.getParameterTypes().hashCode();
}
@Override
public String toString() {
return typeToken.getParameterTypes().toString();
}
}
}
/**
* A harmonizer for the Java virtual machine's method dispatching rules that identifies a method by its parameter types and return type.
*/
enum ForJVMMethod implements Harmonizer {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public Token harmonize(MethodDescription.TypeToken typeToken) {
return new Token(typeToken);
}
/**
* A token that identifies a Java method's type by its parameter types and return type.
*/
protected static class Token {
/**
* The represented type token.
*/
private final MethodDescription.TypeToken typeToken;
/**
* Creates a new type token for a JVM method.
*
* @param typeToken The represented type token.
*/
public Token(MethodDescription.TypeToken typeToken) {
this.typeToken = typeToken;
}
@Override
public boolean equals(Object other) {
return this == other || other instanceof Token
&& typeToken.getReturnType().equals(((Token) other).typeToken.getReturnType())
&& typeToken.getParameterTypes().equals(((Token) other).typeToken.getParameterTypes());
}
@Override
public int hashCode() {
return typeToken.getReturnType().hashCode() + 31 * typeToken.getParameterTypes().hashCode();
}
@Override
public String toString() {
return typeToken.toString();
}
}
}
}
/**
* Implementations are responsible for identifying a representative method for a {@link org.testifyproject.bytebuddy.dynamic.scaffold.MethodGraph.Node}
* between several ambiguously resolved methods.
*/
public interface Merger {
/**
* Merges two ambiguously resolved methods to yield a single representative.
*
* @param left The left method description, i.e. the method that was discovered first or was previously merged.
* @param right The right method description, i.e. the method that was discovered last.
* @return A method description compatible to both method's types that is used as a representative.
*/
MethodDescription merge(MethodDescription left, MethodDescription right);
/**
* A directional merger that always returns either the left or right method description.
*/
enum Directional implements Merger {
/**
* A merger that always returns the left method, i.e. the method that was discovered first or was previously merged.
*/
LEFT(true),
/**
* A merger that always returns the right method, i.e. the method that was discovered last.
*/
RIGHT(false);
/**
* {@code true} if the left method should be returned when merging methods.
*/
private final boolean left;
/**
* Creates a directional merger.
*
* @param left {@code true} if the left method should be returned when merging methods.
*/
Directional(boolean left) {
this.left = left;
}
@Override
public MethodDescription merge(MethodDescription left, MethodDescription right) {
return this.left
? left
: right;
}
}
}
/**
* A key represents a collection of methods within a method graph to later yield a node representing a collection of methods,
* i.e. a method representative including information on the required method bridges.
*
* @param The type of the token used for deciding on method equality.
*/
protected abstract static class Key {
/**
* The internal name of the method this key identifies.
*/
protected final String internalName;
/**
* Creates a new key.
*
* @param internalName The internal name of the method this key identifies.
*/
protected Key(String internalName) {
this.internalName = internalName;
}
/**
* Returns a set of all identifiers of this key.
*
* @return A set of all identifiers of this key.
*/
protected abstract Set getIdentifiers();
@Override
public boolean equals(Object other) {
return other == this || (other instanceof Key
&& internalName.equals(((Key) other).internalName)
&& !Collections.disjoint(getIdentifiers(), ((Key) other).getIdentifiers()));
}
@Override
public int hashCode() {
return internalName.hashCode();
}
/**
* A harmonized key represents a key where equality is decided based on tokens that are returned by a
* {@link org.testifyproject.bytebuddy.dynamic.scaffold.MethodGraph.Compiler.Default.Harmonizer}.
*
* @param The type of the tokens yielded by a harmonizer.
*/
protected static class Harmonized extends Key {
/**
* A mapping of identifiers to the type tokens they represent.
*/
private final Map> identifiers;
/**
* Creates a new harmonized key.
*
* @param internalName The internal name of the method this key identifies.
* @param identifiers A mapping of identifiers to the type tokens they represent.
*/
protected Harmonized(String internalName, Map> identifiers) {
super(internalName);
this.identifiers = identifiers;
}
/**
* Creates a new harmonized key for the given method description.
*
* @param methodDescription The method description to represent as a harmonized key.
* @param harmonizer The harmonizer to use.
* @param The type of the token yielded by a harmonizer.
* @return A harmonized key representing the provided method.
*/
protected static Harmonized of(MethodDescription methodDescription, Harmonizer harmonizer) {
MethodDescription.TypeToken typeToken = methodDescription.asTypeToken();
return new Harmonized(methodDescription.getInternalName(),
Collections.singletonMap(harmonizer.harmonize(typeToken), Collections.emptySet()));
}
/**
* Creates a detached version of this key.
*
* @param typeToken The type token of the representative method.
* @return The detached version of this key.
*/
protected Detached detach(MethodDescription.TypeToken typeToken) {
Set identifiers = new HashSet();
for (Set typeTokens : this.identifiers.values()) {
identifiers.addAll(typeTokens);
}
identifiers.add(typeToken);
return new Detached(internalName, identifiers);
}
/**
* Combines this key with the given key.
*
* @param key The key to be merged with this key.
* @return A harmonized key representing the merger of this key and the given key.
*/
protected Harmonized combineWith(Harmonized key) {
Map> identifiers = new HashMap>(this.identifiers);
for (Map.Entry> entry : key.identifiers.entrySet()) {
Set typeTokens = identifiers.get(entry.getKey());
if (typeTokens == null) {
identifiers.put(entry.getKey(), entry.getValue());
} else {
typeTokens = new HashSet(typeTokens);
typeTokens.addAll(entry.getValue());
identifiers.put(entry.getKey(), typeTokens);
}
}
return new Harmonized(internalName, identifiers);
}
/**
* Extends this key by the given method description.
*
* @param methodDescription The method to extend this key with.
* @param harmonizer The harmonizer to use for determining method equality.
* @return The harmonized key representing the extension of this key with the provided method.
*/
protected Harmonized extend(MethodDescription.InDefinedShape methodDescription, Harmonizer harmonizer) {
Map> identifiers = new HashMap>(this.identifiers);
MethodDescription.TypeToken typeToken = methodDescription.asTypeToken();
V identifier = harmonizer.harmonize(typeToken);
Set typeTokens = identifiers.get(identifier);
if (typeTokens == null) {
identifiers.put(identifier, Collections.singleton(typeToken));
} else {
typeTokens = new HashSet(typeTokens);
typeTokens.add(typeToken);
identifiers.put(identifier, typeTokens);
}
return new Harmonized(internalName, identifiers);
}
@Override
protected Set getIdentifiers() {
return identifiers.keySet();
}
}
/**
* A detached version of a key that identifies methods by their JVM signature, i.e. parameter types and return type.
*/
protected static class Detached extends Key {
/**
* The type tokens represented by this key.
*/
private final Set identifiers;
/**
* Creates a new detached key.
*
* @param internalName The internal name of the method this key identifies.
* @param identifiers The type tokens represented by this key.
*/
protected Detached(String internalName, Set identifiers) {
super(internalName);
this.identifiers = identifiers;
}
/**
* Creates a new detached key of the given method token.
*
* @param token The method token to represent as a key.
* @return A detached key representing the given method token..
*/
protected static Detached of(MethodDescription.SignatureToken token) {
return new Detached(token.getName(), Collections.singleton(token.asTypeToken()));
}
@Override
protected Set getIdentifiers() {
return identifiers;
}
}
/**
* A store for collected methods that are identified by keys.
*
* @param The type of the token used for deciding on method equality.
*/
@EqualsAndHashCode
protected static class Store {
/**
* A mapping of harmonized keys to their represented entry.
*/
private final LinkedHashMap, Entry> entries;
/**
* Creates an empty store.
*/
protected Store() {
this(new LinkedHashMap, Entry>());
}
/**
* Creates a new store representing the given entries.
*
* @param entries A mapping of harmonized keys to their represented entry.
*/
private Store(LinkedHashMap, Entry> entries) {
this.entries = entries;
}
/**
* Combines the two given stores.
*
* @param left The left store to be combined.
* @param right The right store to be combined.
* @param The type of the harmonized key of both stores.
* @return An entry representing the combination of both stores.
*/
private static Entry combine(Entry left, Entry right) {
Set leftMethods = left.getCandidates(), rightMethods = right.getCandidates();
LinkedHashSet combined = new LinkedHashSet(leftMethods.size() + rightMethods.size());
combined.addAll(leftMethods);
combined.addAll(rightMethods);
for (MethodDescription leftMethod : leftMethods) {
TypeDescription leftType = leftMethod.getDeclaringType().asErasure();
for (MethodDescription rightMethod : rightMethods) {
TypeDescription rightType = rightMethod.getDeclaringType().asErasure();
if (leftType.equals(rightType)) {
break;
} else if (leftType.isAssignableTo(rightType)) {
combined.remove(rightMethod);
break;
} else if (leftType.isAssignableFrom(rightType)) {
combined.remove(leftMethod);
break;
}
}
}
Key.Harmonized key = left.getKey().combineWith(right.getKey());
Visibility visibility = left.getVisibility().expandTo(right.getVisibility());
return combined.size() == 1
? new Entry.Resolved(key, combined.iterator().next(), visibility, Entry.Resolved.NOT_MADE_VISIBLE)
: new Entry.Ambiguous(key, combined, visibility);
}
/**
* Registers a new top level method within this store.
*
* @param methodDescription The method to register.
* @param harmonizer The harmonizer to use for determining method equality.
* @return A store with the given method registered as a top-level method.
*/
protected Store registerTopLevel(MethodDescription methodDescription, Harmonizer harmonizer) {
Harmonized key = Harmonized.of(methodDescription, harmonizer);
LinkedHashMap, Entry> entries = new LinkedHashMap, Entry>(this.entries);
Entry currentEntry = entries.remove(key);
Entry extendedEntry = (currentEntry == null
? new Entry.Initial(key)
: currentEntry).extendBy(methodDescription, harmonizer);
entries.put(extendedEntry.getKey(), extendedEntry);
return new Store(entries);
}
/**
* Combines this store with the given store.
*
* @param store The store to combine with this store.
* @return A store representing a combination of this store and the given store.
*/
protected Store combineWith(Store store) {
Store combinedStore = this;
for (Entry entry : store.entries.values()) {
combinedStore = combinedStore.combineWith(entry);
}
return combinedStore;
}
/**
* Combines this store with the given entry.
*
* @param entry The entry to combine with this store.
* @return A store representing a combination of this store and the given entry.
*/
protected Store combineWith(Entry entry) {
LinkedHashMap, Entry> entries = new LinkedHashMap, Entry>(this.entries);
Entry previousEntry = entries.remove(entry.getKey());
Entry injectedEntry = previousEntry == null
? entry
: combine(previousEntry, entry);
entries.put(injectedEntry.getKey(), injectedEntry);
return new Store(entries);
}
/**
* Injects the given store into this store.
*
* @param store The key store to inject into this store.
* @return A store that represents this store with the given store injected.
*/
protected Store inject(Store store) {
Store injectedStore = this;
for (Entry entry : store.entries.values()) {
injectedStore = injectedStore.inject(entry);
}
return injectedStore;
}
/**
* Injects the given entry into this store.
*
* @param entry The entry to be injected into this store.
* @return A store that represents this store with the given entry injected.
*/
protected Store inject(Entry entry) {
LinkedHashMap, Entry> entries = new LinkedHashMap, Entry>(this.entries);
Entry dominantEntry = entries.remove(entry.getKey());
Entry injectedEntry = dominantEntry == null
? entry
: dominantEntry.inject(entry.getKey(), entry.getVisibility());
entries.put(injectedEntry.getKey(), injectedEntry);
return new Store(entries);
}
/**
* Transforms this store into a method graph by applying the given merger.
*
* @param merger The merger to apply for resolving the representative for ambiguous resolutions.
* @return The method graph that represents this key store.
*/
protected MethodGraph asGraph(Merger merger) {
LinkedHashMap, Node> entries = new LinkedHashMap, Node>();
for (Entry entry : this.entries.values()) {
Node node = entry.asNode(merger);
entries.put(entry.getKey().detach(node.getRepresentative().asTypeToken()), node);
}
return new Graph(entries);
}
/**
* An entry of a key store.
*
* @param The type of the harmonized token used for determining method equality.
*/
protected interface Entry {
/**
* Returns the harmonized key of this entry.
*
* @return The harmonized key of this entry.
*/
Harmonized getKey();
/**
* Returns all candidate methods represented by this entry.
*
* @return All candidate methods represented by this entry.
*/
Set getCandidates();
/**
* Returns the minimal visibility of this entry.
*
* @return The minimal visibility of this entry.
*/
Visibility getVisibility();
/**
* Extends this entry by the given method.
*
* @param methodDescription The method description to extend this entry with.
* @param harmonizer The harmonizer to use for determining method equality.
* @return This key extended by the given method.
*/
Entry extendBy(MethodDescription methodDescription, Harmonizer harmonizer);
/**
* Injects the given key into this entry.
*
* @param key The key to inject into this entry.
* @param visibility The entry's minimal visibility.
* @return This entry extended with the given key.
*/
Entry inject(Harmonized key, Visibility visibility);
/**
* Transforms this entry into a node.
*
* @param merger The merger to use for determining the representative method of an ambiguous node.
* @return The resolved node.
*/
Node asNode(Merger merger);
/**
* An entry in its initial state before registering any method as a representative.
*
* @param The type of the harmonized key to determine method equality.
*/
class Initial implements Entry {
/**
* The harmonized key this entry represents.
*/
private final Harmonized key;
/**
* Creates a new initial key.
*
* @param key The harmonized key this entry represents.
*/
protected Initial(Harmonized key) {
this.key = key;
}
@Override
public Harmonized getKey() {
throw new IllegalStateException("Cannot extract key from initial entry:" + this);
}
@Override
public Set getCandidates() {
throw new IllegalStateException("Cannot extract method from initial entry:" + this);
}
@Override
public Visibility getVisibility() {
throw new IllegalStateException("Cannot extract visibility from initial entry:" + this);
}
@Override
public Entry extendBy(MethodDescription methodDescription, Harmonizer harmonizer) {
return new Resolved(key.extend(methodDescription.asDefined(), harmonizer),
methodDescription,
methodDescription.getVisibility(),
Resolved.NOT_MADE_VISIBLE);
}
@Override
public Entry inject(Harmonized key, Visibility visibility) {
throw new IllegalStateException("Cannot inject into initial entry without a registered method: " + this);
}
@Override
public Node asNode(Merger merger) {
throw new IllegalStateException("Cannot transform initial entry without a registered method: " + this);
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& key.equals(((Initial>) other).key);
}
@Override
public int hashCode() {
return key.hashCode();
}
}
/**
* An entry representing a non-ambiguous node resolution.
*
* @param The type of the harmonized key to determine method equality.
*/
@EqualsAndHashCode
class Resolved implements Entry {
/**
* Indicates that a type's methods are already globally visible, meaning that a bridge method is not added
* with the intend of creating a visibility bridge.
*/
private static final int MADE_VISIBLE = Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED;
/**
* Indicates that the entry was not made visible.
*/
private static final boolean NOT_MADE_VISIBLE = false;
/**
* The harmonized key this entry represents.
*/
private final Harmonized key;
/**
* The non-ambiguous, representative method of this entry.
*/
private final MethodDescription methodDescription;
/**
* The minimal required visibility for this method.
*/
private final Visibility visibility;
/**
* {@code true} if this entry's representative was made visible by a visibility bridge.
*/
private final boolean madeVisible;
/**
* Creates a new resolved entry.
*
* @param key The harmonized key this entry represents.
* @param methodDescription The non-ambiguous, representative method of this entry.
* @param visibility The minimal required visibility for this method.
* @param madeVisible {@code true} if this entry's representative was made visible by a visibility bridge.
*/
protected Resolved(Harmonized key, MethodDescription methodDescription, Visibility visibility, boolean madeVisible) {
this.key = key;
this.methodDescription = methodDescription;
this.visibility = visibility;
this.madeVisible = madeVisible;
}
/**
* Creates an entry for an override where a method overrides another method within a super class.
*
* @param key The merged key for both methods.
* @param override The method declared by the extending type, potentially a bridge method.
* @param original The method that is overridden by the extending type.
* @param visibility The minimal required visibility for this entry.
* @param The type of the harmonized key to determine method equality.
* @return An entry representing the merger of both methods.
*/
private static Entry of(Harmonized key, MethodDescription override, MethodDescription original, Visibility visibility) {
visibility = visibility.expandTo(original.getVisibility()).expandTo(override.getVisibility());
return override.isBridge()
? new Resolved(key, original, visibility, (original.getDeclaringType().getModifiers() & MADE_VISIBLE) == 0)
: new Resolved(key, override, visibility, NOT_MADE_VISIBLE);
}
@Override
public Harmonized getKey() {
return key;
}
@Override
public Set getCandidates() {
return Collections.singleton(methodDescription);
}
@Override
public Visibility getVisibility() {
return visibility;
}
@Override
public Entry extendBy(MethodDescription methodDescription, Harmonizer harmonizer) {
Harmonized key = this.key.extend(methodDescription.asDefined(), harmonizer);
Visibility visibility = this.visibility.expandTo(methodDescription.getVisibility());
return methodDescription.getDeclaringType().equals(this.methodDescription.getDeclaringType())
? Ambiguous.of(key, methodDescription, this.methodDescription, visibility)
: Resolved.of(key, methodDescription, this.methodDescription, visibility);
}
@Override
public Entry inject(Harmonized key, Visibility visibility) {
return new Resolved(this.key.combineWith(key), methodDescription, this.visibility.expandTo(visibility), madeVisible);
}
@Override
public MethodGraph.Node asNode(Merger merger) {
return new Node(key.detach(methodDescription.asTypeToken()), methodDescription, visibility, madeVisible);
}
/**
* A node implementation representing a non-ambiguous method.
*/
@EqualsAndHashCode
protected static class Node implements MethodGraph.Node {
/**
* The detached key representing this node.
*/
private final Detached key;
/**
* The representative method of this node.
*/
private final MethodDescription methodDescription;
/**
* The node's minimal visibility.
*/
private final Visibility visibility;
/**
* {@code true} if the represented method was made explicitly visible by a visibility bridge.
*/
private final boolean visible;
/**
* Creates a new node.
*
* @param key The detached key representing this node.
* @param methodDescription The representative method of this node.
* @param visibility The node's minimal visibility.
* @param visible {@code true} if the represented method was made explicitly visible by a visibility bridge.
*/
protected Node(Detached key, MethodDescription methodDescription, Visibility visibility, boolean visible) {
this.key = key;
this.methodDescription = methodDescription;
this.visibility = visibility;
this.visible = visible;
}
@Override
public Sort getSort() {
return visible
? Sort.VISIBLE
: Sort.RESOLVED;
}
@Override
public MethodDescription getRepresentative() {
return methodDescription;
}
@Override
public Set getMethodTypes() {
return key.getIdentifiers();
}
@Override
public Visibility getVisibility() {
return visibility;
}
}
}
/**
* An entry representing an ambiguous node resolution.
*
* @param The type of the harmonized key to determine method equality.
*/
@EqualsAndHashCode
class Ambiguous implements Entry {
/**
* The harmonized key this entry represents.
*/
private final Harmonized key;
/**
* A set of ambiguous methods that this entry represents.
*/
private final LinkedHashSet methodDescriptions;
/**
* The minimal required visibility for this method.
*/
private final Visibility visibility;
/**
* Creates a new ambiguous entry.
*
* @param key The harmonized key this entry represents.
* @param methodDescriptions A set of ambiguous methods that this entry represents.
* @param visibility The minimal required visibility for this method.
*/
protected Ambiguous(Harmonized key, LinkedHashSet methodDescriptions, Visibility visibility) {
this.key = key;
this.methodDescriptions = methodDescriptions;
this.visibility = visibility;
}
/**
* Creates a new ambiguous entry if both provided entries are not considered to be a bridge of one another.
*
* @param key The key of the entry to be created.
* @param left The left method to be considered.
* @param right The right method to be considered.
* @param visibility The entry's minimal visibility.
* @param The type of the token of the harmonized key to determine method equality.
* @return The entry representing both methods.
*/
protected static Entry of(Harmonized key, MethodDescription left, MethodDescription right, Visibility visibility) {
visibility = visibility.expandTo(left.getVisibility()).expandTo(right.getVisibility());
return left.isBridge() ^ right.isBridge()
? new Resolved(key, left.isBridge() ? right : left, visibility, Resolved.NOT_MADE_VISIBLE)
: new Ambiguous(key, new LinkedHashSet(Arrays.asList(left, right)), visibility);
}
@Override
public Harmonized getKey() {
return key;
}
@Override
public Set getCandidates() {
return methodDescriptions;
}
@Override
public Visibility getVisibility() {
return visibility;
}
@Override
public Entry extendBy(MethodDescription methodDescription, Harmonizer harmonizer) {
Harmonized key = this.key.extend(methodDescription.asDefined(), harmonizer);
LinkedHashSet methodDescriptions = new LinkedHashSet(this.methodDescriptions.size() + 1);
TypeDescription declaringType = methodDescription.getDeclaringType().asErasure();
boolean bridge = methodDescription.isBridge();
Visibility visibility = this.visibility;
for (MethodDescription extendedMethod : this.methodDescriptions) {
if (extendedMethod.getDeclaringType().asErasure().equals(declaringType)) {
if (extendedMethod.isBridge() ^ bridge) {
methodDescriptions.add(bridge ? extendedMethod : methodDescription);
} else {
methodDescriptions.add(methodDescription);
methodDescriptions.add(extendedMethod);
}
}
visibility = visibility.expandTo(extendedMethod.getVisibility());
}
if (methodDescriptions.isEmpty()) {
return new Resolved(key, methodDescription, visibility, bridge);
} else if (methodDescriptions.size() == 1) {
return new Resolved(key, methodDescriptions.iterator().next(), visibility, Resolved.NOT_MADE_VISIBLE);
} else {
return new Ambiguous(key, methodDescriptions, visibility);
}
}
@Override
public Entry inject(Harmonized key, Visibility visibility) {
return new Ambiguous(this.key.combineWith(key), methodDescriptions, this.visibility.expandTo(visibility));
}
@Override
public MethodGraph.Node asNode(Merger merger) {
Iterator iterator = methodDescriptions.iterator();
MethodDescription methodDescription = iterator.next();
while (iterator.hasNext()) {
methodDescription = merger.merge(methodDescription, iterator.next());
}
return new Node(key.detach(methodDescription.asTypeToken()), methodDescription, visibility);
}
/**
* A node implementation representing an ambiguous method resolution.
*/
@EqualsAndHashCode
protected static class Node implements MethodGraph.Node {
/**
* The detached key representing this node.
*/
private final Detached key;
/**
* The representative method of this node.
*/
private final MethodDescription methodDescription;
/**
* The node's minimal visibility.
*/
private final Visibility visibility;
/**
* @param key The detached key representing this node.
* @param methodDescription The representative method of this node.
* @param visibility The node's minimal visibility.
*/
protected Node(Detached key, MethodDescription methodDescription, Visibility visibility) {
this.key = key;
this.methodDescription = methodDescription;
this.visibility = visibility;
}
@Override
public Sort getSort() {
return Sort.AMBIGUOUS;
}
@Override
public MethodDescription getRepresentative() {
return methodDescription;
}
@Override
public Set getMethodTypes() {
return key.getIdentifiers();
}
@Override
public Visibility getVisibility() {
return visibility;
}
}
}
}
/**
* A graph implementation based on a key store.
*/
@EqualsAndHashCode
protected static class Graph implements MethodGraph {
/**
* A mapping of a node's type tokens to the represented node.
*/
private final LinkedHashMap, Node> entries;
/**
* Creates a new graph.
*
* @param entries A mapping of a node's type tokens to the represented node.
*/
protected Graph(LinkedHashMap, Node> entries) {
this.entries = entries;
}
@Override
public Node locate(MethodDescription.SignatureToken token) {
Node node = entries.get(Detached.of(token));
return node == null
? Node.Unresolved.INSTANCE
: node;
}
@Override
public NodeList listNodes() {
return new NodeList(new ArrayList(entries.values()));
}
}
}
}
}
}
/**
* A list of nodes.
*/
class NodeList extends FilterableList.AbstractBase {
/**
* The represented nodes.
*/
private final List extends Node> nodes;
/**
* Creates a list of nodes.
*
* @param nodes The represented nodes.
*/
public NodeList(List extends Node> nodes) {
this.nodes = nodes;
}
@Override
public Node get(int index) {
return nodes.get(index);
}
@Override
public int size() {
return nodes.size();
}
@Override
protected NodeList wrap(List values) {
return new NodeList(values);
}
/**
* Transforms this list of nodes into a list of the node's representatives.
*
* @return A list of these node's representatives.
*/
public MethodList> asMethodList() {
List methodDescriptions = new ArrayList(size());
for (Node node : nodes) {
methodDescriptions.add(node.getRepresentative());
}
return new MethodList.Explicit(methodDescriptions);
}
}
/**
* A simple implementation of a method graph.
*/
@EqualsAndHashCode
class Simple implements MethodGraph {
/**
* The nodes represented by this method graph.
*/
private final LinkedHashMap nodes;
/**
* Creates a new simple method graph.
*
* @param nodes The nodes represented by this method graph.
*/
public Simple(LinkedHashMap nodes) {
this.nodes = nodes;
}
/**
* Returns a method graph that contains all of the provided methods as simple nodes.
*
* @param methodDescriptions A list of method descriptions to be represented as simple nodes.
* @return A method graph that represents all of the provided methods as simple nodes.
*/
public static MethodGraph of(List extends MethodDescription> methodDescriptions) {
LinkedHashMap nodes = new LinkedHashMap();
for (MethodDescription methodDescription : methodDescriptions) {
nodes.put(methodDescription.asSignatureToken(), new Node.Simple(methodDescription));
}
return new Simple(nodes);
}
@Override
public Node locate(MethodDescription.SignatureToken token) {
Node node = nodes.get(token);
return node == null
? Node.Unresolved.INSTANCE
: node;
}
@Override
public NodeList listNodes() {
return new NodeList(new ArrayList(nodes.values()));
}
}
}