# StalactiteQueryMethodInvocationParameters.java

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

    import java.util.HashMap;
    import java.util.Map;

    import org.codefilarete.stalactite.query.model.Limit;
    import org.codefilarete.stalactite.spring.repository.query.StalactiteQueryMethod;
    import org.codefilarete.stalactite.spring.repository.query.execution.reduce.LimitHandler;
    import org.codefilarete.stalactite.sql.Dialect;
    import org.codefilarete.stalactite.sql.statement.binder.PreparedStatementWriter;
    import org.codefilarete.tool.collection.Arrays;
    import org.springframework.core.ResolvableType;
    import org.springframework.data.relational.repository.query.RelationalParameters;
    import org.springframework.data.repository.query.Parameter;
    import org.springframework.data.repository.query.Parameters;
    import org.springframework.data.repository.query.ParametersParameterAccessor;

    /**
     * An enhanced version of {@link ParametersParameterAccessor} for Stalactite framework need.
     *
     * @author Guillaume Mary
     */
    public class StalactiteQueryMethodInvocationParameters extends ParametersParameterAccessor implements LimitHandler {

    	public static final String PARAMETER_NEEDS_TO_BE_NAMED = "For queries with named parameters you need to provide names for method parameters."
    			+ " Use @Param for query method parameters, or when on Java 8+ use the javac flag -parameters.";

    	private final StalactiteQueryMethod method;

    	private Limit limit;

    	/**
    	 * Constructor matching super one.
    	 */
    	public StalactiteQueryMethodInvocationParameters(StalactiteQueryMethod method, Object[] values) {
    		super(method.getParameters(), values);
    		this.method = method;
    	}

    	/**
    	 * Overridden to make it public
    	 *
    	 * @return current method parameters values
    	 */
    	@Override
    	public Object[] getValues() {
    		return super.getValues();
    	}

    	/**
    	 * Returns the dynamic projection type ({@link Class} object in the values) if any, null otherwise.
    	 * @return null if no dynamic projection is available
    	 */
    	public Class<?> getDynamicProjectionType() {
    		if (getParameters().hasDynamicProjection()) {
    			return (Class<?>) getValues()[getParameters().getDynamicProjectionIndex()];
    		} else {
    			return null;
    		}
    	}

    	/**
    	 * Transforms the values given at construction time into a {@link Map} of values according to their names found in given {@link Parameters}.
    	 * @return a {@link Map} of values per their names
    	 */
    	public Map<String, Object> getNamedValues() {
    		Map<String, Object> result = new HashMap<>();
    		for (Parameter bindableParameter : getParameters().getBindableParameters()) {
    			String parameterName = bindableParameter.getName().orElseThrow(() -> new IllegalStateException(PARAMETER_NEEDS_TO_BE_NAMED));
    			Object value = getBindableValue(bindableParameter.getIndex());
    			if (value.getClass().isArray()) {
    				Object[] values = (Object[]) value;
    				if (values.length == 1) {
    					value = values[0];
    				} else {
    					value = Arrays.asList(values);
    				}
    			}
    			result.put(parameterName, value);
    		}
    		return result;
    	}

    	public Map<String, PreparedStatementWriter<?>> bindParameters(Dialect dialect) {
    		Map<String, PreparedStatementWriter<?>> result = new HashMap<>();
    		RelationalParameters bindableParameters = method.getParameters().getBindableParameters();

    		for (RelationalParameters.RelationalParameter bindableParameter : bindableParameters) {
    			String parameterName = bindableParameter.getName().orElseThrow(() -> new IllegalStateException(StalactiteQueryMethodInvocationParameters.PARAMETER_NEEDS_TO_BE_NAMED));

    			Object value = getBindableValue(bindableParameter.getIndex());

    			Class<?> valueType;
    			if (value instanceof Iterable) {
    				ResolvableType resolvableType = bindableParameter.getResolvableType();
    				valueType = resolvableType.getGeneric(0).resolve();
    			} else if (value.getClass().isArray()) {
    				valueType = value.getClass().getComponentType();
    			} else {
    				valueType = value.getClass();
    			}
    			PreparedStatementWriter<?> writer = dialect.getColumnBinderRegistry().getWriter(valueType);
    			result.put(parameterName, writer);
    		}

    		return result;
    	}

    	public void setLimit(Limit limit) {
    		this.limit = limit;
    	}

    	public Limit getLimit() {
    		return limit;
    	}

    	@Override
    	public void limit(int count) {
    		setLimit(new Limit(count));
    	}

    	@Override
    	public void limit(int count, Integer offset) {
    		setLimit(new Limit(count, offset));
    	}
    }