Improve JavaDoc and add the ability to match declaring class.

This commit is contained in:
Kristian S. Stangeland 2013-01-30 18:44:13 +01:00
parent 2f8912a8ae
commit 8b12907dfb
7 changed files with 453 additions and 157 deletions

View File

@ -1,7 +1,5 @@
package com.comphenix.protocol.reflect; package com.comphenix.protocol.reflect;
import javax.annotation.Nonnull;
import com.google.common.primitives.Ints; import com.google.common.primitives.Ints;
/** /**
@ -13,100 +11,13 @@ import com.google.common.primitives.Ints;
public abstract class AbstractFuzzyMatcher<T> implements Comparable<AbstractFuzzyMatcher<T>> { public abstract class AbstractFuzzyMatcher<T> implements Comparable<AbstractFuzzyMatcher<T>> {
private Integer roundNumber; 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. * Determine if the given value is a match.
* @param value - the value to match. * @param value - the value to match.
* @param parent - the parent container, or NULL if this value is the root.
* @return TRUE if it is a match, FALSE otherwise. * @return TRUE if it is a match, FALSE otherwise.
*/ */
public abstract boolean isMatch(T value); public abstract boolean isMatch(T value, Object parent);
/** /**
* Calculate the round number indicating when this matcher should be applied. * Calculate the round number indicating when this matcher should be applied.

View File

@ -15,8 +15,9 @@ public abstract class AbstractFuzzyMember<T extends Member> extends AbstractFuzz
// Accessibility matchers // Accessibility matchers
private int modifiersRequired; private int modifiersRequired;
private int modifiersBanned; private int modifiersBanned;
private Pattern nameRegex; private Pattern nameRegex;
private ClassMatcher declaringMatcher = ClassMatcher.MATCH_ALL; private AbstractFuzzyMatcher<Class<?>> declaringMatcher = ExactClassMatcher.MATCH_ALL;
/** /**
* Whether or not this contract can be modified. * Whether or not this contract can be modified.
@ -31,37 +32,94 @@ public abstract class AbstractFuzzyMember<T extends Member> extends AbstractFuzz
public static abstract class Builder<T extends AbstractFuzzyMember<?>> { public static abstract class Builder<T extends AbstractFuzzyMember<?>> {
protected T member = initialMember(); protected T member = initialMember();
/**
* Add a given bit-field of required modifiers for every matching member.
* @param modifier - bit-field of modifiers that are required.
* @return This builder, for chaining.
*/
public Builder<T> requireModifier(int modifier) { public Builder<T> requireModifier(int modifier) {
member.modifiersRequired |= modifier; member.modifiersRequired |= modifier;
return this; return this;
} }
/**
* Add a given bit-field of modifers that will skip or ignore members.
* @param modifier - bit-field of modifiers to skip or ignore.
* @return This builder, for chaining.
*/
public Builder<T> banModifier(int modifier) { public Builder<T> banModifier(int modifier) {
member.modifiersBanned |= modifier; member.modifiersBanned |= modifier;
return this; return this;
} }
/**
* Set the regular expresson that matches a members name.
* @param regex - new regular expression of valid names.
* @return This builder, for chaining.
*/
public Builder<T> nameRegex(String regex) { public Builder<T> nameRegex(String regex) {
member.nameRegex = Pattern.compile(regex); member.nameRegex = Pattern.compile(regex);
return this; return this;
} }
/**
* Set the regular expression pattern that matches a members name.
* @param pattern - regular expression pattern for a valid name.
* @return This builder, for chaining.
*/
public Builder<T> nameRegex(Pattern pattern) { public Builder<T> nameRegex(Pattern pattern) {
member.nameRegex = pattern; member.nameRegex = pattern;
return this; return this;
} }
/**
* Set the exact name of the member we are matching.
* <p<
* This will overwrite the regular expression rule.
* @param name - exact name.
* @return This builder, for chaining.
*/
public Builder<T> nameExact(String name) { public Builder<T> nameExact(String name) {
return nameRegex(Pattern.quote(name)); return nameRegex(Pattern.quote(name));
} }
/**
* Require that a member is defined by this exact class.
* @param declaringClass - the declaring class of any matching member.
* @return This builder, for chaining.
*/
public Builder<T> declaringClassExactType(Class<?> declaringClass) { public Builder<T> declaringClassExactType(Class<?> declaringClass) {
member.declaringMatcher = new ClassMatcher(declaringClass, false); member.declaringMatcher = ExactClassMatcher.matchExact(declaringClass);
return this; return this;
} }
public Builder<T> declaringClassCanHold(Class<?> declaringClass) { /**
member.declaringMatcher = new ClassMatcher(declaringClass, true); * Require that a member is defined by this exact class, or any super class.
* @param declaringClass - the declaring class.
* @return This builder, for chaining.
*/
public Builder<T> declaringClassSuperOf(Class<?> declaringClass) {
member.declaringMatcher = ExactClassMatcher.matchSuper(declaringClass);
return this;
}
/**
* Require that a member is defined by this exact class, or any super class.
* @param declaringClass - the declaring class.
* @return This builder, for chaining.
*/
public Builder<T> declaringClassDerivedOf(Class<?> declaringClass) {
member.declaringMatcher = ExactClassMatcher.matchDerived(declaringClass);
return this;
}
/**
* Require that a member is defined by a class that matches the given matcher.
* @param classMatcher - class matcher.
* @return This builder, for chaining.
*/
public Builder<T> declaringClassMatching(AbstractFuzzyMatcher<Class<?>> classMatcher) {
member.declaringMatcher = classMatcher;
return this; return this;
} }
@ -107,13 +165,13 @@ public abstract class AbstractFuzzyMember<T extends Member> extends AbstractFuzz
} }
@Override @Override
public boolean isMatch(T value) { public boolean isMatch(T value, Object parent) {
int mods = value.getModifiers(); int mods = value.getModifiers();
// Match accessibility and name // Match accessibility and name
return (mods & modifiersRequired) != 0 && return (mods & modifiersRequired) != 0 &&
(mods & modifiersBanned) == 0 && (mods & modifiersBanned) == 0 &&
declaringMatcher.isClassEqual(value.getDeclaringClass()) && declaringMatcher.isMatch(value.getDeclaringClass(), value) &&
isNameMatch(value.getName()); isNameMatch(value.getName());
} }
@ -131,6 +189,6 @@ public abstract class AbstractFuzzyMember<T extends Member> extends AbstractFuzz
throw new IllegalStateException("Cannot calculate round number during construction."); throw new IllegalStateException("Cannot calculate round number during construction.");
// NULL is zero // NULL is zero
return -declaringMatcher.getClassNumber(); return declaringMatcher.getRoundNumber();
} }
} }

