package tc.oc.api.model; import java.util.Map; import java.util.Set; import javax.inject.Singleton; import com.google.inject.Provides; import com.google.inject.TypeLiteral; import com.google.inject.binder.LinkedBindingBuilder; import com.google.inject.multibindings.Multibinder; import com.google.inject.multibindings.OptionalBinder; import tc.oc.api.docs.virtual.Model; import tc.oc.api.docs.virtual.PartialModel; import tc.oc.api.queue.QueueQueryService; import tc.oc.commons.core.inject.Binders; import tc.oc.commons.core.inject.KeyedManifest; import tc.oc.commons.core.inject.SingletonManifest; import tc.oc.commons.core.reflect.ResolvableType; import tc.oc.commons.core.reflect.TypeArgument; import tc.oc.commons.core.reflect.TypeLiterals; import tc.oc.commons.core.stream.Collectors; import tc.oc.commons.core.util.ImmutableTypeMap; import tc.oc.commons.core.util.TypeMap; import tc.oc.inject.ProtectedBinder; import tc.oc.minecraft.suspend.SuspendableBinder; public class ModelBinder implements ModelTypeLiterals, TypeLiterals { private final TypeLiteral M; private final TypeLiteral

P; private final Binders binder; private final Multibinder metas; private final OptionalBinder> queryServiceBinder; private final OptionalBinder> updateServiceBinder; private final OptionalBinder> serviceBinder; private final OptionalBinder> storeBinder; public static ModelBinder of(ProtectedBinder binder, Class M) { return of(binder, M, M); } public static ModelBinder of(ProtectedBinder binder, TypeLiteral M) { return of(binder, M, M); } public static ModelBinder of(ProtectedBinder binder, Class M, Class

P) { return of(binder, TypeLiteral.get(M), TypeLiteral.get(P)); } public static ModelBinder of(ProtectedBinder binder, TypeLiteral M, TypeLiteral

P) { return new ModelBinder<>(binder, M, P); } private ModelBinder(ProtectedBinder protectedBinder, TypeLiteral M, TypeLiteral

P) { this.binder = Binders.wrap(protectedBinder.publicBinder()); this.M = M; this.P = P; this.metas = Multibinder.newSetBinder(binder, ModelMeta.class); this.serviceBinder = OptionalBinder.newOptionalBinder(binder, ModelService(M, P)); this.queryServiceBinder = OptionalBinder.newOptionalBinder(binder, QueryService(M)); this.updateServiceBinder = OptionalBinder.newOptionalBinder(binder, UpdateService(P)); this.storeBinder = OptionalBinder.newOptionalBinder(binder, ModelStore(M)); binder.install(new OneTime()); binder.install(new PerModel()); } public LinkedBindingBuilder> bindStore() { binder.provisionEagerly(ModelStore(M)); new SuspendableBinder(binder).addBinding().to(ModelStore(M)); return storeBinder.setBinding(); } public OptionalBinder> queryService() { return queryServiceBinder; } public OptionalBinder> updateService() { return updateServiceBinder; } public LinkedBindingBuilder> bindDefaultService() { queryService().setDefault().to(ModelService(M, P)); updateService().setDefault().to(ModelService(M, P)); return serviceBinder.setDefault(); } public LinkedBindingBuilder> bindService() { queryService().setBinding().to(ModelService(M, P)); updateService().setBinding().to(ModelService(M, P)); return serviceBinder.setBinding(); } public TypeLiteral> nullService() { return NullModelService(M, P); } public TypeLiteral> nullQueryService() { return NullQueryService(M); } public TypeLiteral> httpService() { return HttpModelService(M, P); } public TypeLiteral> httpQueryService() { return HttpQueryService(M); } public TypeLiteral> queueQueryService() { return QueueQueryService(M); } private class PerModel extends KeyedManifest { @Override protected Object manifestKey() { return M; } @Override protected void configure() { final TypeLiteral> meta = ModelMeta(M, P); metas.addBinding().to(meta); bind(meta).in(Singleton.class); bind(new ResolvableType>(){}.with(new TypeArgument(M){})).to(meta); bind(new ResolvableType>(){}.with(new TypeArgument

(P){})).to(meta); } } private class OneTime extends SingletonManifest { @Provides @Singleton Map byName(Set metas) { return metas.stream().collect(Collectors.indexingBy(ModelMeta::name)); } @Provides @Singleton TypeMap byType(Set metas) { final ImmutableTypeMap.Builder builder = ImmutableTypeMap.builder(); metas.forEach(meta -> builder.put(meta.completeType(), meta)); return builder.build(); } @Provides @Singleton TypeMap byPartialType(Set metas) { final ImmutableTypeMap.Builder builder = ImmutableTypeMap.builder(); metas.forEach(meta -> builder.put(meta.partialType(), meta)); return builder.build(); } } }