package tc.oc.pgm.features; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Stream; import javax.inject.Inject; import com.google.common.util.concurrent.UncheckedExecutionException; import com.google.inject.ImplementedBy; import tc.oc.commons.core.util.CachingTypeMap; import tc.oc.commons.core.util.Optionals; import tc.oc.pgm.match.Match; import tc.oc.pgm.module.ModuleLoadException; import static tc.oc.commons.core.util.Utils.ifInstance; @ImplementedBy(MatchFeatureContextImpl.class) public interface MatchFeatureContext { <T extends Feature<?>> Optional<T> bySlug(Class<T> type, String slug); Optional<Feature<?>> bySlug(String slug); <T extends Feature<?>> T get(FeatureFactory<T> factory); Stream<? extends Feature<?>> all(); <T extends Feature<?>> Stream<? extends T> all(Class<T> type); } class MatchFeatureContextImpl implements MatchFeatureContext { private final Match match; private final Map<String, SluggedFeature<?>> bySlug = new HashMap<>(); private final Map<FeatureFactory<?>, Feature<?>> byFactory = new HashMap<>(); private final CachingTypeMap<Feature<?>, Feature<?>> byType = CachingTypeMap.create(); private final Set<FeatureFactory<?>> creating = new HashSet<>(); @Inject MatchFeatureContextImpl(Match match) { this.match = match; } @Override public <T extends Feature<?>> Optional<T> bySlug(Class<T> type, String slug) { return Optionals.cast(bySlug(slug), type); } @Override public Optional<Feature<?>> bySlug(String slug) { return Optional.ofNullable(bySlug.get(slug)); } @Override public <T extends Feature<?>> T get(FeatureFactory<T> factory) { // Do NOT use computeIfAbsent for this! // The factory may try to get other features while it's creating this one, // and computeIfAbsent handles reentrancy VERY badly, // even if the keys are different. T feature = (T) byFactory.get(factory); if(feature != null) return feature; if(!creating.add(factory)) { throw new IllegalStateException("Recursive creation of feature for " + factory); } try { feature = factory.createFeature(match); } catch(ModuleLoadException e) { // TODO: can we do better than this? throw new UncheckedExecutionException(e); } finally { creating.remove(factory); } byFactory.put(factory, feature); byType.put((Class<? extends Feature<?>>) feature.getClass(), feature); byType.invalidate(); ifInstance(feature, SluggedFeature.class, slugged -> bySlug.put(slugged.slug(), slugged) ); match.registerEventsAndRepeatables(feature); return feature; } @Override public Stream<? extends Feature<?>> all() { return byFactory.values().stream(); } @Override public <T extends Feature<?>> Stream<? extends T> all(Class<T> type) { return (Stream<? extends T>) byType.allAssignableTo(type).stream(); } }