> {
+ 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * Matchers with a lower round number are applied before matchers with a higher round number.
+ *
+ * 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.
+ *
+ * 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 obj) {
+ if (obj instanceof AbstractFuzzyMatcher) {
+ AbstractFuzzyMatcher> matcher = (AbstractFuzzyMatcher>) obj;
+ return Ints.compare(getRoundNumber(), matcher.getRoundNumber());
+ }
+ // No match
+ return -1;
+ }
+}
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/AbstractFuzzyMember.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/AbstractFuzzyMember.java
new file mode 100644
index 00000000..32c23d9b
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/AbstractFuzzyMember.java
@@ -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 - type that it matches.
+ */
+public abstract class AbstractFuzzyMember extends AbstractFuzzyMatcher {
+ // 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> {
+ protected T member = initialMember();
+
+ public Builder requireModifier(int modifier) {
+ member.modifiersRequired |= modifier;
+ return this;
+ }
+
+ public Builder banModifier(int modifier) {
+ member.modifiersBanned |= modifier;
+ return this;
+ }
+
+ public Builder nameRegex(String regex) {
+ member.nameRegex = Pattern.compile(regex);
+ return this;
+ }
+
+ public Builder nameRegex(Pattern pattern) {
+ member.nameRegex = pattern;
+ return this;
+ }
+
+ public Builder nameExact(String name) {
+ return nameRegex(Pattern.quote(name));
+ }
+
+ public Builder declaringClassExactType(Class> declaringClass) {
+ member.declaringMatcher = new ClassMatcher(declaringClass, false);
+ return this;
+ }
+
+ public Builder 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.
+ *
+ * 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.
+ *
+ * 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 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();
+ }
+}
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/FuzzyClassContract.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/FuzzyClassContract.java
new file mode 100644
index 00000000..f6a0c73c
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/FuzzyClassContract.java
@@ -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> {
+ private final ImmutableList> fieldContracts;
+ private final ImmutableList> methodContracts;
+ private final ImmutableList> constructorContracts;
+
+ /**
+ * Represents a class contract builder.
+ * @author Kristian
+ *
+ */
+ public static class Builder {
+ private List> fieldContracts = Lists.newArrayList();
+ private List> methodContracts = Lists.newArrayList();
+ private List> constructorContracts = Lists.newArrayList();
+
+ /**
+ * Add a new field contract.
+ * @param matcher - new field contract.
+ * @return This builder, for chaining.
+ */
+ public Builder field(AbstractFuzzyMatcher 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 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 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> fieldContracts,
+ ImmutableList> methodContracts,
+ ImmutableList> constructorContracts) {
+ super();
+ this.fieldContracts = fieldContracts;
+ this.methodContracts = methodContracts;
+ this.constructorContracts = constructorContracts;
+ }
+
+ /**
+ * Retrieve an immutable list of every field contract.
+ *
+ * This list is ordered in descending order of priority.
+ * @return List of every field contract.
+ */
+ public ImmutableList> getFieldContracts() {
+ return fieldContracts;
+ }
+
+ /**
+ * Retrieve an immutable list of every method contract.
+ *
+ * This list is ordered in descending order of priority.
+ * @return List of every method contract.
+ */
+ public ImmutableList> getMethodContracts() {
+ return methodContracts;
+ }
+
+ /**
+ * Retrieve an immutable list of every constructor contract.
+ *
+ * This list is ordered in descending order of priority.
+ * @return List of every constructor contract.
+ */
+ public ImmutableList> getConstructorContracts() {
+ return constructorContracts;
+ }
+
+ @Override
+ protected int calculateRoundNumber() {
+ // Find the highest round number
+ return combineRounds(findHighestRound(fieldContracts),
+ combineRounds(findHighestRound(methodContracts),
+ findHighestRound(constructorContracts)));
+ }
+
+ private int findHighestRound(Collection> list) {
+ int highest = 0;
+
+ // Go through all the elements
+ for (AbstractFuzzyMatcher 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 boolean processContracts(Collection values, List> 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 int processValue(T value, boolean accepted[], List> matchers) {
+ // The order matters
+ for (int i = 0; i < matchers.size(); i++) {
+ if (!accepted[i]) {
+ AbstractFuzzyMatcher matcher = matchers.get(i);
+
+ // Mark this as detected
+ if (matcher.isMatch(value)) {
+ return i;
+ }
+ }
+ }
+
+ // Failure
+ return -1;
+ }
+}
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/FuzzyFieldContract.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/FuzzyFieldContract.java
new file mode 100644
index 00000000..cc80f7d0
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/FuzzyFieldContract.java
@@ -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 {
+ private ClassMatcher typeMatcher = ClassMatcher.MATCH_ALL;
+
+ public static class Builder extends AbstractFuzzyMember.Builder {
+ 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);
+ }
+}
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/FuzzyMethodContract.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/FuzzyMethodContract.java
new file mode 100644
index 00000000..71ac44f2
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/FuzzyMethodContract.java
@@ -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 {
+ private static class ParameterClassMatcher extends AbstractFuzzyMatcher[]> {
+ /**
+ * 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 paramMatchers = Lists.newArrayList();
+ private List exceptionMatchers = Lists.newArrayList();
+
+ // Expected parameter count
+ private Integer paramCount;
+
+ public static class Builder extends AbstractFuzzyMember.Builder {
+ 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 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 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);
+ }
+}
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/FuzzyReflection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/FuzzyReflection.java
index 4c8cd1bf..2ae04ddb 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/FuzzyReflection.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/FuzzyReflection.java
@@ -17,6 +17,7 @@
package com.comphenix.protocol.reflect;
+import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
@@ -26,6 +27,8 @@ import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
+import com.google.common.collect.Lists;
+
/**
* Retrieves fields and methods by signature, not just name.
*
@@ -89,6 +92,42 @@ public class FuzzyReflection {
return source;
}
+ /**
+ * Retrieve the first method that matches.
+ *
+ * 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 matcher) {
+ List 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.
+ *
+ * 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 getMethodList(AbstractFuzzyMatcher matcher) {
+ List 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.
* @param nameRegex - regular expression that will match method names.
@@ -96,7 +135,6 @@ public class FuzzyReflection {
* @throws IllegalArgumentException If the method cannot be found.
*/
public Method getMethodByName(String nameRegex) {
-
Pattern match = Pattern.compile(nameRegex);
for (Method method : getMethods()) {
@@ -118,7 +156,6 @@ public class FuzzyReflection {
* @throws IllegalArgumentException If the method cannot be found.
*/
public Method getMethodByParameters(String name, Class>... args) {
-
// Find the correct method to call
for (Method method : getMethods()) {
if (Arrays.equals(method.getParameterTypes(), args)) {
@@ -159,7 +196,6 @@ public class FuzzyReflection {
* @throws IllegalArgumentException If the method cannot be found.
*/
public Method getMethodByParameters(String name, String returnTypeRegex, String[] argsRegex) {
-
Pattern match = Pattern.compile(returnTypeRegex);
Pattern[] argMatch = new Pattern[argsRegex.length];
@@ -199,7 +235,6 @@ public class FuzzyReflection {
* @return Every method that satisfies the given constraints.
*/
public List getMethodListByParameters(Class> returnType, Class>[] args) {
-
List methods = new ArrayList();
// Find the correct method to call
@@ -274,6 +309,42 @@ public class FuzzyReflection {
return fields;
}
+ /**
+ * Retrieve the first field that matches.
+ *
+ * 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 matcher) {
+ List 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.
+ *
+ * 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 getFieldList(AbstractFuzzyMatcher matcher) {
+ List 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.
*
@@ -336,8 +407,46 @@ public class FuzzyReflection {
typeRegex + " in " + source.getName());
}
+ /**
+ * Retrieve the first constructor that matches.
+ *
+ * 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 matcher) {
+ List> 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.
+ *
+ * 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> getConstructorList(AbstractFuzzyMatcher matcher) {
+ List> 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).
+ *
+ * Private, protected and package fields are ignored if forceAccess is FALSE.
* @return Every field.
*/
public Set getFields() {
@@ -350,6 +459,8 @@ public class FuzzyReflection {
/**
* Retrieves all private and public methods in declared order (after JDK 1.5).
+ *
+ * Private, protected and package methods are ignored if forceAccess is FALSE.
* @return Every method.
*/
public Set getMethods() {
@@ -360,6 +471,19 @@ public class FuzzyReflection {
return setUnion(source.getMethods());
}
+ /**
+ * Retrieves all private and public constructors in declared order (after JDK 1.5).
+ *
+ * Private, protected and package constructors are ignored if forceAccess is FALSE.
+ * @return Every constructor.
+ */
+ public Set> getConstructors() {
+ if (forceAccess)
+ return setUnion(source.getDeclaredConstructors());
+ else
+ return setUnion(source.getConstructors());
+ }
+
// Prevent duplicate fields
private static Set setUnion(T[]... array) {
Set result = new LinkedHashSet();
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/MethodInfo.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/MethodInfo.java
new file mode 100644
index 00000000..73fdea0b
--- /dev/null
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/MethodInfo.java
@@ -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 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 fromMethods(Collection methods) {
+ List 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 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 fromConstructors(Collection> constructors) {
+ List 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.
+ *
+ * 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();
+}