mirror of
https://github.com/dmulloy2/ProtocolLib.git
synced 2024-12-25 18:47:52 +01:00
Improved the fuzzy reflection system tremendously.
It is now possible to specify exactly what method or field you're looking for, based on a number of different critera such as return value, parameter count or parameter type, exceptions, modifiers, name - all using a very fluent builder syntax.
This commit is contained in:
parent
58017960c9
commit
2f8912a8ae
@ -0,0 +1,164 @@
|
|||||||
|
package com.comphenix.protocol.reflect;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import com.google.common.primitives.Ints;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a matcher for fields, methods, constructors and classes.
|
||||||
|
* <p>
|
||||||
|
* This class should ideally never expose mutable state. Its round number must be immutable.
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public abstract class AbstractFuzzyMatcher<T> implements Comparable<AbstractFuzzyMatcher<T>> {
|
||||||
|
private Integer roundNumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to check class equality.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
protected static class ClassMatcher {
|
||||||
|
/**
|
||||||
|
* Match any class.
|
||||||
|
*/
|
||||||
|
public static final ClassMatcher MATCH_ALL = new ClassMatcher(null, true);
|
||||||
|
|
||||||
|
private final Class<?> matcher;
|
||||||
|
private final boolean useAssignable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new class matcher.
|
||||||
|
* @param matcher - the matching class, or NULL to represent anything.
|
||||||
|
* @param useAssignable - whether or not its acceptible for the input type to be a superclass.
|
||||||
|
*/
|
||||||
|
public ClassMatcher(Class<?> matcher, boolean useAssignable) {
|
||||||
|
this.matcher = matcher;
|
||||||
|
this.useAssignable = useAssignable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if a given class is equivalent.
|
||||||
|
* <p>
|
||||||
|
* If the matcher is NULL, the result will only be TRUE if use assignable is TRUE.
|
||||||
|
* @param input - the input class defined in the source file.
|
||||||
|
* @return TRUE if input is a matcher or a superclass of matcher, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public final boolean isClassEqual(@Nonnull Class<?> input) {
|
||||||
|
if (input == null)
|
||||||
|
throw new IllegalArgumentException("Input class cannot be NULL.");
|
||||||
|
|
||||||
|
// Do our checking
|
||||||
|
if (matcher == null)
|
||||||
|
return useAssignable;
|
||||||
|
else if (useAssignable)
|
||||||
|
return input.isAssignableFrom(matcher); // matcher instanceof input
|
||||||
|
else
|
||||||
|
return input.equals(matcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the number of superclasses of the specific class.
|
||||||
|
* <p>
|
||||||
|
* Object is represented as one. All interfaces are one, unless they're derived.
|
||||||
|
* @param clazz - the class to test.
|
||||||
|
* @return The number of superclasses.
|
||||||
|
*/
|
||||||
|
public final int getClassNumber() {
|
||||||
|
Class<?> clazz = matcher;
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
// Move up the hierachy
|
||||||
|
while (clazz != null) {
|
||||||
|
count++;
|
||||||
|
clazz = clazz.getSuperclass();
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the class we're comparing against.
|
||||||
|
* @return Class to compare against.
|
||||||
|
*/
|
||||||
|
public Class<?> getMatcher() {
|
||||||
|
return matcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not its acceptible for the input type to be a superclass.
|
||||||
|
* @return TRUE if it is, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isUseAssignable() {
|
||||||
|
return useAssignable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
if (useAssignable)
|
||||||
|
return "Any " + matcher;
|
||||||
|
else
|
||||||
|
return "Exact " + matcher;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the given value is a match.
|
||||||
|
* @param value - the value to match.
|
||||||
|
* @return TRUE if it is a match, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public abstract boolean isMatch(T value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the round number indicating when this matcher should be applied.
|
||||||
|
* <p>
|
||||||
|
* Matchers with a lower round number are applied before matchers with a higher round number.
|
||||||
|
* <p>
|
||||||
|
* By convention, this round number should be negative, except for zero in the case of a matcher
|
||||||
|
* that accepts any value. A good implementation should return the inverted tree depth (class hierachy)
|
||||||
|
* of the least specified type used in the matching. Thus {@link Integer} will have a lower round number than
|
||||||
|
* {@link Number}.
|
||||||
|
*
|
||||||
|
* @return A number (positive or negative) that is used to order matchers.
|
||||||
|
*/
|
||||||
|
protected abstract int calculateRoundNumber();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the cached round number. This should never change once calculated.
|
||||||
|
* <p>
|
||||||
|
* Matchers with a lower round number are applied before matchers with a higher round number.
|
||||||
|
* @return The round number.
|
||||||
|
* @see {@link #calculateRoundNumber()}
|
||||||
|
*/
|
||||||
|
public final int getRoundNumber() {
|
||||||
|
if (roundNumber == null) {
|
||||||
|
return roundNumber = calculateRoundNumber();
|
||||||
|
} else {
|
||||||
|
return roundNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combine two round numbers by taking the highest non-zero number, or return zero.
|
||||||
|
* @param roundA - the first round number.
|
||||||
|
* @param roundB - the second round number.
|
||||||
|
* @return The combined round number.
|
||||||
|
*/
|
||||||
|
protected final int combineRounds(int roundA, int roundB) {
|
||||||
|
if (roundA == 0)
|
||||||
|
return roundB;
|
||||||
|
else if (roundB == 0)
|
||||||
|
return roundA;
|
||||||
|
else
|
||||||
|
return Math.max(roundA, roundB);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(AbstractFuzzyMatcher<T> obj) {
|
||||||
|
if (obj instanceof AbstractFuzzyMatcher) {
|
||||||
|
AbstractFuzzyMatcher<?> matcher = (AbstractFuzzyMatcher<?>) obj;
|
||||||
|
return Ints.compare(getRoundNumber(), matcher.getRoundNumber());
|
||||||
|
}
|
||||||
|
// No match
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,136 @@
|
|||||||
|
package com.comphenix.protocol.reflect;
|
||||||
|
|
||||||
|
import java.lang.reflect.Member;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a matcher that matches members.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
* @param <T> - type that it matches.
|
||||||
|
*/
|
||||||
|
public abstract class AbstractFuzzyMember<T extends Member> extends AbstractFuzzyMatcher<T> {
|
||||||
|
// Accessibility matchers
|
||||||
|
private int modifiersRequired;
|
||||||
|
private int modifiersBanned;
|
||||||
|
private Pattern nameRegex;
|
||||||
|
private ClassMatcher declaringMatcher = ClassMatcher.MATCH_ALL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not this contract can be modified.
|
||||||
|
*/
|
||||||
|
protected transient boolean sealed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a builder of a fuzzy member contract.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public static abstract class Builder<T extends AbstractFuzzyMember<?>> {
|
||||||
|
protected T member = initialMember();
|
||||||
|
|
||||||
|
public Builder<T> requireModifier(int modifier) {
|
||||||
|
member.modifiersRequired |= modifier;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder<T> banModifier(int modifier) {
|
||||||
|
member.modifiersBanned |= modifier;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder<T> nameRegex(String regex) {
|
||||||
|
member.nameRegex = Pattern.compile(regex);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder<T> nameRegex(Pattern pattern) {
|
||||||
|
member.nameRegex = pattern;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder<T> nameExact(String name) {
|
||||||
|
return nameRegex(Pattern.quote(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder<T> declaringClassExactType(Class<?> declaringClass) {
|
||||||
|
member.declaringMatcher = new ClassMatcher(declaringClass, false);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder<T> declaringClassCanHold(Class<?> declaringClass) {
|
||||||
|
member.declaringMatcher = new ClassMatcher(declaringClass, true);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new instance of the current type.
|
||||||
|
* @return New instance.
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
protected abstract T initialMember();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a new instance of this type.
|
||||||
|
* <p>
|
||||||
|
* Builders should call {@link AbstractFuzzyMember#prepareBuild()} when constructing new objects.
|
||||||
|
* @return New instance of this type.
|
||||||
|
*/
|
||||||
|
public abstract T build();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AbstractFuzzyMember() {
|
||||||
|
// Only allow construction through the builder
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called before a builder is building a member and copying its state.
|
||||||
|
* <p>
|
||||||
|
* Use this to prepare any special values.
|
||||||
|
*/
|
||||||
|
protected void prepareBuild() {
|
||||||
|
// Permit any modifier if we havent's specified anything
|
||||||
|
if (modifiersRequired == 0) {
|
||||||
|
modifiersRequired = Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone a given contract
|
||||||
|
protected AbstractFuzzyMember(AbstractFuzzyMember<T> other) {
|
||||||
|
this.modifiersRequired = other.modifiersRequired;
|
||||||
|
this.modifiersBanned = other.modifiersBanned;
|
||||||
|
this.nameRegex = other.nameRegex;
|
||||||
|
this.declaringMatcher = other.declaringMatcher;
|
||||||
|
this.sealed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMatch(T value) {
|
||||||
|
int mods = value.getModifiers();
|
||||||
|
|
||||||
|
// Match accessibility and name
|
||||||
|
return (mods & modifiersRequired) != 0 &&
|
||||||
|
(mods & modifiersBanned) == 0 &&
|
||||||
|
declaringMatcher.isClassEqual(value.getDeclaringClass()) &&
|
||||||
|
isNameMatch(value.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isNameMatch(String name) {
|
||||||
|
if (nameRegex == null)
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return nameRegex.matcher(name).matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int calculateRoundNumber() {
|
||||||
|
// Sanity check
|
||||||
|
if (!sealed)
|
||||||
|
throw new IllegalStateException("Cannot calculate round number during construction.");
|
||||||
|
|
||||||
|
// NULL is zero
|
||||||
|
return -declaringMatcher.getClassNumber();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,218 @@
|
|||||||
|
package com.comphenix.protocol.reflect;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if a given class implements a given fuzzy (duck typed) contract.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public class FuzzyClassContract extends AbstractFuzzyMatcher<Class<?>> {
|
||||||
|
private final ImmutableList<AbstractFuzzyMatcher<Field>> fieldContracts;
|
||||||
|
private final ImmutableList<AbstractFuzzyMatcher<MethodInfo>> methodContracts;
|
||||||
|
private final ImmutableList<AbstractFuzzyMatcher<MethodInfo>> constructorContracts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a class contract builder.
|
||||||
|
* @author Kristian
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public static class Builder {
|
||||||
|
private List<AbstractFuzzyMatcher<Field>> fieldContracts = Lists.newArrayList();
|
||||||
|
private List<AbstractFuzzyMatcher<MethodInfo>> methodContracts = Lists.newArrayList();
|
||||||
|
private List<AbstractFuzzyMatcher<MethodInfo>> constructorContracts = Lists.newArrayList();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new field contract.
|
||||||
|
* @param matcher - new field contract.
|
||||||
|
* @return This builder, for chaining.
|
||||||
|
*/
|
||||||
|
public Builder field(AbstractFuzzyMatcher<Field> matcher) {
|
||||||
|
fieldContracts.add(matcher);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new field contract via a builder.
|
||||||
|
* @param builder - builder for the new field contract.
|
||||||
|
* @return This builder, for chaining.
|
||||||
|
*/
|
||||||
|
public Builder field(FuzzyFieldContract.Builder builder) {
|
||||||
|
return field(builder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new method contract.
|
||||||
|
* @param matcher - new method contract.
|
||||||
|
* @return This builder, for chaining.
|
||||||
|
*/
|
||||||
|
public Builder method(AbstractFuzzyMatcher<MethodInfo> matcher) {
|
||||||
|
methodContracts.add(matcher);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new method contract via a builder.
|
||||||
|
* @param builder - builder for the new method contract.
|
||||||
|
* @return This builder, for chaining.
|
||||||
|
*/
|
||||||
|
public Builder method(FuzzyMethodContract.Builder builder) {
|
||||||
|
return method(builder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new constructor contract.
|
||||||
|
* @param matcher - new constructor contract.
|
||||||
|
* @return This builder, for chaining.
|
||||||
|
*/
|
||||||
|
public Builder constructor(AbstractFuzzyMatcher<MethodInfo> matcher) {
|
||||||
|
constructorContracts.add(matcher);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new constructor contract via a builder.
|
||||||
|
* @param builder - builder for the new constructor contract.
|
||||||
|
* @return This builder, for chaining.
|
||||||
|
*/
|
||||||
|
public Builder constructor(FuzzyMethodContract.Builder builder) {
|
||||||
|
return constructor(builder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public FuzzyClassContract build() {
|
||||||
|
Collections.sort(fieldContracts);
|
||||||
|
Collections.sort(methodContracts);
|
||||||
|
Collections.sort(constructorContracts);
|
||||||
|
|
||||||
|
// Construct a new class matcher
|
||||||
|
return new FuzzyClassContract(
|
||||||
|
ImmutableList.copyOf(fieldContracts),
|
||||||
|
ImmutableList.copyOf(methodContracts),
|
||||||
|
ImmutableList.copyOf(constructorContracts)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new fuzzy class contract builder.
|
||||||
|
* @return A new builder.
|
||||||
|
*/
|
||||||
|
public static Builder newBuilder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new fuzzy class contract with the given contracts.
|
||||||
|
* @param fieldContracts - field contracts.
|
||||||
|
* @param methodContracts - method contracts.
|
||||||
|
* @param constructorContracts - constructor contracts.
|
||||||
|
*/
|
||||||
|
private FuzzyClassContract(ImmutableList<AbstractFuzzyMatcher<Field>> fieldContracts,
|
||||||
|
ImmutableList<AbstractFuzzyMatcher<MethodInfo>> methodContracts,
|
||||||
|
ImmutableList<AbstractFuzzyMatcher<MethodInfo>> constructorContracts) {
|
||||||
|
super();
|
||||||
|
this.fieldContracts = fieldContracts;
|
||||||
|
this.methodContracts = methodContracts;
|
||||||
|
this.constructorContracts = constructorContracts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve an immutable list of every field contract.
|
||||||
|
* <p>
|
||||||
|
* This list is ordered in descending order of priority.
|
||||||
|
* @return List of every field contract.
|
||||||
|
*/
|
||||||
|
public ImmutableList<AbstractFuzzyMatcher<Field>> getFieldContracts() {
|
||||||
|
return fieldContracts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve an immutable list of every method contract.
|
||||||
|
* <p>
|
||||||
|
* This list is ordered in descending order of priority.
|
||||||
|
* @return List of every method contract.
|
||||||
|
*/
|
||||||
|
public ImmutableList<AbstractFuzzyMatcher<MethodInfo>> getMethodContracts() {
|
||||||
|
return methodContracts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve an immutable list of every constructor contract.
|
||||||
|
* <p>
|
||||||
|
* This list is ordered in descending order of priority.
|
||||||
|
* @return List of every constructor contract.
|
||||||
|
*/
|
||||||
|
public ImmutableList<AbstractFuzzyMatcher<MethodInfo>> getConstructorContracts() {
|
||||||
|
return constructorContracts;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int calculateRoundNumber() {
|
||||||
|
// Find the highest round number
|
||||||
|
return combineRounds(findHighestRound(fieldContracts),
|
||||||
|
combineRounds(findHighestRound(methodContracts),
|
||||||
|
findHighestRound(constructorContracts)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> int findHighestRound(Collection<AbstractFuzzyMatcher<T>> list) {
|
||||||
|
int highest = 0;
|
||||||
|
|
||||||
|
// Go through all the elements
|
||||||
|
for (AbstractFuzzyMatcher<T> matcher : list)
|
||||||
|
highest = combineRounds(highest, matcher.getRoundNumber());
|
||||||
|
return highest;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMatch(Class<?> value) {
|
||||||
|
FuzzyReflection reflection = FuzzyReflection.fromClass(value);
|
||||||
|
|
||||||
|
// Make sure all the contracts are valid
|
||||||
|
return processContracts(reflection.getFields(), fieldContracts) &&
|
||||||
|
processContracts(MethodInfo.fromMethods(reflection.getMethods()), methodContracts) &&
|
||||||
|
processContracts(MethodInfo.fromConstructors(value.getDeclaredConstructors()), constructorContracts);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> boolean processContracts(Collection<T> values, List<AbstractFuzzyMatcher<T>> matchers) {
|
||||||
|
boolean[] accepted = new boolean[matchers.size()];
|
||||||
|
int count = accepted.length;
|
||||||
|
|
||||||
|
// Process every value in turn
|
||||||
|
for (T value : values) {
|
||||||
|
int index = processValue(value, accepted, matchers);
|
||||||
|
|
||||||
|
// See if this worked
|
||||||
|
if (index >= 0) {
|
||||||
|
accepted[index] = true;
|
||||||
|
count--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Break early
|
||||||
|
if (count == 0)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return count == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> int processValue(T value, boolean accepted[], List<AbstractFuzzyMatcher<T>> matchers) {
|
||||||
|
// The order matters
|
||||||
|
for (int i = 0; i < matchers.size(); i++) {
|
||||||
|
if (!accepted[i]) {
|
||||||
|
AbstractFuzzyMatcher<T> matcher = matchers.get(i);
|
||||||
|
|
||||||
|
// Mark this as detected
|
||||||
|
if (matcher.isMatch(value)) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Failure
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,108 @@
|
|||||||
|
package com.comphenix.protocol.reflect;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a field matcher.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public class FuzzyFieldContract extends AbstractFuzzyMember<Field> {
|
||||||
|
private ClassMatcher typeMatcher = ClassMatcher.MATCH_ALL;
|
||||||
|
|
||||||
|
public static class Builder extends AbstractFuzzyMember.Builder<FuzzyFieldContract> {
|
||||||
|
public Builder requireModifier(int modifier) {
|
||||||
|
super.requireModifier(modifier);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder banModifier(int modifier) {
|
||||||
|
super.banModifier(modifier);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder nameRegex(String regex) {
|
||||||
|
super.nameRegex(regex);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder nameRegex(Pattern pattern) {
|
||||||
|
super.nameRegex(pattern);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder nameExact(String name) {
|
||||||
|
super.nameExact(name);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder declaringClassExactType(Class<?> declaringClass) {
|
||||||
|
super.declaringClassExactType(declaringClass);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder declaringClassCanHold(Class<?> declaringClass) {
|
||||||
|
super.declaringClassCanHold(declaringClass);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nonnull
|
||||||
|
protected FuzzyFieldContract initialMember() {
|
||||||
|
return new FuzzyFieldContract();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder typeExact(Class<?> type) {
|
||||||
|
member.typeMatcher = new ClassMatcher(type, false);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder typeCanHold(Class<?> type) {
|
||||||
|
member.typeMatcher = new ClassMatcher(type, true);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FuzzyFieldContract build() {
|
||||||
|
member.prepareBuild();
|
||||||
|
return new FuzzyFieldContract(member);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder newBuilder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private FuzzyFieldContract() {
|
||||||
|
// Only allow construction through the builder
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new field contract from the given contract.
|
||||||
|
* @param other - the contract to clone.
|
||||||
|
*/
|
||||||
|
private FuzzyFieldContract(FuzzyFieldContract other) {
|
||||||
|
super(other);
|
||||||
|
this.typeMatcher = other.typeMatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMatch(Field value) {
|
||||||
|
if (super.isMatch(value)) {
|
||||||
|
return typeMatcher.isClassEqual(value.getType());
|
||||||
|
}
|
||||||
|
// No match
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int calculateRoundNumber() {
|
||||||
|
int current = -typeMatcher.getClassNumber();
|
||||||
|
|
||||||
|
return combineRounds(super.calculateRoundNumber(), current);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,293 @@
|
|||||||
|
package com.comphenix.protocol.reflect;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.NotImplementedException;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a contract for matching methods or constructors.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public class FuzzyMethodContract extends AbstractFuzzyMember<MethodInfo> {
|
||||||
|
private static class ParameterClassMatcher extends AbstractFuzzyMatcher<Class<?>[]> {
|
||||||
|
/**
|
||||||
|
* The expected index.
|
||||||
|
*/
|
||||||
|
private final ClassMatcher typeMatcher;
|
||||||
|
private final Integer indexMatch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new parameter class matcher.
|
||||||
|
* @param typeMatcher - class type matcher.
|
||||||
|
*/
|
||||||
|
public ParameterClassMatcher(@Nonnull ClassMatcher typeMatcher) {
|
||||||
|
this(typeMatcher, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new parameter class matcher.
|
||||||
|
* @param typeMatcher - class type matcher.
|
||||||
|
* @param indexMatch - parameter index to match, or NULL for anything.
|
||||||
|
*/
|
||||||
|
public ParameterClassMatcher(@Nonnull ClassMatcher typeMatcher, Integer indexMatch) {
|
||||||
|
if (typeMatcher == null)
|
||||||
|
throw new IllegalArgumentException("Type matcher cannot be NULL.");
|
||||||
|
|
||||||
|
this.typeMatcher = typeMatcher;
|
||||||
|
this.indexMatch = indexMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See if there's a match for this matcher.
|
||||||
|
* @param used - parameters that have been matched before.
|
||||||
|
* @param params - the type of each parameter.
|
||||||
|
* @return TRUE if this matcher matches any of the given parameters, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isParameterMatch(Class<?> param, int index) {
|
||||||
|
// Make sure the index is valid (or NULL)
|
||||||
|
if (indexMatch == null || indexMatch == index)
|
||||||
|
return typeMatcher.isClassEqual(param);
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMatch(Class<?>[] value) {
|
||||||
|
throw new NotImplementedException("Use the parameter match instead.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int calculateRoundNumber() {
|
||||||
|
return -typeMatcher.getClassNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("{Type: %s, Index: %s}", typeMatcher, indexMatch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match return value
|
||||||
|
private ClassMatcher returnMatcher = ClassMatcher.MATCH_ALL;
|
||||||
|
|
||||||
|
// Handle parameters and exceptions
|
||||||
|
private List<ParameterClassMatcher> paramMatchers = Lists.newArrayList();
|
||||||
|
private List<ParameterClassMatcher> exceptionMatchers = Lists.newArrayList();
|
||||||
|
|
||||||
|
// Expected parameter count
|
||||||
|
private Integer paramCount;
|
||||||
|
|
||||||
|
public static class Builder extends AbstractFuzzyMember.Builder<FuzzyMethodContract> {
|
||||||
|
public Builder requireModifier(int modifier) {
|
||||||
|
super.requireModifier(modifier);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder banModifier(int modifier) {
|
||||||
|
super.banModifier(modifier);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder nameRegex(String regex) {
|
||||||
|
super.nameRegex(regex);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder nameRegex(Pattern pattern) {
|
||||||
|
super.nameRegex(pattern);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder nameExact(String name) {
|
||||||
|
super.nameExact(name);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder declaringClassExactType(Class<?> declaringClass) {
|
||||||
|
super.declaringClassExactType(declaringClass);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder declaringClassCanHold(Class<?> declaringClass) {
|
||||||
|
super.declaringClassCanHold(declaringClass);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder parameterExactType(Class<?> type) {
|
||||||
|
member.paramMatchers.add(new ParameterClassMatcher(new ClassMatcher(type, false)));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder parameterCanHold(Class<?> type) {
|
||||||
|
member.paramMatchers.add(new ParameterClassMatcher(new ClassMatcher(type, true)));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder parameterExactType(Class<?> type, int index) {
|
||||||
|
member.paramMatchers.add(new ParameterClassMatcher(new ClassMatcher(type, false), index));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder parameterCanHold(Class<?> type, int index) {
|
||||||
|
member.paramMatchers.add(new ParameterClassMatcher(new ClassMatcher(type, true), index));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder parameterCount(int expectedCount) {
|
||||||
|
member.paramCount = expectedCount;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder returnTypeVoid() {
|
||||||
|
return returnTypeExact(Void.TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder returnTypeExact(Class<?> type) {
|
||||||
|
member.returnMatcher = new ClassMatcher(type, false);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder returnCanHold(Class<?> type) {
|
||||||
|
member.returnMatcher = new ClassMatcher(type, true);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder exceptionExactType(Class<?> type) {
|
||||||
|
member.exceptionMatchers.add(new ParameterClassMatcher(new ClassMatcher(type, false)));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder exceptionCanHold(Class<?> type) {
|
||||||
|
member.exceptionMatchers.add(new ParameterClassMatcher(new ClassMatcher(type, true)));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder exceptionExactType(Class<?> type, int index) {
|
||||||
|
member.exceptionMatchers.add(new ParameterClassMatcher(new ClassMatcher(type, false), index));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder exceptionCanHold(Class<?> type, int index) {
|
||||||
|
member.exceptionMatchers.add(new ParameterClassMatcher(new ClassMatcher(type, true), index));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nonnull
|
||||||
|
protected FuzzyMethodContract initialMember() {
|
||||||
|
return new FuzzyMethodContract();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FuzzyMethodContract build() {
|
||||||
|
member.prepareBuild();
|
||||||
|
return new FuzzyMethodContract(member);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder newBuilder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private FuzzyMethodContract() {
|
||||||
|
// Only allow construction from the builder
|
||||||
|
}
|
||||||
|
|
||||||
|
private FuzzyMethodContract(FuzzyMethodContract other) {
|
||||||
|
super(other);
|
||||||
|
this.returnMatcher = other.returnMatcher;
|
||||||
|
this.paramMatchers = other.paramMatchers;
|
||||||
|
this.exceptionMatchers = other.exceptionMatchers;
|
||||||
|
this.paramCount = other.paramCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void prepareBuild() {
|
||||||
|
super.prepareBuild();
|
||||||
|
|
||||||
|
// Sort lists such that more specific tests are up front
|
||||||
|
Collections.sort(paramMatchers);
|
||||||
|
Collections.sort(exceptionMatchers);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMatch(MethodInfo value) {
|
||||||
|
if (super.isMatch(value)) {
|
||||||
|
Class<?>[] params = value.getParameterTypes();
|
||||||
|
Class<?>[] exceptions = value.getExceptionTypes();
|
||||||
|
|
||||||
|
if (!returnMatcher.isClassEqual(value.getReturnType()))
|
||||||
|
return false;
|
||||||
|
if (paramCount != null && paramCount != value.getParameterTypes().length)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Finally, check parameters and exceptions
|
||||||
|
return matchParameters(params, paramMatchers) &&
|
||||||
|
matchParameters(exceptions, exceptionMatchers);
|
||||||
|
}
|
||||||
|
// No match
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean matchParameters(Class<?>[] types, List<ParameterClassMatcher> matchers) {
|
||||||
|
boolean[] accepted = new boolean[matchers.size()];
|
||||||
|
int count = accepted.length;
|
||||||
|
|
||||||
|
// Process every parameter in turn
|
||||||
|
for (int i = 0; i < types.length; i++) {
|
||||||
|
int matcherIndex = processValue(types[i], i, accepted, matchers);
|
||||||
|
|
||||||
|
if (matcherIndex >= 0) {
|
||||||
|
accepted[matcherIndex] = true;
|
||||||
|
count--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Break early
|
||||||
|
if (count == 0)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return count == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int processValue(Class<?> value, int index, boolean accepted[], List<ParameterClassMatcher> matchers) {
|
||||||
|
// The order matters
|
||||||
|
for (int i = 0; i < matchers.size(); i++) {
|
||||||
|
if (!accepted[i]) {
|
||||||
|
// See if we got jackpot
|
||||||
|
if (matchers.get(i).isParameterMatch(value, index)) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Failure
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int calculateRoundNumber() {
|
||||||
|
int current = 0;
|
||||||
|
|
||||||
|
// Consider the return value first
|
||||||
|
current = -returnMatcher.getClassNumber();
|
||||||
|
|
||||||
|
// Handle parameters
|
||||||
|
for (ParameterClassMatcher matcher : paramMatchers) {
|
||||||
|
current = combineRounds(current, matcher.calculateRoundNumber());
|
||||||
|
}
|
||||||
|
// And exceptions
|
||||||
|
for (ParameterClassMatcher matcher : exceptionMatchers) {
|
||||||
|
current = combineRounds(current, matcher.calculateRoundNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
return combineRounds(super.calculateRoundNumber(), current);
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package com.comphenix.protocol.reflect;
|
package com.comphenix.protocol.reflect;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -26,6 +27,8 @@ import java.util.List;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves fields and methods by signature, not just name.
|
* Retrieves fields and methods by signature, not just name.
|
||||||
*
|
*
|
||||||
@ -89,6 +92,42 @@ public class FuzzyReflection {
|
|||||||
return source;
|
return source;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the first method that matches.
|
||||||
|
* <p>
|
||||||
|
* ForceAccess must be TRUE in order for this method to access private, protected and package level method.
|
||||||
|
* @param matcher - the matcher to use.
|
||||||
|
* @return The first method that satisfies the given matcher.
|
||||||
|
* @throws IllegalArgumentException If the method cannot be found.
|
||||||
|
*/
|
||||||
|
public Method getMethod(AbstractFuzzyMatcher<MethodInfo> matcher) {
|
||||||
|
List<Method> result = getMethodList(matcher);
|
||||||
|
|
||||||
|
if (result.size() > 0)
|
||||||
|
return result.get(0);
|
||||||
|
else
|
||||||
|
throw new IllegalArgumentException("Unable to find a method that matches " + matcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a list of every method that matches the given matcher.
|
||||||
|
* <p>
|
||||||
|
* ForceAccess must be TRUE in order for this method to access private, protected and package level methods.
|
||||||
|
* @param matcher - the matcher to apply.
|
||||||
|
* @return List of found methods.
|
||||||
|
*/
|
||||||
|
public List<Method> getMethodList(AbstractFuzzyMatcher<MethodInfo> matcher) {
|
||||||
|
List<Method> methods = Lists.newArrayList();
|
||||||
|
|
||||||
|
// Add all matching fields to the list
|
||||||
|
for (Method method : getMethods()) {
|
||||||
|
if (matcher.isMatch(MethodInfo.fromMethod(method))) {
|
||||||
|
methods.add(method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return methods;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a method by looking at its name.
|
* Retrieves a method by looking at its name.
|
||||||
* @param nameRegex - regular expression that will match method names.
|
* @param nameRegex - regular expression that will match method names.
|
||||||
@ -96,7 +135,6 @@ public class FuzzyReflection {
|
|||||||
* @throws IllegalArgumentException If the method cannot be found.
|
* @throws IllegalArgumentException If the method cannot be found.
|
||||||
*/
|
*/
|
||||||
public Method getMethodByName(String nameRegex) {
|
public Method getMethodByName(String nameRegex) {
|
||||||
|
|
||||||
Pattern match = Pattern.compile(nameRegex);
|
Pattern match = Pattern.compile(nameRegex);
|
||||||
|
|
||||||
for (Method method : getMethods()) {
|
for (Method method : getMethods()) {
|
||||||
@ -118,7 +156,6 @@ public class FuzzyReflection {
|
|||||||
* @throws IllegalArgumentException If the method cannot be found.
|
* @throws IllegalArgumentException If the method cannot be found.
|
||||||
*/
|
*/
|
||||||
public Method getMethodByParameters(String name, Class<?>... args) {
|
public Method getMethodByParameters(String name, Class<?>... args) {
|
||||||
|
|
||||||
// Find the correct method to call
|
// Find the correct method to call
|
||||||
for (Method method : getMethods()) {
|
for (Method method : getMethods()) {
|
||||||
if (Arrays.equals(method.getParameterTypes(), args)) {
|
if (Arrays.equals(method.getParameterTypes(), args)) {
|
||||||
@ -159,7 +196,6 @@ public class FuzzyReflection {
|
|||||||
* @throws IllegalArgumentException If the method cannot be found.
|
* @throws IllegalArgumentException If the method cannot be found.
|
||||||
*/
|
*/
|
||||||
public Method getMethodByParameters(String name, String returnTypeRegex, String[] argsRegex) {
|
public Method getMethodByParameters(String name, String returnTypeRegex, String[] argsRegex) {
|
||||||
|
|
||||||
Pattern match = Pattern.compile(returnTypeRegex);
|
Pattern match = Pattern.compile(returnTypeRegex);
|
||||||
Pattern[] argMatch = new Pattern[argsRegex.length];
|
Pattern[] argMatch = new Pattern[argsRegex.length];
|
||||||
|
|
||||||
@ -199,7 +235,6 @@ public class FuzzyReflection {
|
|||||||
* @return Every method that satisfies the given constraints.
|
* @return Every method that satisfies the given constraints.
|
||||||
*/
|
*/
|
||||||
public List<Method> getMethodListByParameters(Class<?> returnType, Class<?>[] args) {
|
public List<Method> getMethodListByParameters(Class<?> returnType, Class<?>[] args) {
|
||||||
|
|
||||||
List<Method> methods = new ArrayList<Method>();
|
List<Method> methods = new ArrayList<Method>();
|
||||||
|
|
||||||
// Find the correct method to call
|
// Find the correct method to call
|
||||||
@ -274,6 +309,42 @@ public class FuzzyReflection {
|
|||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the first field that matches.
|
||||||
|
* <p>
|
||||||
|
* ForceAccess must be TRUE in order for this method to access private, protected and package level fields.
|
||||||
|
* @param matcher - the matcher to use.
|
||||||
|
* @return The first method that satisfies the given matcher.
|
||||||
|
* @throws IllegalArgumentException If the method cannot be found.
|
||||||
|
*/
|
||||||
|
public Field getField(AbstractFuzzyMatcher<Field> matcher) {
|
||||||
|
List<Field> result = getFieldList(matcher);
|
||||||
|
|
||||||
|
if (result.size() > 0)
|
||||||
|
return result.get(0);
|
||||||
|
else
|
||||||
|
throw new IllegalArgumentException("Unable to find a field that matches " + matcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a list of every field that matches the given matcher.
|
||||||
|
* <p>
|
||||||
|
* ForceAccess must be TRUE in order for this method to access private, protected and package level fields.
|
||||||
|
* @param matcher - the matcher to apply.
|
||||||
|
* @return List of found fields.
|
||||||
|
*/
|
||||||
|
public List<Field> getFieldList(AbstractFuzzyMatcher<Field> matcher) {
|
||||||
|
List<Field> fields = Lists.newArrayList();
|
||||||
|
|
||||||
|
// Add all matching fields to the list
|
||||||
|
for (Field field : getFields()) {
|
||||||
|
if (matcher.isMatch(field)) {
|
||||||
|
fields.add(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a field by type.
|
* Retrieves a field by type.
|
||||||
* <p>
|
* <p>
|
||||||
@ -336,8 +407,46 @@ public class FuzzyReflection {
|
|||||||
typeRegex + " in " + source.getName());
|
typeRegex + " in " + source.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the first constructor that matches.
|
||||||
|
* <p>
|
||||||
|
* ForceAccess must be TRUE in order for this method to access private, protected and package level constructors.
|
||||||
|
* @param matcher - the matcher to use.
|
||||||
|
* @return The first constructor that satisfies the given matcher.
|
||||||
|
* @throws IllegalArgumentException If the constructor cannot be found.
|
||||||
|
*/
|
||||||
|
public Constructor<?> getConstructor(AbstractFuzzyMatcher<MethodInfo> matcher) {
|
||||||
|
List<Constructor<?>> result = getConstructorList(matcher);
|
||||||
|
|
||||||
|
if (result.size() > 0)
|
||||||
|
return result.get(0);
|
||||||
|
else
|
||||||
|
throw new IllegalArgumentException("Unable to find a method that matches " + matcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a list of every constructor that matches the given matcher.
|
||||||
|
* <p>
|
||||||
|
* ForceAccess must be TRUE in order for this method to access private, protected and package level constructors.
|
||||||
|
* @param matcher - the matcher to apply.
|
||||||
|
* @return List of found constructors.
|
||||||
|
*/
|
||||||
|
public List<Constructor<?>> getConstructorList(AbstractFuzzyMatcher<MethodInfo> matcher) {
|
||||||
|
List<Constructor<?>> constructors = Lists.newArrayList();
|
||||||
|
|
||||||
|
// Add all matching fields to the list
|
||||||
|
for (Constructor<?> constructor : getConstructors()) {
|
||||||
|
if (matcher.isMatch(MethodInfo.fromConstructor(constructor))) {
|
||||||
|
constructors.add(constructor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return constructors;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves all private and public fields in declared order (after JDK 1.5).
|
* Retrieves all private and public fields in declared order (after JDK 1.5).
|
||||||
|
* <p>
|
||||||
|
* Private, protected and package fields are ignored if forceAccess is FALSE.
|
||||||
* @return Every field.
|
* @return Every field.
|
||||||
*/
|
*/
|
||||||
public Set<Field> getFields() {
|
public Set<Field> getFields() {
|
||||||
@ -350,6 +459,8 @@ public class FuzzyReflection {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves all private and public methods in declared order (after JDK 1.5).
|
* Retrieves all private and public methods in declared order (after JDK 1.5).
|
||||||
|
* <p>
|
||||||
|
* Private, protected and package methods are ignored if forceAccess is FALSE.
|
||||||
* @return Every method.
|
* @return Every method.
|
||||||
*/
|
*/
|
||||||
public Set<Method> getMethods() {
|
public Set<Method> getMethods() {
|
||||||
@ -360,6 +471,19 @@ public class FuzzyReflection {
|
|||||||
return setUnion(source.getMethods());
|
return setUnion(source.getMethods());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves all private and public constructors in declared order (after JDK 1.5).
|
||||||
|
* <p>
|
||||||
|
* Private, protected and package constructors are ignored if forceAccess is FALSE.
|
||||||
|
* @return Every constructor.
|
||||||
|
*/
|
||||||
|
public Set<Constructor<?>> getConstructors() {
|
||||||
|
if (forceAccess)
|
||||||
|
return setUnion(source.getDeclaredConstructors());
|
||||||
|
else
|
||||||
|
return setUnion(source.getConstructors());
|
||||||
|
}
|
||||||
|
|
||||||
// Prevent duplicate fields
|
// Prevent duplicate fields
|
||||||
private static <T> Set<T> setUnion(T[]... array) {
|
private static <T> Set<T> setUnion(T[]... array) {
|
||||||
Set<T> result = new LinkedHashSet<T>();
|
Set<T> result = new LinkedHashSet<T>();
|
||||||
|
@ -0,0 +1,230 @@
|
|||||||
|
package com.comphenix.protocol.reflect;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.GenericDeclaration;
|
||||||
|
import java.lang.reflect.Member;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.TypeVariable;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.NotImplementedException;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a method or a constructor.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public abstract class MethodInfo implements GenericDeclaration, Member {
|
||||||
|
/**
|
||||||
|
* Wraps a method as a MethodInfo object.
|
||||||
|
* @param method - the method to wrap.
|
||||||
|
* @return The wrapped method.
|
||||||
|
*/
|
||||||
|
public static MethodInfo fromMethod(final Method method) {
|
||||||
|
return new MethodInfo() {
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return method.getName();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Class<?>[] getParameterTypes() {
|
||||||
|
return method.getParameterTypes();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Class<?> getDeclaringClass() {
|
||||||
|
return method.getDeclaringClass();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Class<?> getReturnType() {
|
||||||
|
return method.getReturnType();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public int getModifiers() {
|
||||||
|
return method.getModifiers();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Class<?>[] getExceptionTypes() {
|
||||||
|
return method.getExceptionTypes();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public TypeVariable<?>[] getTypeParameters() {
|
||||||
|
return method.getTypeParameters();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public String toGenericString() {
|
||||||
|
return method.toGenericString();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return method.toString();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public boolean isSynthetic() {
|
||||||
|
return method.isSynthetic();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return method.hashCode();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public boolean isConstructor() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a list of method infos from a given array of methods.
|
||||||
|
* @param methods - array of methods.
|
||||||
|
* @return Method info list.
|
||||||
|
*/
|
||||||
|
public static Collection<MethodInfo> fromMethods(Method[] methods) {
|
||||||
|
return fromMethods(Arrays.asList(methods));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a list of method infos from a given collection of methods.
|
||||||
|
* @param methods - list of methods.
|
||||||
|
* @return Method info list.
|
||||||
|
*/
|
||||||
|
public static List<MethodInfo> fromMethods(Collection<Method> methods) {
|
||||||
|
List<MethodInfo> infos = Lists.newArrayList();
|
||||||
|
|
||||||
|
for (Method method : methods)
|
||||||
|
infos.add(fromMethod(method));
|
||||||
|
return infos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps a constructor as a method information object.
|
||||||
|
* @param constructor - the constructor to wrap.
|
||||||
|
* @return A wrapped constructor.
|
||||||
|
*/
|
||||||
|
public static MethodInfo fromConstructor(final Constructor<?> constructor) {
|
||||||
|
return new MethodInfo() {
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return constructor.getName();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Class<?>[] getParameterTypes() {
|
||||||
|
return constructor.getParameterTypes();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Class<?> getDeclaringClass() {
|
||||||
|
return constructor.getDeclaringClass();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Class<?> getReturnType() {
|
||||||
|
return Void.class;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public int getModifiers() {
|
||||||
|
return constructor.getModifiers();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Class<?>[] getExceptionTypes() {
|
||||||
|
return constructor.getExceptionTypes();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public TypeVariable<?>[] getTypeParameters() {
|
||||||
|
return constructor.getTypeParameters();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public String toGenericString() {
|
||||||
|
return constructor.toGenericString();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return constructor.toString();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public boolean isSynthetic() {
|
||||||
|
return constructor.isSynthetic();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return constructor.hashCode();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public boolean isConstructor() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a list of method infos from a given array of constructors.
|
||||||
|
* @param constructors - array of constructors.
|
||||||
|
* @return Method info list.
|
||||||
|
*/
|
||||||
|
public static Collection<MethodInfo> fromConstructors(Constructor<?>[] constructors) {
|
||||||
|
return fromConstructors(Arrays.asList(constructors));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a list of method infos from a given collection of constructors.
|
||||||
|
* @param constructors - list of constructors.
|
||||||
|
* @return Method info list.
|
||||||
|
*/
|
||||||
|
public static List<MethodInfo> fromConstructors(Collection<Constructor<?>> constructors) {
|
||||||
|
List<MethodInfo> infos = Lists.newArrayList();
|
||||||
|
|
||||||
|
for (Constructor<?> constructor : constructors)
|
||||||
|
infos.add(fromConstructor(constructor));
|
||||||
|
return infos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string describing this method or constructor
|
||||||
|
* @return A string representation of the object.
|
||||||
|
* @see {@link Method#toString()} or {@link Constructor#toString()}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string describing this method or constructor, including type parameters.
|
||||||
|
* @return A string describing this Method, include type parameters
|
||||||
|
* @see {@link Method#toGenericString()} or {@link Constructor#toGenericString()}
|
||||||
|
*/
|
||||||
|
public abstract String toGenericString();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of Class objects that represent the types of the exceptions declared to be thrown by the
|
||||||
|
* underlying method or constructor represented by this MethodInfo object.
|
||||||
|
* @return The exception types declared as being thrown by the method or constructor this object represents.
|
||||||
|
* @see {@link Method#getExceptionTypes()} or {@link Constructor#getExceptionTypes()}
|
||||||
|
*/
|
||||||
|
public abstract Class<?>[] getExceptionTypes();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Class object that represents the formal return type of the method or constructor
|
||||||
|
* represented by this MethodInfo object.
|
||||||
|
* <p>
|
||||||
|
* This is always {@link Void} for constructors.
|
||||||
|
* @return The return value, or Void if a constructor.
|
||||||
|
* @see {@link Method#getReturnType()}
|
||||||
|
*/
|
||||||
|
public abstract Class<?> getReturnType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of Class objects that represent the formal parameter types, in declaration order,
|
||||||
|
* of the method or constructor represented by this MethodInfo object.
|
||||||
|
* @return The parameter types for the method or constructor this object represents.
|
||||||
|
* @see {@link Method#getParameterTypes()} or {@link Constructor#getParameterTypes()}
|
||||||
|
*/
|
||||||
|
public abstract Class<?>[] getParameterTypes();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if this is a constructor or not.
|
||||||
|
* @return TRUE if this represents a constructor, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public abstract boolean isConstructor();
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user