# AbstractRepositoryQuery.java

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

    import java.util.IdentityHashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.function.LongSupplier;
    import java.util.function.Supplier;

    import org.codefilarete.reflection.AccessorChain;
    import org.codefilarete.reflection.AccessorDefinition;
    import org.codefilarete.stalactite.query.model.JoinLink;
    import org.codefilarete.stalactite.spring.repository.query.StalactiteQueryMethod;
    import org.codefilarete.stalactite.spring.repository.query.StalactiteRepositoryQuery;
    import org.codefilarete.stalactite.spring.repository.query.execution.reduce.QueryResultCollectioner;
    import org.codefilarete.stalactite.spring.repository.query.execution.reduce.QueryResultPager;
    import org.codefilarete.stalactite.spring.repository.query.execution.reduce.QueryResultReducer;
    import org.codefilarete.stalactite.spring.repository.query.execution.reduce.QueryResultSingler;
    import org.codefilarete.stalactite.spring.repository.query.execution.reduce.QueryResultSlicer;
    import org.codefilarete.stalactite.spring.repository.query.execution.reduce.QueryResultStreamer;
    import org.codefilarete.tool.Reflections;
    import org.codefilarete.tool.collection.Iterables;
    import org.springframework.data.repository.query.ParametersParameterAccessor;
    import org.springframework.data.repository.query.RepositoryQuery;
    import org.springframework.data.repository.query.ResultProcessor;

    /**
     * Parent class of some {@link RepositoryQuery} to share some code
     * @author Guillaume Mary
     */
    public abstract class AbstractRepositoryQuery<C, R> implements StalactiteRepositoryQuery<C, R> {

    	public static IdentityHashMap<JoinLink<?, ?>, String> buildAliases(IdentityHashMap<? extends JoinLink<?, ?>, ? extends AccessorChain<?, ?>> columnToProperties) {
    		return Iterables.map(
    				columnToProperties.entrySet(),
    				Map.Entry::getKey,
    				entry -> AccessorDefinition.giveDefinition(entry.getValue()).getName().replace('.', '_'),
    				IdentityHashMap::new);
    	}

    	protected final StalactiteQueryMethod method;

    	public AbstractRepositoryQuery(StalactiteQueryMethod method) {
    		this.method = method;
    	}

    	@Override
    	public StalactiteQueryMethod getQueryMethod() {
    		return method;
    	}

    	@Override
    	public R execute(Object[] parameters) {
    		StalactiteQueryMethodInvocationParameters invocationParameters = new StalactiteQueryMethodInvocationParameters(method, parameters);
    		AbstractQueryExecutor<List<Object>, Object> queryExecutor = buildQueryExecutor(invocationParameters);
    		Supplier<List<Object>> resultSupplier = queryExecutor.buildQueryExecutor(invocationParameters);

    		R adaptation = buildResultReducer(invocationParameters)
    				.adapt(resultSupplier)
    				.apply(parameters);

    		ResultProcessor resultProcessor = buildResultProcessor(parameters);
    		return resultProcessor.processResult(adaptation);
    	}

    	protected abstract AbstractQueryExecutor<List<Object>, Object> buildQueryExecutor(StalactiteQueryMethodInvocationParameters invocationParameters);

    	protected <ROW> QueryResultReducer<R, ROW> buildResultReducer(StalactiteQueryMethodInvocationParameters invocationParameters) {
    		QueryResultReducer<?, C> result;
    		switch (method.getQueryMethodReturnType()) {
    			case COLLECTION:
    				result = new QueryResultCollectioner<>();
    				break;
    			case PAGE:
    				result = new QueryResultPager<>(this, invocationParameters, buildCountSupplier(invocationParameters));
    				break;
    			case SLICE:
    				result = new QueryResultSlicer<>(this, invocationParameters);
    				break;
    			case STREAM:
    				result = new QueryResultStreamer<>(this, invocationParameters);
    				break;
    			case SINGLE_ENTITY:
    				result = new QueryResultSingler<>();
    				break;
    			case SINGLE_PROJECTION:
    				result = new QueryResultSingler<>();
    				break;
    			default:
    				throw new IllegalArgumentException("Unsupported return type for method " + Reflections.toString(method.getMethod()));
    		}
    		return (QueryResultReducer<R, ROW>) result;
    	}

    	protected abstract LongSupplier buildCountSupplier(StalactiteQueryMethodInvocationParameters accessor);

    	protected ResultProcessor buildResultProcessor(Object[] parameters) {
    		// - hasDynamicProjection() is for case of method that gives the expected returned type as a last argument (or a compound one by Collection or other)
    		ResultProcessor resultProcessor = method.getResultProcessor();
    		if (method.getParameters().hasDynamicProjection()) {
    			resultProcessor = resultProcessor.withDynamicProjection(new ParametersParameterAccessor(method.getParameters(), parameters));
    		}
    		return resultProcessor;
    	}

    }