package org.jadira.scanner.core.spi; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import org.jadira.scanner.core.api.Allocator; import org.jadira.scanner.core.api.Filter; import org.jadira.scanner.core.api.Locator; import org.jadira.scanner.core.api.Projector; import org.jadira.scanner.core.api.Resolver; import org.jadira.scanner.core.concurrent.AllocatorTask; import org.jadira.scanner.core.concurrent.FilterTask; import org.jadira.scanner.core.concurrent.ProjectorTask; public abstract class AbstractResolver<T, E, A> implements Resolver<T, E, A> { private static final Integer ZERO = Integer.valueOf(0); private static final int SEGMENT_SIZE = 500; private final List<A> driverData; protected AbstractResolver() { this.driverData = new ArrayList<A>(); } protected AbstractResolver(List<A> driverData) { this.driverData = driverData; } protected List<A> getDriverData() { return driverData; } protected List<A> locate(Locator<A> locator) { final List<A> result = new ArrayList<A>(); if (driverData != null) { result.addAll(driverData); } final List<A> located = locator == null ? null : locator.locate(); if (located != null) { result.addAll(located); } return result; } protected List<E> allocate(List<A> driverData) { AllocatorTask<E, A> task = new AllocatorTask<E, A>(getAllocator(), driverData); final List<E> result = task.compute(); return result; } protected List<E> project(Projector<E> projector, List<E> sourceList) { ProjectorTask<E> task = new ProjectorTask<E>(projector, sourceList); List<E> result = task.compute(); return result; } protected List<T> assign(List<E> sourceList) { AllocatorTask<T, E> task = new AllocatorTask<T, E>(getAssigner(), sourceList); List<T> result = task.compute(); return result; } private <S> List<S> filter(Class<?> sourceType, Integer limit, List<Filter<?>> myFilters, List<S> sourceList) { if (ZERO.equals(limit)) { return Collections.emptyList(); } List<S> result = sourceList; for (Filter<?> nextFilter : myFilters) { if (nextFilter.targetType().isAssignableFrom(sourceType)) { @SuppressWarnings("unchecked") Filter<S> theFilter = (Filter<S>) nextFilter; FilterTask<S> task = new FilterTask<S>(limit, theFilter, result); result = task.compute(); } } return result; } protected abstract Allocator<E,A> getAllocator(); protected abstract Allocator<T,E> getAssigner(); @Override public T resolveFirst(Locator<A> locator, Projector<E> projector, Filter<?>... filters) { List<? extends T> result = resolve(Integer.valueOf(1), locator, projector, filters); return result.isEmpty() ? null : result.get(0); } @Override public List<? extends T> resolve(Integer limit, Locator<A> locator, Projector<E> projector, Filter<?>... filters) { final List<Filter<?>> myFilters = Arrays.asList(filters); final List<A> locatedList = locate(locator); List<E> sourceList = allocate(locatedList); // Chunk the source list to avoid resource starvation final List<T> output = new ArrayList<T>(); sourceList = project(projector, sourceList); while (!sourceList.isEmpty() && (limit == null || (output.size() < limit))) { List<E> nextSegmentList; if (sourceList.size() <= SEGMENT_SIZE) { nextSegmentList = sourceList; sourceList = Collections.emptyList(); } else { nextSegmentList = sourceList.subList(0, SEGMENT_SIZE); sourceList = sourceList.subList(SEGMENT_SIZE, sourceList.size()); } nextSegmentList = filter(getSourceType(), null, myFilters, nextSegmentList); if (!nextSegmentList.isEmpty()) { List<T> targetList = assign(nextSegmentList); targetList = filter(getTargetType(), limit, myFilters, targetList); output.addAll(targetList); } } if (limit == null) { return output; } else { return output.subList(0, output.size() < limit ? output.size() : limit); } } @Override public List<? extends T> resolveAll(Locator<A> locator, Projector<E> projector, Filter<?>... filters) { return resolve(null, locator, projector, filters); } protected Class<?> getSourceType() { return TypeHelper.getTypeArguments(AbstractResolver.class, this.getClass()).get(1); } protected Class<?> getTargetType() { return TypeHelper.getTypeArguments(AbstractResolver.class, this.getClass()).get(0); } }