View File

@ -0,0 +1,145 @@
package com.comphenix.protocol.reflect;
/**
* Used to check class equality.
*
* @author Kristian
*/
class ExactClassMatcher extends AbstractFuzzyMatcher<Class<?>> {
/**
* Different matching rules.
*/
enum Options {
/**
* Match classes exactly.
*/
MATCH_EXACT,
/**
* A match if the input class is a superclass of the matcher class, or the same class.
*/
MATCH_SUPER,
/**
* A match if the input class is a derived class of the matcher class, or the same class.
*/
MATCH_DERIVED
}
/**
* Match any class.
*/
public static final ExactClassMatcher MATCH_ALL = new ExactClassMatcher(null, Options.MATCH_SUPER);
private final Class<?> matcher;
private final Options option;
/**
* Construct a class matcher that matches types exactly.
* @param matcher - the matching class.
*/
public static ExactClassMatcher matchExact(Class<?> matcher) {
return new ExactClassMatcher(matcher, Options.MATCH_EXACT);
}
/**
* Construct a class matcher that matches super types of the given class.
* @param matcher - the matching type must be a super class of this type.
* @return A new class mathcher.
*/
public static ExactClassMatcher matchSuper(Class<?> matcher) {
return new ExactClassMatcher(matcher, Options.MATCH_SUPER);
}
/**
* Construct a class matcher that matches derived types of the given class.
* @param matcher - the matching type must be a derived class of this type.
* @return A new class mathcher.
*/
public static ExactClassMatcher matchDerived(Class<?> matcher) {
return new ExactClassMatcher(matcher, Options.MATCH_DERIVED);
}
/**
* Constructs a new class matcher.
* @param matcher - the matching class, or NULL to represent anything.
* @param option - options specifying the matching rules.
*/
private ExactClassMatcher(Class<?> matcher, Options option) {
this.matcher = matcher;
this.option = option;
}
/**
* Determine if a given class is equivalent.
* <p>
* If the matcher is NULL, the result will only be TRUE if we're not matching exactly.
* @param input - the input class defined in the source file.
* @param parent - the container that holds a reference to this class.
* @return TRUE if input matches according to the rules in {@link #getOptions()}, FALSE otherwise.
*/
@Override
public boolean isMatch(Class<?> input, Object parent) {
if (input == null)
throw new IllegalArgumentException("Input class cannot be NULL.");
// Do our checking
if (matcher == null)
return option != Options.MATCH_EXACT;
else if (option == Options.MATCH_SUPER)
return input.isAssignableFrom(matcher); // matcher instanceof input
else if (option == Options.MATCH_DERIVED)
return matcher.isAssignableFrom(input); // input instanceof matcher
else
return input.equals(matcher);
}
@Override
protected int calculateRoundNumber() {
return -getClassNumber(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 static int getClassNumber(Class<?> clazz) {
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;
}
/**
* The matching rules for this class matcher.
* @return The current matching option.
*/
public Options getOptions() {
return option;
}
@Override
public String toString() {
if (option == Options.MATCH_SUPER)
return matcher + " instanceof input";
else if (option == Options.MATCH_DERIVED)
return "input instanceof " + matcher;
else
return "Exact " + matcher;
}
}

View File

@ -1,6 +1,7 @@
package com.comphenix.protocol.reflect; package com.comphenix.protocol.reflect;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -106,6 +107,32 @@ public class FuzzyClassContract extends AbstractFuzzyMatcher<Class<?>> {
return new Builder(); return new Builder();
} }
/**
* Match the parent class of a method, field or constructor.
* @return Parent matcher.
*/
public static AbstractFuzzyMatcher<Class<?>> matchParent() {
return new AbstractFuzzyMatcher<Class<?>>() {
@Override
public boolean isMatch(Class<?> value, Object parent) {
if (parent instanceof Member) {
return ((Member) parent).getDeclaringClass().equals(value);
} else if (parent instanceof Class) {
return parent.equals(value);
} else {
// Can't be a match
return false;
}
}
@Override
protected int calculateRoundNumber() {
// We match a very specific type
return -100;
}
};
}
/** /**
* Constructs a new fuzzy class contract with the given contracts. * Constructs a new fuzzy class contract with the given contracts.
* @param fieldContracts - field contracts. * @param fieldContracts - field contracts.
@ -169,22 +196,22 @@ public class FuzzyClassContract extends AbstractFuzzyMatcher<Class<?>> {
} }
@Override @Override
public boolean isMatch(Class<?> value) { public boolean isMatch(Class<?> value, Object parent) {
FuzzyReflection reflection = FuzzyReflection.fromClass(value); FuzzyReflection reflection = FuzzyReflection.fromClass(value);
// Make sure all the contracts are valid // Make sure all the contracts are valid
return processContracts(reflection.getFields(), fieldContracts) && return processContracts(reflection.getFields(), value, fieldContracts) &&
processContracts(MethodInfo.fromMethods(reflection.getMethods()), methodContracts) && processContracts(MethodInfo.fromMethods(reflection.getMethods()), value, methodContracts) &&
processContracts(MethodInfo.fromConstructors(value.getDeclaredConstructors()), constructorContracts); processContracts(MethodInfo.fromConstructors(value.getDeclaredConstructors()), value, constructorContracts);
} }
private <T> boolean processContracts(Collection<T> values, List<AbstractFuzzyMatcher<T>> matchers) { private <T> boolean processContracts(Collection<T> values, Class<?> parent, List<AbstractFuzzyMatcher<T>> matchers) {
boolean[] accepted = new boolean[matchers.size()]; boolean[] accepted = new boolean[matchers.size()];
int count = accepted.length; int count = accepted.length;
// Process every value in turn // Process every value in turn
for (T value : values) { for (T value : values) {
int index = processValue(value, accepted, matchers); int index = processValue(value, parent, accepted, matchers);
// See if this worked // See if this worked
if (index >= 0) { if (index >= 0) {
@ -199,14 +226,14 @@ public class FuzzyClassContract extends AbstractFuzzyMatcher<Class<?>> {
return count == 0; return count == 0;
} }
private <T> int processValue(T value, boolean accepted[], List<AbstractFuzzyMatcher<T>> matchers) { private <T> int processValue(T value, Class<?> parent, boolean accepted[], List<AbstractFuzzyMatcher<T>> matchers) {
// The order matters // The order matters
for (int i = 0; i < matchers.size(); i++) { for (int i = 0; i < matchers.size(); i++) {
if (!accepted[i]) { if (!accepted[i]) {
AbstractFuzzyMatcher<T> matcher = matchers.get(i); AbstractFuzzyMatcher<T> matcher = matchers.get(i);
// Mark this as detected // Mark this as detected
if (matcher.isMatch(value)) { if (matcher.isMatch(value, parent)) {
return i; return i;
} }
} }

View File

@ -11,29 +11,34 @@ import javax.annotation.Nonnull;
* @author Kristian * @author Kristian
*/ */
public class FuzzyFieldContract extends AbstractFuzzyMember<Field> { public class FuzzyFieldContract extends AbstractFuzzyMember<Field> {
private ClassMatcher typeMatcher = ClassMatcher.MATCH_ALL; private AbstractFuzzyMatcher<Class<?>> typeMatcher = ExactClassMatcher.MATCH_ALL;
public static class Builder extends AbstractFuzzyMember.Builder<FuzzyFieldContract> { public static class Builder extends AbstractFuzzyMember.Builder<FuzzyFieldContract> {
@Override
public Builder requireModifier(int modifier) { public Builder requireModifier(int modifier) {
super.requireModifier(modifier); super.requireModifier(modifier);
return this; return this;
} }
@Override
public Builder banModifier(int modifier) { public Builder banModifier(int modifier) {
super.banModifier(modifier); super.banModifier(modifier);
return this; return this;
} }
@Override
public Builder nameRegex(String regex) { public Builder nameRegex(String regex) {
super.nameRegex(regex); super.nameRegex(regex);
return this; return this;
} }
@Override
public Builder nameRegex(Pattern pattern) { public Builder nameRegex(Pattern pattern) {
super.nameRegex(pattern); super.nameRegex(pattern);
return this; return this;
} }
@Override
public Builder nameExact(String name) { public Builder nameExact(String name) {
super.nameExact(name); super.nameExact(name);
return this; return this;
@ -44,8 +49,21 @@ public class FuzzyFieldContract extends AbstractFuzzyMember<Field> {
return this; return this;
} }
public Builder declaringClassCanHold(Class<?> declaringClass) { @Override
super.declaringClassCanHold(declaringClass); public Builder declaringClassSuperOf(Class<?> declaringClass) {
super.declaringClassSuperOf(declaringClass);
return this;
}
@Override
public Builder declaringClassDerivedOf(Class<?> declaringClass) {
super.declaringClassDerivedOf(declaringClass);
return this;
}
@Override
public Builder declaringClassMatching(AbstractFuzzyMatcher<Class<?>> classMatcher) {
super.declaringClassMatching(classMatcher);
return this; return this;
} }
@ -56,12 +74,12 @@ public class FuzzyFieldContract extends AbstractFuzzyMember<Field> {
} }
public Builder typeExact(Class<?> type) { public Builder typeExact(Class<?> type) {
member.typeMatcher = new ClassMatcher(type, false); member.typeMatcher = ExactClassMatcher.matchExact(type);
return this; return this;
} }
public Builder typeCanHold(Class<?> type) { public Builder typeSuperOf(Class<?> type) {
member.typeMatcher = new ClassMatcher(type, true); member.typeMatcher = ExactClassMatcher.matchSuper(type);
return this; return this;
} }
@ -91,9 +109,9 @@ public class FuzzyFieldContract extends AbstractFuzzyMember<Field> {
} }
@Override @Override
public boolean isMatch(Field value) { public boolean isMatch(Field value, Object parent) {
if (super.isMatch(value)) { if (super.isMatch(value, parent)) {
return typeMatcher.isClassEqual(value.getType()); return typeMatcher.isMatch(value.getType(), value);
} }
// No match // No match
return false; return false;
@ -101,8 +119,8 @@ public class FuzzyFieldContract extends AbstractFuzzyMember<Field> {
@Override @Override
protected int calculateRoundNumber() { protected int calculateRoundNumber() {
int current = -typeMatcher.getClassNumber(); // Combine the two
return combineRounds(super.calculateRoundNumber(),
return combineRounds(super.calculateRoundNumber(), current); typeMatcher.calculateRoundNumber());
} }
} }

View File

@ -20,14 +20,14 @@ public class FuzzyMethodContract extends AbstractFuzzyMember<MethodInfo> {
/** /**
* The expected index. * The expected index.
*/ */
private final ClassMatcher typeMatcher; private final AbstractFuzzyMatcher<Class<?>> typeMatcher;
private final Integer indexMatch; private final Integer indexMatch;
/** /**
* Construct a new parameter class matcher. * Construct a new parameter class matcher.
* @param typeMatcher - class type matcher. * @param typeMatcher - class type matcher.
*/ */
public ParameterClassMatcher(@Nonnull ClassMatcher typeMatcher) { public ParameterClassMatcher(@Nonnull AbstractFuzzyMatcher<Class<?>> typeMatcher) {
this(typeMatcher, null); this(typeMatcher, null);
} }
@ -36,7 +36,7 @@ public class FuzzyMethodContract extends AbstractFuzzyMember<MethodInfo> {
* @param typeMatcher - class type matcher. * @param typeMatcher - class type matcher.
* @param indexMatch - parameter index to match, or NULL for anything. * @param indexMatch - parameter index to match, or NULL for anything.
*/ */
public ParameterClassMatcher(@Nonnull ClassMatcher typeMatcher, Integer indexMatch) { public ParameterClassMatcher(@Nonnull AbstractFuzzyMatcher<Class<?>> typeMatcher, Integer indexMatch) {
if (typeMatcher == null) if (typeMatcher == null)
throw new IllegalArgumentException("Type matcher cannot be NULL."); throw new IllegalArgumentException("Type matcher cannot be NULL.");
@ -47,25 +47,26 @@ public class FuzzyMethodContract extends AbstractFuzzyMember<MethodInfo> {
/** /**
* See if there's a match for this matcher. * See if there's a match for this matcher.
* @param used - parameters that have been matched before. * @param used - parameters that have been matched before.
* @param parent - the container (member) that holds a reference to this parameter.
* @param params - the type of each parameter. * @param params - the type of each parameter.
* @return TRUE if this matcher matches any of the given parameters, FALSE otherwise. * @return TRUE if this matcher matches any of the given parameters, FALSE otherwise.
*/ */
public boolean isParameterMatch(Class<?> param, int index) { public boolean isParameterMatch(Class<?> param, MethodInfo parent, int index) {
// Make sure the index is valid (or NULL) // Make sure the index is valid (or NULL)
if (indexMatch == null || indexMatch == index) if (indexMatch == null || indexMatch == index)
return typeMatcher.isClassEqual(param); return typeMatcher.isMatch(param, parent);
else else
return false; return false;
} }
@Override @Override
public boolean isMatch(Class<?>[] value) { public boolean isMatch(Class<?>[] value, Object parent) {
throw new NotImplementedException("Use the parameter match instead."); throw new NotImplementedException("Use the parameter match instead.");
} }
@Override @Override
protected int calculateRoundNumber() { protected int calculateRoundNumber() {
return -typeMatcher.getClassNumber(); return typeMatcher.getRoundNumber();
} }
@Override @Override
@ -75,7 +76,7 @@ public class FuzzyMethodContract extends AbstractFuzzyMember<MethodInfo> {
} }
// Match return value // Match return value
private ClassMatcher returnMatcher = ClassMatcher.MATCH_ALL; private AbstractFuzzyMatcher<Class<?>> returnMatcher = ExactClassMatcher.MATCH_ALL;
// Handle parameters and exceptions // Handle parameters and exceptions
private List<ParameterClassMatcher> paramMatchers = Lists.newArrayList(); private List<ParameterClassMatcher> paramMatchers = Lists.newArrayList();
@ -90,92 +91,229 @@ public class FuzzyMethodContract extends AbstractFuzzyMember<MethodInfo> {
return this; return this;
} }
@Override
public Builder banModifier(int modifier) { public Builder banModifier(int modifier) {
super.banModifier(modifier); super.banModifier(modifier);
return this; return this;
} }
@Override
public Builder nameRegex(String regex) { public Builder nameRegex(String regex) {
super.nameRegex(regex); super.nameRegex(regex);
return this; return this;
} }
@Override
public Builder nameRegex(Pattern pattern) { public Builder nameRegex(Pattern pattern) {
super.nameRegex(pattern); super.nameRegex(pattern);
return this; return this;
} }
@Override
public Builder nameExact(String name) { public Builder nameExact(String name) {
super.nameExact(name); super.nameExact(name);
return this; return this;
} }
@Override
public Builder declaringClassExactType(Class<?> declaringClass) { public Builder declaringClassExactType(Class<?> declaringClass) {
super.declaringClassExactType(declaringClass); super.declaringClassExactType(declaringClass);
return this; return this;
} }
public Builder declaringClassCanHold(Class<?> declaringClass) { @Override
super.declaringClassCanHold(declaringClass); public Builder declaringClassSuperOf(Class<?> declaringClass) {
super.declaringClassSuperOf(declaringClass);
return this; return this;
} }
@Override
public Builder declaringClassDerivedOf(Class<?> declaringClass) {
super.declaringClassDerivedOf(declaringClass);
return this;
}
@Override
public Builder declaringClassMatching(AbstractFuzzyMatcher<Class<?>> classMatcher) {
super.declaringClassMatching(classMatcher);
return this;
}
/**
* Add a new required parameter by type for any matching method.
* @param type - the exact type this parameter must match.
* @return This builder, for chaining.
*/
public Builder parameterExactType(Class<?> type) { public Builder parameterExactType(Class<?> type) {
member.paramMatchers.add(new ParameterClassMatcher(new ClassMatcher(type, false))); member.paramMatchers.add(new ParameterClassMatcher(ExactClassMatcher.matchExact(type)));
return this; return this;
} }
public Builder parameterCanHold(Class<?> type) { /**
member.paramMatchers.add(new ParameterClassMatcher(new ClassMatcher(type, true))); * Add a new required parameter whose type must be a superlcass of the given type.
* <p>
* If a parameter is of type Number, any derived class (Integer, Long, etc.) will match it.
* @param type - a type or derived type of the matching parameter.
* @return This builder, for chaining.
*/
public Builder parameterSuperOf(Class<?> type) {
member.paramMatchers.add(new ParameterClassMatcher(ExactClassMatcher.matchSuper(type)));
return this; return this;
} }
/**
* Add a new required parameter whose type must match the given class matcher.
* @param classMatcher - the class matcher.
* @return This builder, for chaining.
*/
public Builder parameterMatches(AbstractFuzzyMatcher<Class<?>> classMatcher) {
member.paramMatchers.add(new ParameterClassMatcher(classMatcher));
return this;
}
/**
* Add a new required parameter by type and position for any matching method.
* @param type - the exact type this parameter must match.
* @param index - the expected position in the parameter list.
* @return This builder, for chaining.
*/
public Builder parameterExactType(Class<?> type, int index) { public Builder parameterExactType(Class<?> type, int index) {
member.paramMatchers.add(new ParameterClassMatcher(new ClassMatcher(type, false), index)); member.paramMatchers.add(new ParameterClassMatcher(ExactClassMatcher.matchExact(type), index));
return this; return this;
} }
public Builder parameterCanHold(Class<?> type, int index) { /**
member.paramMatchers.add(new ParameterClassMatcher(new ClassMatcher(type, true), index)); * Add a new required parameter whose type must be a superclass of the given type.
* <p>
* If a parameter is of type Number, any derived class (Integer, Long, etc.) will match it.
* @param type - a type or derived type of the matching parameter.
* @param index - the expected position in the parameter list.
* @return This builder, for chaining.
*/
public Builder parameterSuperOf(Class<?> type, int index) {
member.paramMatchers.add(new ParameterClassMatcher(ExactClassMatcher.matchSuper(type), index));
return this; return this;
} }
/**
* Add a new required parameter whose type must match the given class matcher and index.
* @param classMatcher - the class matcher.
* @param index - the expected position in the parameter list.
* @return This builder, for chaining.
*/
public Builder parameterMatches(AbstractFuzzyMatcher<Class<?>> classMatcher, int index) {
member.paramMatchers.add(new ParameterClassMatcher(classMatcher, index));
return this;
}
/**
* Set the expected number of parameters in the matching method.
* @param expectedCount - the number of parameters to expect.
* @return This builder, for chaining.
*/
public Builder parameterCount(int expectedCount) { public Builder parameterCount(int expectedCount) {
member.paramCount = expectedCount; member.paramCount = expectedCount;
return this; return this;
} }
/**
* Require a void method.
* @return This builder, for chaining.
*/
public Builder returnTypeVoid() { public Builder returnTypeVoid() {
return returnTypeExact(Void.TYPE); return returnTypeExact(Void.TYPE);
} }
/**
* Set the return type of a matching method exactly.
* @param type - the exact return type.
* @return This builder, for chaining.
*/
public Builder returnTypeExact(Class<?> type) { public Builder returnTypeExact(Class<?> type) {
member.returnMatcher = new ClassMatcher(type, false); member.returnMatcher = ExactClassMatcher.matchExact(type);
return this; return this;
} }
public Builder returnCanHold(Class<?> type) { /**
member.returnMatcher = new ClassMatcher(type, true); * Set the expected super class of the return type for every matching method.
* @param type - the return type, or a super class of it.
* @return This builder, for chaining.
*/
public Builder returnDerivedOf(Class<?> type) {
member.returnMatcher = ExactClassMatcher.matchDerived(type);
return this; return this;
} }
/**
* Set a matcher that must match the return type of a matching method.
* @param classMatcher - the exact return type.
* @return This builder, for chaining.
*/
public Builder returnTypeMatches(AbstractFuzzyMatcher<Class<?>> classMatcher) {
member.returnMatcher = classMatcher;
return this;
}
/**
* Add a throwable exception that must match the given type exactly.
* @param type - exception type.
* @return This builder, for chaining.
*/
public Builder exceptionExactType(Class<?> type) { public Builder exceptionExactType(Class<?> type) {
member.exceptionMatchers.add(new ParameterClassMatcher(new ClassMatcher(type, false))); member.exceptionMatchers.add(new ParameterClassMatcher(ExactClassMatcher.matchExact(type)));
return this; return this;
} }
public Builder exceptionCanHold(Class<?> type) { /**
member.exceptionMatchers.add(new ParameterClassMatcher(new ClassMatcher(type, true))); * Add a throwable exception that must match the given type or be derived.
* @param type - exception type.
* @return This builder, for chaining.
*/
public Builder exceptionSuperOf(Class<?> type) {
member.exceptionMatchers.add(new ParameterClassMatcher(ExactClassMatcher.matchSuper(type)));
return this; return this;
} }
/**
* Add a throwable exception that must match the given matcher,
* @param classMatcher - the class matcher that must match.
* @return This builder, for chaining.
*/
public Builder exceptionMatches(AbstractFuzzyMatcher<Class<?>> classMatcher) {
member.exceptionMatchers.add(new ParameterClassMatcher(classMatcher));
return this;
}
/**
* Add a throwable exception that must match the given type exactly and index.
* @param type - exception type.
* @param index - the position in the throwable list.
* @return This builder, for chaining.
*/
public Builder exceptionExactType(Class<?> type, int index) { public Builder exceptionExactType(Class<?> type, int index) {
member.exceptionMatchers.add(new ParameterClassMatcher(new ClassMatcher(type, false), index)); member.exceptionMatchers.add(new ParameterClassMatcher(ExactClassMatcher.matchExact(type), index));
return this; return this;
} }
public Builder exceptionCanHold(Class<?> type, int index) { /**
member.exceptionMatchers.add(new ParameterClassMatcher(new ClassMatcher(type, true), index)); * Add a throwable exception that must match the given type or be derived and index.
* @param type - exception type.
* @param index - the position in the throwable list.
* @return This builder, for chaining.
*/
public Builder exceptionSuperOf(Class<?> type, int index) {
member.exceptionMatchers.add(new ParameterClassMatcher(ExactClassMatcher.matchSuper(type), index));
return this;
}
/**
* Add a throwable exception that must match the given matcher and index.
* @param classMatcher - the class matcher that must match.
* @param index - the position in the throwable list.
* @return This builder, for chaining.
*/
public Builder exceptionMatches(AbstractFuzzyMatcher<Class<?>> classMatcher, int index) {
member.exceptionMatchers.add(new ParameterClassMatcher(classMatcher, index));
return this; return this;
} }
@ -190,7 +328,6 @@ public class FuzzyMethodContract extends AbstractFuzzyMember<MethodInfo> {
member.prepareBuild(); member.prepareBuild();
return new FuzzyMethodContract(member); return new FuzzyMethodContract(member);
} }
} }
public static Builder newBuilder() { public static Builder newBuilder() {
@ -219,31 +356,31 @@ public class FuzzyMethodContract extends AbstractFuzzyMember<MethodInfo> {
} }
@Override @Override
public boolean isMatch(MethodInfo value) { public boolean isMatch(MethodInfo value, Object parent) {
if (super.isMatch(value)) { if (super.isMatch(value, parent)) {
Class<?>[] params = value.getParameterTypes(); Class<?>[] params = value.getParameterTypes();
Class<?>[] exceptions = value.getExceptionTypes(); Class<?>[] exceptions = value.getExceptionTypes();
if (!returnMatcher.isClassEqual(value.getReturnType())) if (!returnMatcher.isMatch(value.getReturnType(), value))
return false; return false;
if (paramCount != null && paramCount != value.getParameterTypes().length) if (paramCount != null && paramCount != value.getParameterTypes().length)
return false; return false;
// Finally, check parameters and exceptions // Finally, check parameters and exceptions
return matchParameters(params, paramMatchers) && return matchParameters(params, value, paramMatchers) &&
matchParameters(exceptions, exceptionMatchers); matchParameters(exceptions, value, exceptionMatchers);
} }
// No match // No match
return false; return false;
} }
private boolean matchParameters(Class<?>[] types, List<ParameterClassMatcher> matchers) { private boolean matchParameters(Class<?>[] types, MethodInfo parent, List<ParameterClassMatcher> matchers) {
boolean[] accepted = new boolean[matchers.size()]; boolean[] accepted = new boolean[matchers.size()];
int count = accepted.length; int count = accepted.length;
// Process every parameter in turn // Process every parameter in turn
for (int i = 0; i < types.length; i++) { for (int i = 0; i < types.length; i++) {
int matcherIndex = processValue(types[i], i, accepted, matchers); int matcherIndex = processValue(types[i], parent, i, accepted, matchers);
if (matcherIndex >= 0) { if (matcherIndex >= 0) {
accepted[matcherIndex] = true; accepted[matcherIndex] = true;
@ -257,12 +394,12 @@ public class FuzzyMethodContract extends AbstractFuzzyMember<MethodInfo> {
return count == 0; return count == 0;
} }
private int processValue(Class<?> value, int index, boolean accepted[], List<ParameterClassMatcher> matchers) { private int processValue(Class<?> value, MethodInfo parent, int index, boolean accepted[], List<ParameterClassMatcher> matchers) {
// The order matters // The order matters
for (int i = 0; i < matchers.size(); i++) { for (int i = 0; i < matchers.size(); i++) {
if (!accepted[i]) { if (!accepted[i]) {
// See if we got jackpot // See if we got jackpot
if (matchers.get(i).isParameterMatch(value, index)) { if (matchers.get(i).isParameterMatch(value, parent, index)) {
return i; return i;
} }
} }
@ -277,7 +414,7 @@ public class FuzzyMethodContract extends AbstractFuzzyMember<MethodInfo> {
int current = 0; int current = 0;
// Consider the return value first // Consider the return value first
current = -returnMatcher.getClassNumber(); current = returnMatcher.getRoundNumber();
// Handle parameters // Handle parameters
for (ParameterClassMatcher matcher : paramMatchers) { for (ParameterClassMatcher matcher : paramMatchers) {

View File

@ -121,7 +121,7 @@ public class FuzzyReflection {
// Add all matching fields to the list // Add all matching fields to the list
for (Method method : getMethods()) { for (Method method : getMethods()) {
if (matcher.isMatch(MethodInfo.fromMethod(method))) { if (matcher.isMatch(MethodInfo.fromMethod(method), source)) {
methods.add(method); methods.add(method);
} }
} }
@ -338,7 +338,7 @@ public class FuzzyReflection {
// Add all matching fields to the list // Add all matching fields to the list
for (Field field : getFields()) { for (Field field : getFields()) {
if (matcher.isMatch(field)) { if (matcher.isMatch(field, source)) {
fields.add(field); fields.add(field);
} }
} }
@ -436,7 +436,7 @@ public class FuzzyReflection {
// Add all matching fields to the list // Add all matching fields to the list
for (Constructor<?> constructor : getConstructors()) { for (Constructor<?> constructor : getConstructors()) {
if (matcher.isMatch(MethodInfo.fromConstructor(constructor))) { if (matcher.isMatch(MethodInfo.fromConstructor(constructor), source)) {
constructors.add(constructor); constructors.add(constructor);
} }
} }