From 2f8912a8aef724b33f416b6f233b0aed1012c0ce Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Wed, 30 Jan 2013 05:08:38 +0100 Subject: [PATCH] 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. --- .../reflect/AbstractFuzzyMatcher.java | 164 ++++++++++ .../protocol/reflect/AbstractFuzzyMember.java | 136 ++++++++ .../protocol/reflect/FuzzyClassContract.java | 218 +++++++++++++ .../protocol/reflect/FuzzyFieldContract.java | 108 +++++++ .../protocol/reflect/FuzzyMethodContract.java | 293 ++++++++++++++++++ .../protocol/reflect/FuzzyReflection.java | 132 +++++++- .../protocol/reflect/MethodInfo.java | 230 ++++++++++++++ 7 files changed, 1277 insertions(+), 4 deletions(-) create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/reflect/AbstractFuzzyMatcher.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/reflect/AbstractFuzzyMember.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/reflect/FuzzyClassContract.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/reflect/FuzzyFieldContract.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/reflect/FuzzyMethodContract.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/reflect/MethodInfo.java diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/AbstractFuzzyMatcher.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/AbstractFuzzyMatcher.java new file mode 100644 index 00000000..c16ed506 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/AbstractFuzzyMatcher.java @@ -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. + *

+ * This class should ideally never expose mutable state. Its round number must be immutable. + * @author Kristian + */ +public abstract class AbstractFuzzyMatcher implements Comparable> { + 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(); +}