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:
Kristian S. Stangeland 2013-01-30 05:08:38 +01:00
parent 58017960c9
commit 2f8912a8ae
7 changed files with 1277 additions and 4 deletions

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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>();

View File

@ -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();
}