diff --git a/src/main/java/org/ultramine/core/service/ServiceDelegate.java b/src/main/java/org/ultramine/core/service/ServiceDelegate.java index e70d1d7..fcef1a0 100644 --- a/src/main/java/org/ultramine/core/service/ServiceDelegate.java +++ b/src/main/java/org/ultramine/core/service/ServiceDelegate.java @@ -1,12 +1,25 @@ package org.ultramine.core.service; -import javax.annotation.Nullable; +import org.ultramine.server.service.NotResolvedServiceProvider; + +import javax.annotation.Nonnull; public interface ServiceDelegate { - void setProvider(T obj); + void setProvider(@Nonnull T obj); - @Nullable T getProvider(); + @Nonnull + T getProvider(); - T asService(); + @Nonnull + @SuppressWarnings("unchecked") + default T asService() + { + return (T) this; + } + + default boolean isResolved() + { + return !(getProvider() instanceof NotResolvedServiceProvider); + } } diff --git a/src/main/java/org/ultramine/core/service/ServiceManager.java b/src/main/java/org/ultramine/core/service/ServiceManager.java index b3d5744..81cb1f7 100644 --- a/src/main/java/org/ultramine/core/service/ServiceManager.java +++ b/src/main/java/org/ultramine/core/service/ServiceManager.java @@ -1,13 +1,19 @@ package org.ultramine.core.service; -import javax.annotation.Nonnull; +import org.ultramine.core.util.Undoable; -@Service +import javax.annotation.Nonnull; +import javax.annotation.concurrent.ThreadSafe; + +@Service( + singleProvider = true +) +@ThreadSafe public interface ServiceManager { - void register(@Nonnull Class serviceClass, @Nonnull T provider, int priority); + Undoable register(@Nonnull Class serviceClass, @Nonnull T provider, int priority); - void register(@Nonnull Class serviceClass, @Nonnull ServiceProviderLoader providerLoader, int priority); + Undoable register(@Nonnull Class serviceClass, @Nonnull ServiceProviderLoader providerLoader, int priority); @Nonnull T provide(@Nonnull Class service); } diff --git a/src/main/java/org/ultramine/core/service/ServiceSwitchEvent.java b/src/main/java/org/ultramine/core/service/ServiceSwitchEvent.java index 0602c36..04a126a 100644 --- a/src/main/java/org/ultramine/core/service/ServiceSwitchEvent.java +++ b/src/main/java/org/ultramine/core/service/ServiceSwitchEvent.java @@ -3,13 +3,12 @@ import cpw.mods.fml.common.eventhandler.Event; import javax.annotation.Nonnull; -import javax.annotation.Nullable; public abstract class ServiceSwitchEvent extends Event { private final Class serviceClass; private final ServiceDelegate delegate; - private final @Nullable ServiceProviderLoader oldProviderLoader; + private final @Nonnull ServiceProviderLoader oldProviderLoader; private final @Nonnull ServiceProviderLoader newProviderLoader; public ServiceSwitchEvent(Class serviceClass, ServiceDelegate delegate, ServiceProviderLoader oldProviderLoader, @Nonnull ServiceProviderLoader newProviderLoader) @@ -30,7 +29,7 @@ return serviceClass; } - @Nullable + @Nonnull public ServiceProviderLoader getOldProviderLoader() { return oldProviderLoader; diff --git a/src/main/java/org/ultramine/server/service/NotResolvedServiceProvider.java b/src/main/java/org/ultramine/server/service/NotResolvedServiceProvider.java new file mode 100644 index 0000000..709d2fb --- /dev/null +++ b/src/main/java/org/ultramine/server/service/NotResolvedServiceProvider.java @@ -0,0 +1,12 @@ +package org.ultramine.server.service; + +public class NotResolvedServiceProvider +{ + // This hack is needed because this class is requested before UMServiceManager registering + public static UMServiceManager services; + + public Object resolveProvider() + { + return services.resolveProvider(this); + } +} diff --git a/src/main/java/org/ultramine/server/service/ServiceDelegateGenerator.java b/src/main/java/org/ultramine/server/service/ServiceDelegateGenerator.java index b3920c1..c58a426 100644 --- a/src/main/java/org/ultramine/server/service/ServiceDelegateGenerator.java +++ b/src/main/java/org/ultramine/server/service/ServiceDelegateGenerator.java @@ -16,7 +16,9 @@ public class ServiceDelegateGenerator { private static final Unsafe U = UnsafeUtil.getUnsafe(); - + private static final String ServiceDelegate_INTERNAL_NAME = Type.getInternalName(ServiceDelegate.class); + private static final String NotImplementedServiceProvider_INTERNAL_NAME = Type.getInternalName(NotResolvedServiceProvider.class); + @SuppressWarnings("unchecked") public static Class> makeServiceDelegate(Class base, String name, Class iface) { @@ -34,7 +36,7 @@ String ifaceInternalName = Type.getInternalName(iface); String ifaceDesc = Type.getDescriptor(iface); - cw.visit(V1_5, ACC_PUBLIC | ACC_SUPER, thisClassInternalName, null, "java/lang/Object", new String[]{ ifaceInternalName, Type.getInternalName(ServiceDelegate.class) }); + cw.visit(V1_5, ACC_PUBLIC | ACC_SUPER, thisClassInternalName, null, "java/lang/Object", new String[]{ ifaceInternalName, ServiceDelegate_INTERNAL_NAME }); cw.visitSource(".dynamic", null); { @@ -73,11 +75,58 @@ mv.visitEnd(); } + for(Method method : iface.getDeclaredMethods()) { - MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "asService", "()Ljava/lang/Object;", null, null); + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, method.getName(), Type.getMethodDescriptor(method), null, null); + mv.visitCode(); + + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, thisClassInternalName, "instance", ifaceDesc); + int argCounter = 1; + for(Parameter par : method.getParameters()) + { + int insn = loadInsnForType(par.getType()); + mv.visitVarInsn(insn, argCounter); + argCounter += insn == LLOAD || insn == DLOAD ? 2 : 1; + } + mv.visitMethodInsn(INVOKEINTERFACE, ifaceInternalName, method.getName(), Type.getMethodDescriptor(method), true); + + mv.visitInsn(returnInsnForType(method.getReturnType())); + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + + cw.visitEnd(); + return cw.toByteArray(); + } + + @SuppressWarnings("unchecked") + public static Class makeNotResolvedServiceProvider(Class base, String name, Class iface) + { + return (Class) U.defineAnonymousClass(base, makeNotResolvedServiceProvider(name, iface), null); + } + + public static byte[] makeNotResolvedServiceProvider(String name, Class iface) + { + if(!iface.isInterface()) + throw new IllegalArgumentException("iface should be an interface"); + + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); + + String thisClassInternalName = name.replace('.', '/'); + String ifaceInternalName = Type.getInternalName(iface); + String ifaceDesc = Type.getDescriptor(iface); + + cw.visit(V1_5, ACC_PUBLIC | ACC_SUPER, thisClassInternalName, null, NotImplementedServiceProvider_INTERNAL_NAME, + new String[]{ ifaceInternalName}); + cw.visitSource(".dynamic", null); + + { + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); - mv.visitInsn(ARETURN); + mv.visitMethodInsn(INVOKESPECIAL, NotImplementedServiceProvider_INTERNAL_NAME, "", "()V", false); + mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } @@ -88,7 +137,7 @@ mv.visitCode(); mv.visitVarInsn(ALOAD, 0); - mv.visitFieldInsn(GETFIELD, thisClassInternalName, "instance", ifaceDesc); + mv.visitMethodInsn(INVOKEVIRTUAL, thisClassInternalName, "resolveProvider", "()Ljava/lang/Object;", false); int argCounter = 1; for(Parameter par : method.getParameters()) { diff --git a/src/main/java/org/ultramine/server/service/UMServiceManager.java b/src/main/java/org/ultramine/server/service/UMServiceManager.java index df47790..e54ff5d 100644 --- a/src/main/java/org/ultramine/server/service/UMServiceManager.java +++ b/src/main/java/org/ultramine/server/service/UMServiceManager.java @@ -7,53 +7,60 @@ import org.ultramine.core.service.ServiceProviderLoader; import org.ultramine.core.service.ServiceStateHandler; import org.ultramine.core.service.ServiceSwitchEvent; +import org.ultramine.core.util.Undoable; import javax.annotation.Nonnull; +import javax.annotation.concurrent.ThreadSafe; import java.util.ArrayList; -import java.util.HashMap; +import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +@ThreadSafe public class UMServiceManager implements ServiceManager { - private final Map, ServiceWrapper> services = new HashMap<>(); + private final Map, ServiceWrapper> services = new ConcurrentHashMap<>(); - private @Nonnull ServiceWrapper getOrCreateService(Class serviceClass) + public UMServiceManager() { - @SuppressWarnings("unchecked") - ServiceWrapper service = services.get(serviceClass); - if(service == null) - { + NotResolvedServiceProvider.services = this; + } + + @SuppressWarnings("unchecked") + private @Nonnull ServiceWrapper getOrCreateService(Class serviceClass1) + { + return ((Map, ServiceWrapper>) (Object) services).computeIfAbsent(serviceClass1, serviceClass -> { Service desc = serviceClass.getAnnotation(Service.class); if(desc == null) throw new IllegalArgumentException("Given class is not a service class: "+serviceClass.getName()); ServiceDelegate delegate; + T notResolvedProvider; try { delegate = ServiceDelegateGenerator.makeServiceDelegate(getClass(), serviceClass.getSimpleName() + "_delegate", serviceClass).newInstance(); + notResolvedProvider = ServiceDelegateGenerator.makeNotResolvedServiceProvider(getClass(), serviceClass.getSimpleName() + "_notResolvedProvider", serviceClass).newInstance(); } catch(InstantiationException | IllegalAccessException e) { throw new RuntimeException(e); } - service = new ServiceWrapper<>(serviceClass, delegate, desc); - services.put(serviceClass, service); - } - return service; + return new ServiceWrapper<>(serviceClass, delegate, desc, new ServiceProviderRegistration<>(new SimpleServiceProviderLoader<>(notResolvedProvider), Integer.MIN_VALUE)); + }); } @Override - public void register(@Nonnull Class serviceClass, @Nonnull T provider, int priority) + public Undoable register(@Nonnull Class serviceClass, @Nonnull T provider, int priority) { serviceClass.getClass(); // NPE provider.getClass(); // NPE - register(serviceClass, new SimpleServiceProviderLoader<>(provider), priority); + return register(serviceClass, new SimpleServiceProviderLoader<>(provider), priority); } @Override - public void register(@Nonnull Class serviceClass, @Nonnull ServiceProviderLoader providerLoader, int priority) + public Undoable register(@Nonnull Class serviceClass, @Nonnull ServiceProviderLoader providerLoader, int priority) { serviceClass.getClass(); // NPE providerLoader.getClass(); // NPE ServiceWrapper service = getOrCreateService(serviceClass); - service.addProvider(new ServiceProviderRegistration<>(providerLoader, priority)); + return service.addProvider(new ServiceProviderRegistration<>(providerLoader, priority)); } @Nonnull @@ -63,19 +70,29 @@ return getOrCreateService(service).provide(); } + Object resolveProvider(NotResolvedServiceProvider provider) + { + Class serviceClass = provider.getClass().getInterfaces()[0]; + return getOrCreateService(serviceClass).resolveProvider(); + } + private static class ServiceWrapper { private final Class serviceClass; private final ServiceDelegate delegate; private final Service desc; + private final ServiceProviderRegistration notResolvedProviderRegistration; private final List> providers = new ArrayList<>(); private ServiceProviderRegistration currentProvider; - public ServiceWrapper(Class serviceClass, ServiceDelegate delegate, Service desc) + public ServiceWrapper(Class serviceClass, ServiceDelegate delegate, Service desc, ServiceProviderRegistration notResolvedRegistration) { this.serviceClass = serviceClass; this.delegate = delegate; this.desc = desc; + this.notResolvedProviderRegistration = notResolvedRegistration; + this.currentProvider = notResolvedProviderRegistration; + notResolvedRegistration.providerLoader.load(delegate); } public Service getDesc() @@ -85,7 +102,7 @@ private void switchTo(ServiceProviderRegistration newProvider) { - if(providers.isEmpty()) + if(providers.isEmpty() && newProvider != notResolvedProviderRegistration) throw new IllegalStateException("Service provider is not registered"); ServiceProviderRegistration oldProvider = currentProvider; MinecraftForge.EVENT_BUS.post(new ServiceSwitchEvent.Pre(serviceClass, delegate, oldProvider == null ? null : oldProvider.providerLoader, newProvider.providerLoader)); @@ -102,20 +119,50 @@ MinecraftForge.EVENT_BUS.post(new ServiceSwitchEvent.Post(serviceClass, delegate, oldProvider == null ? null : oldProvider.providerLoader, newProvider.providerLoader)); } - public void addProvider(ServiceProviderRegistration provider) + private void forceSwitchToMostRelevant() + { + if(providers.isEmpty()) + switchTo(notResolvedProviderRegistration); + else + //noinspection OptionalGetWithoutIsPresent + switchTo(providers.stream().sorted(Comparator.comparingInt((ServiceProviderRegistration o) -> o.priority).reversed()).findFirst().get()); + } + + private boolean isResolved() + { + return currentProvider != notResolvedProviderRegistration; + } + + public synchronized Undoable addProvider(ServiceProviderRegistration provider) { if(desc.singleProvider() && providers.size() != 0) throw new IllegalStateException("Tried to register second provider for single-impl service'"+serviceClass.getName() + "'. First provider: " + providers.get(0).providerLoader + ", second provider: " + provider); providers.add(provider); - if(currentProvider == null || provider.priority >= currentProvider.priority) + if((isResolved() || desc.singleProvider()) && provider.priority >= currentProvider.priority) switchTo(provider); + + return () -> { + synchronized(this) { + providers.remove(provider); + if(isResolved() && provider == currentProvider) + forceSwitchToMostRelevant(); + } + }; } - public T provide() + public synchronized T provide() { return delegate.asService(); } + + public synchronized T resolveProvider() + { + if(providers.isEmpty()) + throw new IllegalStateException("Service provider is not registered for service " + serviceClass); + forceSwitchToMostRelevant(); + return delegate.getProvider(); + } } private static class ServiceProviderRegistration implements Comparable