package tc.oc.api.model; import java.util.Map; import java.util.Set; import java.util.logging.Logger; import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Singleton; import com.google.common.collect.HashMultimap; import com.google.common.collect.Maps; import com.google.common.collect.SetMultimap; import com.google.common.reflect.TypeParameter; import com.google.common.reflect.TypeToken; import com.google.inject.ImplementedBy; import tc.oc.api.docs.virtual.Model; import tc.oc.commons.core.logging.Loggers; import tc.oc.commons.core.reflect.Annotations; import tc.oc.commons.core.reflect.Methods; import tc.oc.commons.core.util.TypeMap; @ImplementedBy(ModelDispatcherImpl.class) public interface ModelDispatcher { void subscribe(ModelListener listener); void unsubscribe(ModelListener listener); void modelUpdated(@Nullable Model before, @Nullable Model after, Model latest); } @Singleton class ModelDispatcherImpl implements ModelDispatcher { private final Logger logger; private final TypeMap<Model, ModelHandler> handlers = TypeMap.create(); private final SetMultimap<ModelListener, Map.Entry<TypeToken<? extends Model>, ModelHandler>> listeners = HashMultimap.create(); private static <T extends Model> TypeToken<ModelHandler<T>> handlerType(TypeToken<T> model) { return new TypeToken<ModelHandler<T>>(){}.where(new TypeParameter<T>(){}, model); } @Inject ModelDispatcherImpl(Loggers loggers, TypeMap<Model, ModelHandler> staticHandlers, Set<ModelListener> staticListeners) { this.logger = loggers.get(getClass()); handlers.putAll(staticHandlers); // Add all statically bound ModelHandlers staticListeners.forEach(this::subscribe); // Subscribe all statically bound ModelListeners } @Override public void subscribe(ModelListener listener) { final TypeToken<? extends ModelListener> listenerType = TypeToken.of(listener.getClass()); Methods.declaredMethodsInAncestors(listener.getClass()) .filter(Annotations.annotatedWith(ModelListener.HandleModel.class)).forEach(method -> { final TypeToken<? extends Model> model = (TypeToken<? extends Model>) listenerType.method(method).getParameters().get(0).getType(); final ModelHandler<? extends Model> handler = Methods.lambda(handlerType(model), method, listener); handlers.put(model, handler); listeners.put(listener, Maps.immutableEntry(model, handler)); logger.fine(() -> "Dispatching " + model + " updates to " + Methods.describe(listener.getClass(), method.getName())); }); } @Override public void unsubscribe(ModelListener listener) { listeners.removeAll(listener).forEach(entry -> handlers.remove(entry.getKey(), entry.getValue())); } @Override public void modelUpdated(@Nullable Model before, @Nullable Model after, Model latest) { for(ModelHandler handler : handlers.allAssignableFrom(latest.getClass())) { handler.modelUpdated(before, after, latest); } } }