package tc.oc.pgm.xml.parser; import java.util.List; import java.util.NoSuchElementException; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import com.google.common.collect.ImmutableSet; import com.google.common.reflect.TypeParameter; import com.google.common.reflect.TypeToken; import com.google.inject.TypeLiteral; import tc.oc.commons.core.IterableUtils; import tc.oc.commons.core.reflect.Types; import tc.oc.commons.core.util.AmbiguousElementException; import tc.oc.commons.core.util.DuplicateElementException; import tc.oc.commons.core.util.Optionals; /** * Combines a sequence of {@link I} instances into a single instance of {@link O}, * which may be the same as {@link I}, or some container of {@link I}. * * Exceptions are thrown if the inner sequence does not fit the structure of the outer type: * * {@link NoSuchElementException} If the sequence is empty and the aggregate requires at least one element * {@link AmbiguousElementException} If the sequence has multiple elements and the aggregate requires no more than one element * {@link DuplicateElementException} If the sequence has duplicate elements and the aggregate requires unique elements */ public abstract class Aggregator<O, I> { private final TypeToken<O> outerType; private final TypeToken<I> innerType; protected Aggregator(TypeToken<I> innerType, TypeToken<O> outerType) { this.outerType = outerType; this.innerType = innerType; } public TypeToken<O> outerTypeToken() { return outerType; } public TypeToken<I> innerTypeToken() { return innerType; } public TypeLiteral<O> outerTypeLiteral() { return Types.toLiteral(outerType); } public TypeLiteral<I> innerTypeLiteral() { return Types.toLiteral(innerType); } protected abstract O aggregateElements(List<I> elements); public O aggregateElements(Stream<I> elements) throws NoSuchElementException, AmbiguousElementException, DuplicateElementException { return aggregateElements(elements.collect(Collectors.toList())); } public static boolean isWrapped(TypeToken<?> type) { return Types.isAssignable(Optional.class, type) || Types.isAssignable(Iterable.class, type); } public static <T> Aggregator<T, ?> forType(TypeToken<T> type) { if(Types.isAssignable(Optional.class, type)) { return new OptionalAggregator(Optionals.elementType((TypeToken) type)); } if(Types.isAssignable(Set.class, type)) { return new SetAggregator(IterableUtils.elementType((TypeToken) type)); } if(Types.isAssignable(Iterable.class, type)) { return new ListAggregator(IterableUtils.elementType((TypeToken) type)); } return new RequiredAggregator<>(type); } } class RequiredAggregator<T> extends Aggregator<T, T> { protected RequiredAggregator(TypeToken<T> innerType) { super(innerType, innerType); } @Override public T aggregateElements(List<T> elements) { switch(elements.size()) { case 0: throw new NoSuchElementException(); case 1: return elements.get(0); default: throw new AmbiguousElementException(); } } } class OptionalAggregator<T> extends Aggregator<Optional<T>, T> { protected OptionalAggregator(TypeToken<T> innerType) { super(innerType, new TypeToken<Optional<T>>(){}.where(new TypeParameter<T>(){}, innerType)); } @Override public Optional<T> aggregateElements(List<T> elements) { switch(elements.size()) { case 0: return Optional.empty(); case 1: return Optional.of(elements.get(0)); default: throw new AmbiguousElementException("Multiple values provided"); } } } class ListAggregator<T> extends Aggregator<List<T>, T> { protected ListAggregator(TypeToken<T> innerType) { super(innerType, new TypeToken<List<T>>(){}.where(new TypeParameter<T>(){}, innerType)); } @Override public List<T> aggregateElements(List<T> elements) { return elements; } } class SetAggregator<T> extends Aggregator<Set<T>, T> { protected SetAggregator(TypeToken<T> innerType) { super(innerType, new TypeToken<Set<T>>(){}.where(new TypeParameter<T>(){}, innerType)); } @Override public Set<T> aggregateElements(List<T> elements) { final Set<T> set = ImmutableSet.copyOf(elements); if(set.size() != elements.size()) { throw new DuplicateElementException(); } return set; } }