563 lines
16 KiB
Java
563 lines
16 KiB
Java
package com.comphenix.protocol.reflect.fuzzy;
|
|
|
|
import com.comphenix.protocol.reflect.MethodInfo;
|
|
import com.google.common.collect.ImmutableList;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.regex.Pattern;
|
|
import javax.annotation.Nonnull;
|
|
|
|
/**
|
|
* Represents a contract for matching methods or constructors.
|
|
*
|
|
* @author Kristian
|
|
*/
|
|
public class FuzzyMethodContract extends AbstractFuzzyMember<MethodInfo> {
|
|
|
|
// Match return value
|
|
private AbstractFuzzyMatcher<Class<?>> returnMatcher = ClassTypeMatcher.MATCH_ALL;
|
|
// Handle parameters and exceptions
|
|
private List<ParameterClassMatcher> paramMatchers;
|
|
private List<ParameterClassMatcher> exceptionMatchers;
|
|
// Expected parameter count
|
|
private Integer paramCount;
|
|
|
|
private FuzzyMethodContract() {
|
|
// Only allow construction from the builder
|
|
this.paramMatchers = new ArrayList<>();
|
|
this.exceptionMatchers = new ArrayList<>();
|
|
}
|
|
|
|
private FuzzyMethodContract(FuzzyMethodContract other) {
|
|
super(other);
|
|
this.returnMatcher = other.returnMatcher;
|
|
this.paramMatchers = other.paramMatchers;
|
|
this.exceptionMatchers = other.exceptionMatchers;
|
|
this.paramCount = other.paramCount;
|
|
}
|
|
|
|
/**
|
|
* Return a method contract builder.
|
|
*
|
|
* @return Method contract builder.
|
|
*/
|
|
public static Builder newBuilder() {
|
|
return new Builder();
|
|
}
|
|
|
|
/**
|
|
* Construct a new immutable copy of the given method contract.
|
|
*
|
|
* @param other - the contract to clone.
|
|
* @return A immutable copy of the given contract.
|
|
*/
|
|
private static FuzzyMethodContract immutableCopy(FuzzyMethodContract other) {
|
|
FuzzyMethodContract copy = new FuzzyMethodContract(other);
|
|
|
|
// Ensure that the lists are immutable
|
|
copy.paramMatchers = ImmutableList.copyOf(copy.paramMatchers);
|
|
copy.exceptionMatchers = ImmutableList.copyOf(copy.exceptionMatchers);
|
|
return copy;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the class matcher for the return type.
|
|
*
|
|
* @return Class matcher for the return type.
|
|
*/
|
|
public AbstractFuzzyMatcher<Class<?>> getReturnMatcher() {
|
|
return this.returnMatcher;
|
|
}
|
|
|
|
/**
|
|
* Retrieve an immutable list of every parameter matcher for this method.
|
|
*
|
|
* @return Immutable list of every parameter matcher.
|
|
*/
|
|
public ImmutableList<ParameterClassMatcher> getParamMatchers() {
|
|
if (this.paramMatchers instanceof ImmutableList) {
|
|
return (ImmutableList<ParameterClassMatcher>) this.paramMatchers;
|
|
} else {
|
|
throw new IllegalStateException("Lists haven't been sealed yet.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieve an immutable list of every exception matcher for this method.
|
|
*
|
|
* @return Immutable list of every exception matcher.
|
|
*/
|
|
public List<ParameterClassMatcher> getExceptionMatchers() {
|
|
if (this.exceptionMatchers instanceof ImmutableList) {
|
|
return this.exceptionMatchers;
|
|
} else {
|
|
throw new IllegalStateException("Lists haven't been sealed yet.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieve the expected parameter count for this method.
|
|
*
|
|
* @return Expected parameter count, or NULL if anyting goes.
|
|
*/
|
|
public Integer getParamCount() {
|
|
return this.paramCount;
|
|
}
|
|
|
|
@Override
|
|
public boolean isMatch(MethodInfo value, Object parent) {
|
|
if (super.isMatch(value, parent)) {
|
|
// check the return type first (the easiest check)
|
|
if (!this.returnMatcher.isMatch(value.getReturnType(), value)) {
|
|
return false;
|
|
}
|
|
|
|
// check for the parameter types
|
|
Class<?>[] params = value.getParameterTypes();
|
|
if (this.paramCount != null && this.paramCount != params.length) {
|
|
return false;
|
|
}
|
|
|
|
// check parameters and exceptions
|
|
return this.matchTypes(params, value, this.paramMatchers)
|
|
&& this.matchTypes(value.getExceptionTypes(), value, this.exceptionMatchers);
|
|
}
|
|
|
|
// No match
|
|
return false;
|
|
}
|
|
|
|
private boolean matchTypes(Class<?>[] types, MethodInfo parent, List<ParameterClassMatcher> matchers) {
|
|
if (matchers.isEmpty()) {
|
|
// no matchers - no show
|
|
return true;
|
|
}
|
|
|
|
// the amount of matchers which are ok with the parameter types
|
|
int acceptingMatchers = 0;
|
|
for (int i = 0; i < types.length; i++) {
|
|
if (this.processValue(types[i], parent, i, matchers)) {
|
|
acceptingMatchers++;
|
|
// if all matchers accepted one type we are done
|
|
if (acceptingMatchers == matchers.size()) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private boolean processValue(Class<?> value, MethodInfo parent, int index, List<ParameterClassMatcher> matchers) {
|
|
// The order matters
|
|
for (ParameterClassMatcher matcher : matchers) {
|
|
// See if we got jackpot
|
|
if (matcher.isParameterMatch(value, parent, index)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Failure
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
protected Map<String, Object> getKeyValueView() {
|
|
Map<String, Object> member = super.getKeyValueView();
|
|
|
|
// Only add fields that are actual constraints
|
|
if (this.returnMatcher != ClassTypeMatcher.MATCH_ALL) {
|
|
member.put("return", this.returnMatcher);
|
|
}
|
|
|
|
if (!this.paramMatchers.isEmpty()) {
|
|
member.put("params", this.paramMatchers);
|
|
}
|
|
|
|
if (!this.exceptionMatchers.isEmpty()) {
|
|
member.put("exceptions", this.exceptionMatchers);
|
|
}
|
|
|
|
if (this.paramCount != null) {
|
|
member.put("paramCount", this.paramCount);
|
|
}
|
|
|
|
return member;
|
|
}
|
|
|
|
private static final class ParameterClassMatcher implements AbstractFuzzyMatcher<Class<?>[]> {
|
|
|
|
/**
|
|
* The expected index.
|
|
*/
|
|
private final AbstractFuzzyMatcher<Class<?>> typeMatcher;
|
|
private final Integer indexMatch;
|
|
|
|
/**
|
|
* Construct a new parameter class matcher.
|
|
*
|
|
* @param typeMatcher - class type matcher.
|
|
*/
|
|
public ParameterClassMatcher(@Nonnull AbstractFuzzyMatcher<Class<?>> 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 AbstractFuzzyMatcher<Class<?>> typeMatcher, Integer indexMatch) {
|
|
this.typeMatcher = typeMatcher;
|
|
this.indexMatch = indexMatch;
|
|
}
|
|
|
|
/**
|
|
* See if there's a match for this matcher.
|
|
*
|
|
* @param param - the type to match.
|
|
* @param parent - the container (member) that holds a reference to this parameter.
|
|
* @param index - the index of the current parameter.
|
|
* @return TRUE if this matcher matches any of the given parameters, FALSE otherwise.
|
|
*/
|
|
public boolean isParameterMatch(Class<?> param, MethodInfo parent, int index) {
|
|
// Make sure the index is valid (or NULL)
|
|
if (this.indexMatch == null || this.indexMatch == index) {
|
|
return this.typeMatcher.isMatch(param, parent);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean isMatch(Class<?>[] value, Object parent) {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return String.format("{ Parameter Type: %s, Index: %s }", this.typeMatcher, this.indexMatch);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Represents a builder for a fuzzy method contract.
|
|
*
|
|
* @author Kristian
|
|
*/
|
|
public static final class Builder extends AbstractFuzzyMember.Builder<FuzzyMethodContract> {
|
|
|
|
@Override
|
|
public Builder requireModifier(int modifier) {
|
|
super.requireModifier(modifier);
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public Builder requirePublic() {
|
|
super.requirePublic();
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public Builder banModifier(int modifier) {
|
|
super.banModifier(modifier);
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public Builder nameRegex(String regex) {
|
|
super.nameRegex(regex);
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public Builder nameRegex(Pattern pattern) {
|
|
super.nameRegex(pattern);
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public Builder nameExact(String name) {
|
|
super.nameExact(name);
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public Builder declaringClassExactType(Class<?> declaringClass) {
|
|
super.declaringClassExactType(declaringClass);
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
this.member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchExact(type)));
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Add a new required parameter whose type must be a superclass of the given type.
|
|
* <p>
|
|
* If a method parameter is of type Number, then any derived class here (Integer, Long, etc.) will match it.
|
|
*
|
|
* @param type - a type or less derived type of the matching parameter.
|
|
* @return This builder, for chaining.
|
|
*/
|
|
public Builder parameterSuperOf(Class<?> type) {
|
|
this.member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchSuper(type)));
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Add a new required parameter whose type must be a derived class of the given class.
|
|
* <p>
|
|
* If the method parameter has the type Integer, then the class Number here will match it.
|
|
*
|
|
* @param type - a type or more derived type of the matching parameter.
|
|
* @return This builder, for chaining.
|
|
*/
|
|
public Builder parameterDerivedOf(Class<?> type) {
|
|
this.member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchDerived(type)));
|
|
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) {
|
|
this.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) {
|
|
this.member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchExact(type), index));
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Add a new required parameters by type and order for any matching method.
|
|
*
|
|
* @param types - the types of every parameters in order.
|
|
* @return This builder, for chaining.
|
|
*/
|
|
public Builder parameterExactArray(Class<?>... types) {
|
|
this.parameterCount(types.length);
|
|
for (int i = 0; i < types.length; i++) {
|
|
this.parameterExactType(types[i], i);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
this.member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchSuper(type), index));
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Add a new required parameter whose type must be a derived class of the given class.
|
|
* <p>
|
|
* If the method parameter has the type Integer, then the class Number here will match it.
|
|
*
|
|
* @param type - a type or more derived type of the matching parameter.
|
|
* @param index - the expected position in the parameter list.
|
|
* @return This builder, for chaining.
|
|
*/
|
|
public Builder parameterDerivedOf(Class<?> type, int index) {
|
|
this.member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchDerived(type), index));
|
|
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) {
|
|
this.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) {
|
|
this.member.paramCount = expectedCount;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Require a void method.
|
|
*
|
|
* @return This builder, for chaining.
|
|
*/
|
|
public Builder returnTypeVoid() {
|
|
return this.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) {
|
|
this.member.returnMatcher = FuzzyMatchers.matchExact(type);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
this.member.returnMatcher = FuzzyMatchers.matchDerived(type);
|
|
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) {
|
|
this.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) {
|
|
this.member.exceptionMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchExact(type)));
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
this.member.exceptionMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchSuper(type)));
|
|
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) {
|
|
this.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) {
|
|
this.member.exceptionMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchExact(type), index));
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
this.member.exceptionMatchers.add(new ParameterClassMatcher(FuzzyMatchers.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) {
|
|
this.member.exceptionMatchers.add(new ParameterClassMatcher(classMatcher, index));
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
@Nonnull
|
|
protected FuzzyMethodContract initialMember() {
|
|
// With mutable lists
|
|
return new FuzzyMethodContract();
|
|
}
|
|
|
|
@Override
|
|
public FuzzyMethodContract build() {
|
|
this.member.prepareBuild();
|
|
return immutableCopy(this.member);
|
|
}
|
|
}
|
|
}
|