package openmods.access; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import java.lang.reflect.Modifier; import java.util.Collection; import java.util.Map; import java.util.Queue; import java.util.Set; public class ApiProviderRegistry<A> { private final Class<? super A> markerType; private final Map<Class<? extends A>, ApiInstanceProvider<?>> providers = Maps.newHashMap(); private boolean isFrozen; private static boolean shouldIncludeSuper(ApiSingleton meta) { return meta == null || meta.includeSuper(); } public void freeze() { this.isFrozen = true; } @SuppressWarnings("unchecked") private void findAllImplementedApis(Collection<Class<? extends A>> output, Class<?>... intfs) { for (Class<?> cls : intfs) { Preconditions.checkArgument(cls.isInterface()); if (markerType.isAssignableFrom(cls) && markerType != cls) output.add((Class<? extends A>)cls); } } private void addAllApiInterfaces(Set<Class<? extends A>> interfaces) { Queue<Class<? extends A>> queue = Lists.newLinkedList(interfaces); Class<? extends A> cls; while ((cls = queue.poll()) != null) { interfaces.add(cls); findAllImplementedApis(queue, cls.getInterfaces()); } } private <T extends A> void registerInterfaces(Class<? extends T> cls, ApiInstanceProvider<T> provider, boolean includeSuper) { Set<Class<? extends A>> implemented = Sets.newHashSet(); findAllImplementedApis(implemented, cls.getInterfaces()); if (includeSuper) addAllApiInterfaces(implemented); for (Class<? extends A> impl : implemented) { ApiInstanceProvider<?> prev = providers.put(impl, provider); Preconditions.checkState(prev == null, "Conflict on %s: %s -> %s", impl, prev, provider); } } private static boolean shouldIncludeSuper(ApiImplementation meta) { return meta == null || meta.includeSuper(); } private static boolean isCacheable(ApiImplementation meta) { return meta == null || meta.cacheable(); } public ApiProviderRegistry(Class<? super A> markerType) { this.markerType = markerType; } public boolean isFrozen() { return isFrozen; } public <T extends A> void registerClass(Class<? extends T> cls) { Preconditions.checkState(!isFrozen, "This registry is already frozen"); Preconditions.checkArgument(!Modifier.isAbstract(cls.getModifiers())); final ApiImplementation meta = cls.getAnnotation(ApiImplementation.class); ApiInstanceProvider<T> provider = isCacheable(meta)? new ApiInstanceProvider.CachedInstance<T>(cls) : new ApiInstanceProvider.NewInstance<T>(cls); registerInterfaces(cls, provider, shouldIncludeSuper(meta)); } public <T extends A> void registerInstance(T obj) { Preconditions.checkState(!isFrozen, "This registry is already frozen"); @SuppressWarnings("unchecked") final Class<? extends T> cls = (Class<? extends T>)obj.getClass(); final ApiSingleton meta = cls.getAnnotation(ApiSingleton.class); ApiInstanceProvider<T> provider = new ApiInstanceProvider.Singleton<T>(obj); registerInterfaces(cls, provider, shouldIncludeSuper(meta)); } @SuppressWarnings("unchecked") public <T extends A> T getApi(Class<T> cls) { ApiInstanceProvider<?> provider = providers.get(cls); Preconditions.checkNotNull(provider, "Can't get implementation for class %s", cls); return (T)provider.getInterface(); } public <T extends A> boolean isApiPresent(Class<T> cls) { return providers.containsKey(cls); } }