package tc.oc.pgm.features;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Provider;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import org.jdom2.Document;
import tc.oc.commons.core.formatting.StringUtils;
import tc.oc.commons.core.inject.KeyedManifest;
import tc.oc.commons.core.reflect.ResolvableType;
import tc.oc.commons.core.reflect.TypeArgument;
import tc.oc.commons.core.reflect.Types;
import tc.oc.commons.core.stream.Collectors;
import tc.oc.commons.core.util.Optionals;
import tc.oc.pgm.map.inject.MapBinders;
import tc.oc.pgm.map.inject.MapScoped;
import tc.oc.pgm.module.ModuleExceptionHandler;
import tc.oc.pgm.xml.ElementFlattener;
import tc.oc.pgm.xml.Node;
import tc.oc.pgm.xml.finder.NodeFinder;
import tc.oc.pgm.xml.parser.Parser;
/**
* Configures a {@link FeatureDefinition} that is parsed from the root
* of the {@link Document} using a {@link NodeFinder}, and binds the
* results into List<T> in {@link MapScoped}.
*
* If no {@link NodeFinder} is specified, a typical structure is assumed,
* based on the {@link FeatureInfo} annotation present on {@link T}.
*/
public class RootFeatureManifest<T extends FeatureDefinition> extends KeyedManifest implements MapBinders {
private final TypeLiteral<T> featureType;
private final TypeArgument<T> featureTypeArg;
private final TypeLiteral<List<T>> featureListType;
private final Class<T> rawType;
private final NodeFinder nodeFinder;
protected RootFeatureManifest() {
this(null);
}
public RootFeatureManifest(@Nullable TypeLiteral<T> type) {
this(type, null);
}
public RootFeatureManifest(@Nullable Class<T> type, @Nullable NodeFinder nodeFinder) {
this(type == null ? null : TypeLiteral.get(type), nodeFinder);
}
public RootFeatureManifest(@Nullable TypeLiteral<T> type, @Nullable NodeFinder nodeFinder) {
this.featureType = type != null ? type : new ResolvableType<T>(){}.in(getClass());
this.featureTypeArg = new TypeArgument<T>(this.featureType){};
this.featureListType = new ResolvableType<List<T>>(){}.with(featureTypeArg);
this.rawType = (Class<T>) featureType.getRawType();
if(nodeFinder == null) {
final FeatureInfo info = Features.info(rawType);
final Set<String> singular = info.singular().length > 0 ? ImmutableSet.copyOf(info.singular())
: ImmutableSet.of(info.name());
final Set<String> plural = info.plural().length > 0 ? ImmutableSet.copyOf(info.plural())
: singular.stream()
.map(StringUtils::pluralize)
.collect(Collectors.toImmutableSet());
final ElementFlattener flattener = new ElementFlattener(plural, singular, 1);
nodeFinder = (parent, name) -> flattener.flattenChildren(parent).map(Node::of);
}
this.nodeFinder = nodeFinder;
}
@Override
protected Object manifestKey() {
return featureType;
}
@Override
protected void configure() {
provisionAtParseTime(featureListType)
.toProvider(new FeatureListProvider())
.in(MapScoped.class);
}
private class FeatureListProvider implements Provider<List<T>> {
@Inject Provider<Document> documentProvider;
@Inject Provider<ModuleExceptionHandler> exceptionHandlerProvider;
final Provider<FeatureParser<T>> parserProvider = getProvider(Key.get(Types.parameterizedTypeLiteral(FeatureParser.class, featureType)));
@Override
public List<T> get() {
final Document document = documentProvider.get();
final ModuleExceptionHandler exceptionHandler = exceptionHandlerProvider.get();
final Parser<T> parser = parserProvider.get();
// Skip over features that throw and keep loading the rest.
return nodeFinder.findNodes(document.getRootElement(), "")
.flatMap(child -> Optionals.stream(exceptionHandler.ignoringFailures(() -> parser.parse(child))))
.collect(Collectors.toImmutableList());
}
}
}