# QueryMethodValidator.java

    package org.codefilarete.stalactite.spring.repository.query;

    import org.springframework.data.repository.query.Parameter;
    import org.springframework.data.repository.query.ParameterOutOfBoundsException;
    import org.springframework.data.repository.query.QueryMethod;
    import org.springframework.data.repository.query.parser.Part;
    import org.springframework.data.repository.query.parser.Part.Type;
    import org.springframework.data.repository.query.parser.PartTree;
    import org.springframework.data.util.Streamable;

    /**
     * Validator for query method. Basically, it ensures that arguments match length and types of properties.
     *
     * @author Guillaume Mary
     * @see #validate()
     */
    public class QueryMethodValidator {

    	private final PartTree tree;
    	private final QueryMethod method;

    	public QueryMethodValidator(PartTree tree, QueryMethod method) {
    		this.tree = tree;
    		this.method = method;
    	}

    	public void validate() {
    		int argCount = 0;
    		Iterable<Part> parts = () -> tree.stream().flatMap(Streamable::stream).iterator();
    		for (Part part : parts) {
    			int numberOfArguments = part.getNumberOfArguments();
    			for (int i = 0; i < numberOfArguments; i++) {
    				throwExceptionOnArgumentMismatch(part, argCount);
    				argCount++;
    			}
    		}
    	}

    	private void throwExceptionOnArgumentMismatch(Part part, int index) {
    		Type type = part.getType();
    		String property = part.getProperty().toDotPath();

    		Parameter parameter;
    		try {
    			parameter = method.getParameters().getBindableParameter(index);
    		} catch (ParameterOutOfBoundsException e) {
    			throw new IllegalStateException(String.format(
    					"Method %s expects at least %d arguments but only found %d. This leaves an operator of type %s for property %s unbound.",
    					method.getName(), index + 1, index, type.name(), property));
    		}

    		if (expectsCollection(type) && !parameterIsCollectionLike(parameter)) {
    			throw new IllegalStateException(wrongParameterTypeMessage(property, type, "Collection", parameter));
    		} else if (!expectsCollection(type) && !parameterIsScalarLike(parameter)) {
    			throw new IllegalStateException(wrongParameterTypeMessage(property, type, "scalar", parameter));
    		}
    	}

    	private String wrongParameterTypeMessage(String property, Type operatorType, String expectedArgumentType, Parameter parameter) {

    		return String.format("Operator %s on %s requires a %s argument, found %s in method %s.", operatorType.name(),
    				property, expectedArgumentType, parameter.getType(), method.getName());
    	}

    	private boolean parameterIsCollectionLike(Parameter parameter) {
    		return Iterable.class.isAssignableFrom(parameter.getType()) || parameter.getType().isArray();
    	}

    	/**
    	 * Arrays are may be treated as collection like or in the case of binary data as scalar
    	 */
    	private boolean parameterIsScalarLike(Parameter parameter) {
    		return !Iterable.class.isAssignableFrom(parameter.getType());
    	}

    	private boolean expectsCollection(Type type) {
    		return type == Type.IN || type == Type.NOT_IN;
    	}
    }