diff --git a/build.gradle b/build.gradle
index 85d6d86..826bc55 100644
--- a/build.gradle
+++ b/build.gradle
@@ -103,6 +103,8 @@
runtimeCommon 'net.openhft:koloboke-impl-jdk8:0.6.8'
runtime 'mysql:mysql-connector-java:5.1.31'
+
+ testCompile "org.spockframework:spock-core:1.1-groovy-2.4-rc-1"
}
task injectVersion(type: SpeicialClassTransformTask) {
diff --git a/src/main/java/org/ultramine/core/service/InjectService.java b/src/main/java/org/ultramine/core/service/InjectService.java
new file mode 100644
index 0000000..8451c0d
--- /dev/null
+++ b/src/main/java/org/ultramine/core/service/InjectService.java
@@ -0,0 +1,24 @@
+package org.ultramine.core.service;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * For any annotated field bytecode transformation will be applied for the corresponding service provider injection.
+ * The field should be declared as static and non-final. After transformation field become final. Example:
+ *
+ * {@literal @}InjectService
+ * private static SomeService service;
+ *
+ * Will be transformed to:
+ *
+ * private static final SomeService service = (SomeService) ServiceBytecodeAdapter.provideService(SomeService.class);
+ *
+ */
+@Retention(value = RetentionPolicy.RUNTIME)
+@Target(value = ElementType.FIELD)
+public @interface InjectService
+{
+}
diff --git a/src/main/java/org/ultramine/core/service/Service.java b/src/main/java/org/ultramine/core/service/Service.java
new file mode 100644
index 0000000..4f12d6e
--- /dev/null
+++ b/src/main/java/org/ultramine/core/service/Service.java
@@ -0,0 +1,15 @@
+package org.ultramine.core.service;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(value = RetentionPolicy.RUNTIME)
+@Target(value = ElementType.TYPE)
+public @interface Service
+{
+ String name() default "";
+ String description() default "";
+ boolean singleProvider() default false;
+}
diff --git a/src/main/java/org/ultramine/core/service/ServiceBytecodeAdapter.java b/src/main/java/org/ultramine/core/service/ServiceBytecodeAdapter.java
new file mode 100644
index 0000000..cd22310
--- /dev/null
+++ b/src/main/java/org/ultramine/core/service/ServiceBytecodeAdapter.java
@@ -0,0 +1,18 @@
+package org.ultramine.core.service;
+
+import org.ultramine.server.service.UMServiceManager;
+
+public class ServiceBytecodeAdapter
+{
+ private static ServiceManager manager = new UMServiceManager();
+
+ static
+ {
+ manager.register(ServiceManager.class, manager, 0);
+ }
+
+ public static Object provideService(Class> serviceClass)
+ {
+ return manager.provide(serviceClass);
+ }
+}
diff --git a/src/main/java/org/ultramine/core/service/ServiceDelegate.java b/src/main/java/org/ultramine/core/service/ServiceDelegate.java
new file mode 100644
index 0000000..e70d1d7
--- /dev/null
+++ b/src/main/java/org/ultramine/core/service/ServiceDelegate.java
@@ -0,0 +1,12 @@
+package org.ultramine.core.service;
+
+import javax.annotation.Nullable;
+
+public interface ServiceDelegate
+{
+ void setProvider(T obj);
+
+ @Nullable T getProvider();
+
+ T asService();
+}
diff --git a/src/main/java/org/ultramine/core/service/ServiceManager.java b/src/main/java/org/ultramine/core/service/ServiceManager.java
new file mode 100644
index 0000000..b3d5744
--- /dev/null
+++ b/src/main/java/org/ultramine/core/service/ServiceManager.java
@@ -0,0 +1,13 @@
+package org.ultramine.core.service;
+
+import javax.annotation.Nonnull;
+
+@Service
+public interface ServiceManager
+{
+ void register(@Nonnull Class serviceClass, @Nonnull T provider, int priority);
+
+ void 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/ServiceProviderLoader.java b/src/main/java/org/ultramine/core/service/ServiceProviderLoader.java
new file mode 100644
index 0000000..06da99d
--- /dev/null
+++ b/src/main/java/org/ultramine/core/service/ServiceProviderLoader.java
@@ -0,0 +1,8 @@
+package org.ultramine.core.service;
+
+public interface ServiceProviderLoader
+{
+ void load(ServiceDelegate service);
+
+ void unload();
+}
diff --git a/src/main/java/org/ultramine/core/service/ServiceSwitchEvent.java b/src/main/java/org/ultramine/core/service/ServiceSwitchEvent.java
new file mode 100644
index 0000000..0602c36
--- /dev/null
+++ b/src/main/java/org/ultramine/core/service/ServiceSwitchEvent.java
@@ -0,0 +1,60 @@
+package org.ultramine.core.service;
+
+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> newProviderLoader;
+
+ public ServiceSwitchEvent(Class> serviceClass, ServiceDelegate> delegate, ServiceProviderLoader> oldProviderLoader, @Nonnull ServiceProviderLoader> newProviderLoader)
+ {
+ this.serviceClass = serviceClass;
+ this.delegate = delegate;
+ this.oldProviderLoader = oldProviderLoader;
+ this.newProviderLoader = newProviderLoader;
+ }
+
+ public ServiceDelegate> getServiceDelegate()
+ {
+ return delegate;
+ }
+
+ public Class> getServiceClass()
+ {
+ return serviceClass;
+ }
+
+ @Nullable
+ public ServiceProviderLoader> getOldProviderLoader()
+ {
+ return oldProviderLoader;
+ }
+
+ @Nonnull
+ public ServiceProviderLoader> getNewProviderLoader()
+ {
+ return newProviderLoader;
+ }
+
+ public static class Pre extends ServiceSwitchEvent
+ {
+ public Pre(Class> serviceClass, ServiceDelegate> delegate, ServiceProviderLoader> oldProvider, @Nonnull ServiceProviderLoader> newProvider)
+ {
+ super(serviceClass, delegate, oldProvider, newProvider);
+ }
+ }
+
+ public static class Post extends ServiceSwitchEvent
+ {
+ public Post(Class> serviceClass, ServiceDelegate> delegate, ServiceProviderLoader> oldProvider, @Nonnull ServiceProviderLoader> newProvider)
+ {
+ super(serviceClass, delegate, oldProvider, newProvider);
+ }
+ }
+}
diff --git a/src/main/java/org/ultramine/server/asm/transformers/ServiceInjectionTransformer.java b/src/main/java/org/ultramine/server/asm/transformers/ServiceInjectionTransformer.java
new file mode 100644
index 0000000..49ff791
--- /dev/null
+++ b/src/main/java/org/ultramine/server/asm/transformers/ServiceInjectionTransformer.java
@@ -0,0 +1,88 @@
+package org.ultramine.server.asm.transformers;
+
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.tree.AnnotationNode;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.FieldInsnNode;
+import org.objectweb.asm.tree.FieldNode;
+import org.objectweb.asm.tree.InsnList;
+import org.objectweb.asm.tree.InsnNode;
+import org.objectweb.asm.tree.LdcInsnNode;
+import org.objectweb.asm.tree.MethodInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+import org.objectweb.asm.tree.TypeInsnNode;
+import org.ultramine.server.asm.UMTBatchTransformer.IUMClassTransformer;
+import org.ultramine.server.asm.UMTBatchTransformer.TransformResult;
+
+import javax.annotation.Nonnull;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ServiceInjectionTransformer implements IUMClassTransformer
+{
+ private static final String INJECT_SERVICE_DESC = "Lorg/ultramine/core/service/InjectService;";
+ private static final String SBA_CLASS = "org/ultramine/core/service/ServiceBytecodeAdapter";
+
+ @Nonnull
+ @Override
+ public TransformResult transform(String name, String transformedName, ClassReader classReader, ClassNode classNode)
+ {
+ List fieldsToInject = new ArrayList<>();
+ for(FieldNode f : classNode.fields)
+ if(f.visibleAnnotations != null)
+ for(AnnotationNode ann : f.visibleAnnotations)
+ if(ann.desc.equals(INJECT_SERVICE_DESC))
+ fieldsToInject.add(f);
+
+ if(fieldsToInject.size() > 0)
+ {
+ for(FieldNode field : fieldsToInject)
+ {
+ if((field.access & Opcodes.ACC_STATIC) == 0)
+ throw new RuntimeException("Service injection for non-static fields is not supported: " + classNode.name + "#" + field.name);
+ if((field.access & Opcodes.ACC_FINAL) != 0)
+ throw new RuntimeException("Service injection for final fields is not supported: " + classNode.name + "#" + field.name);
+ }
+
+ for(FieldNode field : fieldsToInject)
+ field.access |= Opcodes.ACC_FINAL;
+
+ boolean clinitFound = false;
+ for(MethodNode method : classNode.methods)
+ {
+ if(method.name.equals(""))
+ {
+ for(FieldNode field : fieldsToInject)
+ method.instructions.insert(buildInjectorFor(classNode.name, field));
+ clinitFound = true;
+ break;
+ }
+ }
+
+ if(!clinitFound)
+ {
+ MethodNode method = new MethodNode(Opcodes.ACC_STATIC, "", "()V", null, null);
+ for(FieldNode field : fieldsToInject)
+ method.instructions.insert(buildInjectorFor(classNode.name, field));
+ method.instructions.add(new InsnNode(Opcodes.RETURN));
+ classNode.methods.add(method);
+ }
+
+ return TransformResult.MODIFIED_STACK;
+ }
+
+ return TransformResult.NOT_MODIFIED;
+ }
+
+ private static InsnList buildInjectorFor(String owner, FieldNode field)
+ {
+ InsnList list = new InsnList();
+ list.add(new LdcInsnNode(Type.getType(field.desc)));
+ list.add(new MethodInsnNode(Opcodes.INVOKESTATIC, SBA_CLASS, "provideService", "(Ljava/lang/Class;)Ljava/lang/Object;", false));
+ list.add(new TypeInsnNode(Opcodes.CHECKCAST, field.desc.substring(1, field.desc.length()-1)));
+ list.add(new FieldInsnNode(Opcodes.PUTSTATIC, owner, field.name, field.desc));
+ return list;
+ }
+}
diff --git a/src/main/java/org/ultramine/server/asm/transformers/UMTransformerCollection.java b/src/main/java/org/ultramine/server/asm/transformers/UMTransformerCollection.java
index abe143e..2757660 100644
--- a/src/main/java/org/ultramine/server/asm/transformers/UMTransformerCollection.java
+++ b/src/main/java/org/ultramine/server/asm/transformers/UMTransformerCollection.java
@@ -8,6 +8,7 @@
{
registerGlobalTransformer(new PrintStackTraceTransformer());
registerGlobalTransformer(new TrigMathTransformer());
+ registerGlobalTransformer(new ServiceInjectionTransformer());
registerSpecialTransformer(new BlockLeavesBaseFixer(), "net.minecraft.block.BlockLeavesBase");
}
}
diff --git a/src/main/java/org/ultramine/server/service/ServiceDelegateGenerator.java b/src/main/java/org/ultramine/server/service/ServiceDelegateGenerator.java
new file mode 100644
index 0000000..b3920c1
--- /dev/null
+++ b/src/main/java/org/ultramine/server/service/ServiceDelegateGenerator.java
@@ -0,0 +1,128 @@
+package org.ultramine.server.service;
+
+import static org.objectweb.asm.Opcodes.*;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Type;
+import org.ultramine.server.util.UnsafeUtil;
+
+import org.ultramine.core.service.ServiceDelegate;
+import sun.misc.Unsafe;
+
+public class ServiceDelegateGenerator
+{
+ private static final Unsafe U = UnsafeUtil.getUnsafe();
+
+ @SuppressWarnings("unchecked")
+ public static Class> makeServiceDelegate(Class> base, String name, Class iface)
+ {
+ return (Class>) U.defineAnonymousClass(base, makeServiceDelegate(name, iface), null);
+ }
+
+ public static byte[] makeServiceDelegate(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, "java/lang/Object", new String[]{ ifaceInternalName, Type.getInternalName(ServiceDelegate.class) });
+ cw.visitSource(".dynamic", null);
+
+ {
+ cw.visitField(ACC_PUBLIC, "instance", ifaceDesc, null, null).visitEnd();
+ }
+
+ {
+ MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 0);
+ mv.visitEnd();
+ }
+
+ {
+ MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "setProvider", "(Ljava/lang/Object;)V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitTypeInsn(CHECKCAST, ifaceInternalName);
+ mv.visitFieldInsn(PUTFIELD, thisClassInternalName, "instance", ifaceDesc);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 0);
+ mv.visitEnd();
+ }
+
+ {
+ MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "getProvider", "()Ljava/lang/Object;", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitFieldInsn(GETFIELD, thisClassInternalName, "instance", ifaceDesc);
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(0, 0);
+ mv.visitEnd();
+ }
+
+ {
+ MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "asService", "()Ljava/lang/Object;", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(0, 0);
+ mv.visitEnd();
+ }
+
+ for(Method method : iface.getDeclaredMethods())
+ {
+ 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();
+ }
+
+ private static int loadInsnForType(Class> cls)
+ {
+ if(cls == boolean.class || cls == byte.class || cls == short.class || cls == int.class) return ILOAD;
+ if(cls == long.class) return LLOAD;
+ if(cls == float.class) return FLOAD;
+ if(cls == double.class) return DLOAD;
+ return ALOAD;
+ }
+
+ private static int returnInsnForType(Class> cls)
+ {
+ if(cls == boolean.class || cls == byte.class || cls == short.class || cls == int.class) return IRETURN;
+ if(cls == long.class) return LRETURN;
+ if(cls == float.class) return FRETURN;
+ if(cls == double.class) return DRETURN;
+ if(cls == void.class) return RETURN;
+ return ARETURN;
+ }
+}
diff --git a/src/main/java/org/ultramine/server/service/UMServiceManager.java b/src/main/java/org/ultramine/server/service/UMServiceManager.java
new file mode 100644
index 0000000..ca214ab
--- /dev/null
+++ b/src/main/java/org/ultramine/server/service/UMServiceManager.java
@@ -0,0 +1,160 @@
+package org.ultramine.server.service;
+
+import net.minecraftforge.common.MinecraftForge;
+import org.ultramine.core.service.Service;
+import org.ultramine.core.service.ServiceDelegate;
+import org.ultramine.core.service.ServiceManager;
+import org.ultramine.core.service.ServiceProviderLoader;
+import org.ultramine.core.service.ServiceSwitchEvent;
+
+import javax.annotation.Nonnull;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class UMServiceManager implements ServiceManager
+{
+ private final Map, ServiceWrapper> services = new HashMap<>();
+
+ private @Nonnull ServiceWrapper getOrCreateService(Class serviceClass)
+ {
+ @SuppressWarnings("unchecked")
+ ServiceWrapper service = services.get(serviceClass);
+ if(service == null)
+ {
+ Service desc = serviceClass.getAnnotation(Service.class);
+ if(desc == null)
+ throw new IllegalArgumentException("Given class is not a service class: "+serviceClass.getName());
+ ServiceDelegate delegate;
+ try {
+ delegate = ServiceDelegateGenerator.makeServiceDelegate(getClass(), serviceClass.getSimpleName() + "_delegate", serviceClass).newInstance();
+ } catch(InstantiationException | IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ service = new ServiceWrapper<>(serviceClass, delegate, desc);
+ services.put(serviceClass, service);
+ }
+ return service;
+ }
+
+ @Override
+ public void register(@Nonnull Class serviceClass, @Nonnull T provider, int priority)
+ {
+ serviceClass.getClass(); // NPE
+ provider.getClass(); // NPE
+ register(serviceClass, new SimpleServiceProviderLoader<>(provider), priority);
+ }
+
+ @Override
+ public void 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));
+ }
+
+ @Nonnull
+ @Override
+ public T provide(@Nonnull Class service)
+ {
+ return getOrCreateService(service).provide();
+ }
+
+ private static class ServiceWrapper
+ {
+ private final Class serviceClass;
+ private final ServiceDelegate delegate;
+ private final Service desc;
+ private final List> providers = new ArrayList<>();
+ private ServiceProviderRegistration currentProvider;
+
+ public ServiceWrapper(Class serviceClass, ServiceDelegate delegate, Service desc)
+ {
+ this.serviceClass = serviceClass;
+ this.delegate = delegate;
+ this.desc = desc;
+ }
+
+ public Service getDesc()
+ {
+ return desc;
+ }
+
+ private void switchTo(ServiceProviderRegistration newProvider)
+ {
+ if(providers.isEmpty())
+ 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));
+ if(oldProvider != null)
+ oldProvider.providerLoader.unload();
+ newProvider.providerLoader.load(delegate);
+ currentProvider = newProvider;
+ MinecraftForge.EVENT_BUS.post(new ServiceSwitchEvent.Post(serviceClass, delegate, oldProvider == null ? null : oldProvider.providerLoader, newProvider.providerLoader));
+ }
+
+ public void 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)
+ switchTo(provider);
+ }
+
+ public T provide()
+ {
+ return delegate.asService();
+ }
+ }
+
+ private static class ServiceProviderRegistration implements Comparable
+ {
+ public final ServiceProviderLoader providerLoader;
+ public final int priority;
+
+ private ServiceProviderRegistration(ServiceProviderLoader providerLoader, int priority)
+ {
+ this.providerLoader = providerLoader;
+ this.priority = priority;
+ }
+
+ public int compareTo(ServiceProviderRegistration o)
+ {
+ return Integer.compare(priority, o.priority);
+ }
+ }
+
+ private static class SimpleServiceProviderLoader implements ServiceProviderLoader
+ {
+ public final T provider;
+
+ private SimpleServiceProviderLoader(T provider)
+ {
+ this.provider = provider;
+ }
+
+ @Override
+ public void load(ServiceDelegate service)
+ {
+ service.setProvider(provider);
+ }
+
+ @Override
+ public void unload()
+ {
+
+ }
+
+ @Override
+ public String toString()
+ {
+ return "SimpleServiceProviderLoader{" +
+ "provider=" + provider +
+ '}';
+ }
+ }
+}
diff --git a/src/test/java/org/ultramine/service/ServiceDelegateGeneratorTest.groovy b/src/test/java/org/ultramine/service/ServiceDelegateGeneratorTest.groovy
new file mode 100644
index 0000000..724d3f5
--- /dev/null
+++ b/src/test/java/org/ultramine/service/ServiceDelegateGeneratorTest.groovy
@@ -0,0 +1,58 @@
+package org.ultramine.service
+
+import org.ultramine.core.service.ServiceDelegate
+import org.ultramine.server.service.ServiceDelegateGenerator
+import spock.lang.Specification
+
+class ServiceDelegateGeneratorTest extends Specification {
+ def "MakeInterfaceDelegate"() {
+ setup:
+ ServiceDelegate delegate = ServiceDelegateGenerator.makeServiceDelegate(getClass(), "qwe", TestInterface.class).newInstance();
+ def receiver = Mock(TestInterface)
+ delegate.setProvider(receiver)
+ def wrapper = (TestInterface) delegate;
+
+ expect:
+ wrapper == delegate.asService()
+ receiver == delegate.getProvider()
+
+ when:
+ wrapper.testBooleanArg(true)
+ wrapper.testByteArg((byte) 1)
+ wrapper.testShortArg((short) 1)
+ wrapper.testIntArg(1)
+ wrapper.testLongArg(Long.MAX_VALUE, 1)
+ wrapper.testPrimitives(false, (byte)1, (short)2, 3, 4L, 5f, 6d);
+ wrapper.testObject("123")
+ then:
+ 1 * receiver.testBooleanArg(true);
+ 1 * receiver.testByteArg(1);
+ 1 * receiver.testShortArg(1)
+ 1 * receiver.testIntArg(1)
+ 1 * receiver.testLongArg(Long.MAX_VALUE, 1)
+ 1 * receiver.testPrimitives(false, 1, 2, 3, 4L, 5f, 6d)
+ 1 * receiver.testObject("123")
+
+ when:
+ receiver.testReturnInt() >> 15
+ receiver.testReturnLong() >> 16
+ receiver.testReturnObject() >> "123"
+ then:
+ wrapper.testReturnInt() == 15
+ wrapper.testReturnLong() == 16
+ wrapper.testReturnObject() == "123"
+ }
+
+ interface TestInterface {
+ void testBooleanArg(boolean b);
+ void testByteArg(byte b);
+ void testShortArg(short b);
+ void testIntArg(int b);
+ void testLongArg(long b, long b1);
+ void testPrimitives(boolean b, byte bt, short s, int i, long l, float f, double d);
+ void testObject(String str)
+ int testReturnInt();
+ long testReturnLong();
+ String testReturnObject();
+ }
+}