From 59b92be93f7e78cb41c1bd46ea97168fde3ee8bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Fri, 4 Dec 2020 23:44:00 +0100 Subject: [PATCH] Working on generalizing device bus to allow different device types. Make bus elements source of identifiers in API, too, since they are also in implementation. --- src/main/java/li/cil/oc2/api/API.java | 10 +- src/main/java/li/cil/oc2/api/bus/Device.java | 14 ++ .../java/li/cil/oc2/api/bus/DeviceBus.java | 11 +- .../cil/oc2/api/bus/DeviceBusController.java | 25 +-- .../li/cil/oc2/api/bus/DeviceBusElement.java | 27 +++- .../api/bus/device/AbstractDeviceMethod.java | 50 ------ .../li/cil/oc2/api/bus/device/Device.java | 42 ----- .../oc2/api/bus/device/object/Callback.java | 6 +- .../oc2/api/bus/device/object/Callbacks.java | 117 ++++++++++++-- .../api/bus/device/object/NamedDevice.java | 6 +- ...DeviceInterface.java => ObjectDevice.java} | 24 +-- .../bus/device/object/ObjectDeviceMethod.java | 108 ------------- .../api/bus/device/rpc/AbstractRPCMethod.java | 50 ++++++ .../RPCDevice.java} | 29 ++-- .../{DeviceMethod.java => rpc/RPCMethod.java} | 14 +- .../RPCParameter.java} | 8 +- .../oc2/api/bus/device/rpc/package-info.java | 7 + ...rfaceProvider.java => DeviceProvider.java} | 23 +-- .../li/cil/oc2/api/provider/DeviceQuery.java | 4 +- .../li/cil/oc2/api/vm/InterruptAllocator.java | 9 ++ .../api/vm/MemoryMappedDeviceReference.java | 10 ++ .../cil/oc2/api/vm/VirtualMachineContext.java | 12 ++ src/main/java/li/cil/oc2/common/IMC.java | 4 +- .../common/bus/AbstractDeviceBusElement.java | 69 +++++++++ .../li/cil/oc2/common/bus/RPCAdapter.java | 144 +++++++++++++++--- .../bus/TileEntityDeviceBusController.java | 61 +++----- .../bus/TileEntityDeviceBusElement.java | 75 ++++----- .../DeviceBusControllerCapability.java | 13 +- .../DeviceBusElementCapability.java | 12 +- .../li/cil/oc2/common/device/DeviceImpl.java | 67 -------- ...faceCollection.java => RPCDeviceList.java} | 18 +-- ...yAnyTileEntityDeviceInterfaceProvider.java | 12 -- ...CapabilityAnyTileEntityDeviceProvider.java | 12 ++ ...ctCapabilityTileEntityDeviceProvider.java} | 12 +- ... => AbstractTileEntityDeviceProvider.java} | 15 +- ...Provider.java => BlockDeviceProvider.java} | 12 +- ....java => EnergyStorageDeviceProvider.java} | 12 +- ...r.java => FluidHandlerDeviceProvider.java} | 12 +- ...er.java => ItemHandlerDeviceProvider.java} | 12 +- .../oc2/common/device/provider/Providers.java | 53 +++---- ...der.java => TileEntityDeviceProvider.java} | 12 +- .../oc2/common/tile/BusCableTileEntity.java | 2 +- .../oc2/common/tile/ComputerTileEntity.java | 20 ++- .../oc2/common/vm/InterruptAllocatorImpl.java | 52 +++++++ .../serializers/DeviceJsonSerializer.java | 28 ---- ...RPCDeviceWithIdentifierJsonSerializer.java | 28 ++++ ...izer.java => RPCMethodJsonSerializer.java} | 12 +- .../java/li/cil/oc2/bus/DeviceBusTests.java | 15 +- .../li/cil/oc2/vm/AbstractTestMethod.java | 14 +- .../java/li/cil/oc2/vm/RPCAdapterTests.java | 65 ++++---- 50 files changed, 803 insertions(+), 666 deletions(-) create mode 100644 src/main/java/li/cil/oc2/api/bus/Device.java delete mode 100644 src/main/java/li/cil/oc2/api/bus/device/AbstractDeviceMethod.java delete mode 100644 src/main/java/li/cil/oc2/api/bus/device/Device.java rename src/main/java/li/cil/oc2/api/bus/device/object/{ObjectDeviceInterface.java => ObjectDevice.java} (74%) delete mode 100644 src/main/java/li/cil/oc2/api/bus/device/object/ObjectDeviceMethod.java create mode 100644 src/main/java/li/cil/oc2/api/bus/device/rpc/AbstractRPCMethod.java rename src/main/java/li/cil/oc2/api/bus/device/{DeviceInterface.java => rpc/RPCDevice.java} (53%) rename src/main/java/li/cil/oc2/api/bus/device/{DeviceMethod.java => rpc/RPCMethod.java} (88%) rename src/main/java/li/cil/oc2/api/bus/device/{DeviceMethodParameter.java => rpc/RPCParameter.java} (79%) create mode 100644 src/main/java/li/cil/oc2/api/bus/device/rpc/package-info.java rename src/main/java/li/cil/oc2/api/provider/{DeviceInterfaceProvider.java => DeviceProvider.java} (68%) create mode 100644 src/main/java/li/cil/oc2/api/vm/InterruptAllocator.java create mode 100644 src/main/java/li/cil/oc2/api/vm/MemoryMappedDeviceReference.java create mode 100644 src/main/java/li/cil/oc2/api/vm/VirtualMachineContext.java create mode 100644 src/main/java/li/cil/oc2/common/bus/AbstractDeviceBusElement.java delete mode 100644 src/main/java/li/cil/oc2/common/device/DeviceImpl.java rename src/main/java/li/cil/oc2/common/device/{DeviceInterfaceCollection.java => RPCDeviceList.java} (62%) delete mode 100644 src/main/java/li/cil/oc2/common/device/provider/AbstractCapabilityAnyTileEntityDeviceInterfaceProvider.java create mode 100644 src/main/java/li/cil/oc2/common/device/provider/AbstractCapabilityAnyTileEntityDeviceProvider.java rename src/main/java/li/cil/oc2/common/device/provider/{AbstractCapabilityTileEntityDeviceInterfaceProvider.java => AbstractCapabilityTileEntityDeviceProvider.java} (56%) rename src/main/java/li/cil/oc2/common/device/provider/{AbstractTileEntityDeviceInterfaceProvider.java => AbstractTileEntityDeviceProvider.java} (53%) rename src/main/java/li/cil/oc2/common/device/provider/{BlockDeviceInterfaceProvider.java => BlockDeviceProvider.java} (71%) rename src/main/java/li/cil/oc2/common/device/provider/{EnergyStorageDeviceInterfaceProvider.java => EnergyStorageDeviceProvider.java} (67%) rename src/main/java/li/cil/oc2/common/device/provider/{FluidHandlerDeviceInterfaceProvider.java => FluidHandlerDeviceProvider.java} (67%) rename src/main/java/li/cil/oc2/common/device/provider/{ItemHandlerDeviceInterfaceProvider.java => ItemHandlerDeviceProvider.java} (66%) rename src/main/java/li/cil/oc2/common/device/provider/{TileEntityDeviceInterfaceProvider.java => TileEntityDeviceProvider.java} (57%) create mode 100644 src/main/java/li/cil/oc2/common/vm/InterruptAllocatorImpl.java delete mode 100644 src/main/java/li/cil/oc2/serialization/serializers/DeviceJsonSerializer.java create mode 100644 src/main/java/li/cil/oc2/serialization/serializers/RPCDeviceWithIdentifierJsonSerializer.java rename src/main/java/li/cil/oc2/serialization/serializers/{DeviceMethodJsonSerializer.java => RPCMethodJsonSerializer.java} (72%) diff --git a/src/main/java/li/cil/oc2/api/API.java b/src/main/java/li/cil/oc2/api/API.java index 76e89fc8..0d547666 100644 --- a/src/main/java/li/cil/oc2/api/API.java +++ b/src/main/java/li/cil/oc2/api/API.java @@ -1,10 +1,10 @@ package li.cil.oc2.api; import com.google.gson.GsonBuilder; -import li.cil.oc2.api.bus.device.DeviceMethod; import li.cil.oc2.api.bus.device.object.Callback; -import li.cil.oc2.api.provider.DeviceInterfaceProvider; +import li.cil.oc2.api.bus.device.rpc.RPCMethod; import li.cil.oc2.api.imc.DeviceMethodParameterTypeAdapter; +import li.cil.oc2.api.provider.DeviceProvider; import java.lang.reflect.Type; @@ -12,7 +12,7 @@ public final class API { public static final String MOD_ID = "oc2"; /** - * IMC message for registering a {@link DeviceInterfaceProvider}. + * IMC message for registering a {@link DeviceProvider}. *

* Example: *

@@ -27,11 +27,11 @@ public final class API {
      * 

* Must be called with a supplier that provides an instance of {@link DeviceMethodParameterTypeAdapter}. *

- * It can be necessary to register additional serializers when implementing {@link DeviceMethod}s + * It can be necessary to register additional serializers when implementing {@link RPCMethod}s * that use custom parameter types. * * @see GsonBuilder#registerTypeAdapter(Type, Object) - * @see DeviceMethod + * @see RPCMethod * @see Callback */ public static final String IMC_ADD_DEVICE_METHOD_PARAMETER_TYPE_ADAPTER = "addDeviceMethodParameterTypeAdapter"; diff --git a/src/main/java/li/cil/oc2/api/bus/Device.java b/src/main/java/li/cil/oc2/api/bus/Device.java new file mode 100644 index 00000000..8a6c8851 --- /dev/null +++ b/src/main/java/li/cil/oc2/api/bus/Device.java @@ -0,0 +1,14 @@ +package li.cil.oc2.api.bus; + +/** + * Base interface for objects that can be registered as devices on a {@link DeviceBus}. + *

+ * Which types are handled/supported by a bus depends on the {@link DeviceBusController} + * managing the bus. + *

+ * Note that it is strongly encouraged for implementations to provide an overloaded + * {@link #equals(Object)} and {@link #hashCode()} so that identical devices can be + * detected. + */ +public interface Device { +} diff --git a/src/main/java/li/cil/oc2/api/bus/DeviceBus.java b/src/main/java/li/cil/oc2/api/bus/DeviceBus.java index 4605c6e5..0fe4e8a0 100644 --- a/src/main/java/li/cil/oc2/api/bus/DeviceBus.java +++ b/src/main/java/li/cil/oc2/api/bus/DeviceBus.java @@ -1,12 +1,11 @@ package li.cil.oc2.api.bus; -import li.cil.oc2.api.bus.device.Device; -import li.cil.oc2.api.bus.device.DeviceInterface; +import li.cil.oc2.api.bus.device.rpc.RPCDevice; import java.util.Collection; /** - * A device bus provides the interface by which {@link DeviceInterface} can be made available + * A device bus provides the interface by which {@link RPCDevice} can be made available * to a {@link DeviceBusController}, which is usually used by VMs to access devices. */ public interface DeviceBus { @@ -58,10 +57,4 @@ public interface DeviceBus { * have to interact with this interface. */ void scheduleScan(); - -// long addDevice(MemoryMappedDevice device); -// -// void addDevice(final long address, MemoryMappedDevice device); -// -// void removeDevice(MemoryMappedDevice device); } diff --git a/src/main/java/li/cil/oc2/api/bus/DeviceBusController.java b/src/main/java/li/cil/oc2/api/bus/DeviceBusController.java index fa377b85..f2d7db66 100644 --- a/src/main/java/li/cil/oc2/api/bus/DeviceBusController.java +++ b/src/main/java/li/cil/oc2/api/bus/DeviceBusController.java @@ -1,10 +1,6 @@ package li.cil.oc2.api.bus; -import li.cil.oc2.api.bus.device.Device; -import li.cil.oc2.api.bus.device.DeviceInterface; - -import java.util.Collection; -import java.util.Optional; +import java.util.Set; import java.util.UUID; /** @@ -13,7 +9,7 @@ import java.util.UUID; * {@link DeviceBusElement#addController(DeviceBusController)}. *

* This interface is usually provided by VM containers and used to collect connected - * {@link DeviceInterface}s by aggregating the devices that were added to the device bus elements + * {@link Device}s by aggregating the devices that were added to the device bus elements * via {@link DeviceBus#addDevice(Device)}. *

* The only way for {@link DeviceBusElement}s to be added to a bus is for a @@ -52,18 +48,23 @@ public interface DeviceBusController { /** * The list of all devices currently known to this controller. *

- * This is the aggregation of all {@link DeviceInterface} added to all {@link DeviceBusElement}s known + * This is the aggregation of all {@link Device}s added to all {@link DeviceBusElement}s known * to the controller as found during the last scan scheduled via {@link #scheduleBusScan()}. * * @return the list of all devices on the bus managed by this controller. */ - Collection getDevices(); + Set getDevices(); /** - * Get the device with the specified unique identifier, if possible. + * Obtain the unique identifiers for the specified device, if any, as + * provided by the {@link DeviceBusElement}s that provided this device. + *

+ * If the device was added to multiple {@link DeviceBusElement}s this + * may return multiple {@link UUID}s. * - * @param uuid the id of the device to get. - * @return the device with the specified id, if possible. + * @param device the device to get the identifiers for. + * @return the identifiers for the device, if any. + * @see DeviceBusElement#getDeviceIdentifier(Device) */ - Optional getDevice(final UUID uuid); + Set getDeviceIdentifiers(Device device); } diff --git a/src/main/java/li/cil/oc2/api/bus/DeviceBusElement.java b/src/main/java/li/cil/oc2/api/bus/DeviceBusElement.java index 9df659f1..e619f3a4 100644 --- a/src/main/java/li/cil/oc2/api/bus/DeviceBusElement.java +++ b/src/main/java/li/cil/oc2/api/bus/DeviceBusElement.java @@ -1,8 +1,10 @@ package li.cil.oc2.api.bus; -import li.cil.oc2.api.bus.device.Device; +import li.cil.oc2.api.bus.device.rpc.RPCDevice; import java.util.Collection; +import java.util.Optional; +import java.util.UUID; /** * Represents a single connection point on a device bus. @@ -33,14 +35,14 @@ public interface DeviceBusElement extends DeviceBus { * * @param controller the controller to add. */ - void addController(final DeviceBusController controller); + void addController(DeviceBusController controller); /** * Unregisters a controller from this bus element. * * @param controller the controller to remove. */ - void removeController(final DeviceBusController controller); + void removeController(DeviceBusController controller); /** * Returns the list of devices connected specifically by this element. @@ -56,4 +58,23 @@ public interface DeviceBusElement extends DeviceBus { * @return the devices that have been added to this element. */ Collection getLocalDevices(); + + /** + * Returns an identifier unique to the specified device. + *

+ * This id must persist over save/load to prevent code in a running VM losing + * track of the device. Note that some device types (e.g. {@link RPCDevice}s) + * require for an ID to be provided for them to work at all. + *

+ * It is possible for multiple devices to have the same identifier. Typically + * this means they represent a view on the same underlying object. How this is + * handled depends on the device type and may or may not be supported. + *

+ * Only devices retrieved by calling {@link #getLocalDevices()} should be passed + * to this method. + * + * @param device the device to obtain the ID for. + * @return the stable id for the specified device. + */ + Optional getDeviceIdentifier(Device device); } diff --git a/src/main/java/li/cil/oc2/api/bus/device/AbstractDeviceMethod.java b/src/main/java/li/cil/oc2/api/bus/device/AbstractDeviceMethod.java deleted file mode 100644 index 80bdc552..00000000 --- a/src/main/java/li/cil/oc2/api/bus/device/AbstractDeviceMethod.java +++ /dev/null @@ -1,50 +0,0 @@ -package li.cil.oc2.api.bus.device; - -/** - * Convenience base class for {@link DeviceMethod} implementations. - */ -public abstract class AbstractDeviceMethod implements DeviceMethod { - protected final String name; - protected final boolean synchronize; - protected final Class returnType; - protected final DeviceMethodParameter[] parameters; - - protected AbstractDeviceMethod(final String name, final boolean synchronize, final Class returnType, final DeviceMethodParameter... parameters) { - this.name = name; - this.synchronize = synchronize; - this.returnType = returnType; - this.parameters = parameters; - } - - protected AbstractDeviceMethod(final String name, final Class returnType, final DeviceMethodParameter... parameters) { - this(name, false, returnType, parameters); - } - - protected AbstractDeviceMethod(final String name, final boolean synchronize, final DeviceMethodParameter... parameters) { - this(name, synchronize, void.class, parameters); - } - - protected AbstractDeviceMethod(final String name, final DeviceMethodParameter... parameters) { - this(name, false, void.class, parameters); - } - - @Override - public String getName() { - return name; - } - - @Override - public boolean isSynchronized() { - return synchronize; - } - - @Override - public Class getReturnType() { - return returnType; - } - - @Override - public DeviceMethodParameter[] getParameters() { - return parameters; - } -} diff --git a/src/main/java/li/cil/oc2/api/bus/device/Device.java b/src/main/java/li/cil/oc2/api/bus/device/Device.java deleted file mode 100644 index a7214b02..00000000 --- a/src/main/java/li/cil/oc2/api/bus/device/Device.java +++ /dev/null @@ -1,42 +0,0 @@ -package li.cil.oc2.api.bus.device; - -import li.cil.oc2.api.bus.DeviceBus; -import li.cil.oc2.api.provider.DeviceInterfaceProvider; - -import java.util.UUID; - -/** - * Specialization of a device interface that can be referenced by a {@link UUID} and - * represents a device as a whole. - *

- * Implementations will typically act as a top-level wrapper for one singular device - * as viewed by a list of {@link DeviceInterface}s. - *

- * A unique ID is required when adding devices to a {@link DeviceBus} such that they - * may then be referenced. - *

- * Note that {@link DeviceInterfaceProvider}s are not expected to return instances - * implementing this interface, but return regular {@link DeviceInterface}s instead. - */ -public interface Device extends DeviceInterface { - /** - * An id unique to this device. - *

- * This id must persist over save/load to prevent code in a running VM losing - * track of the device. - */ - UUID getUniqueIdentifier(); - - /** - * Returns a possible underlying instance of a device. - *

- * Frequently some {@link DeviceInterface} obtained from a {@link DeviceInterfaceProvider} will be - * wrapped by an instance of this interface. To prevent this leading to duplicated - * device listings this allows accessing the device proper for equality checks. - * - * @return the underlying device. May be this device itself. - */ - default DeviceInterface getIdentifiedDevice() { - return this; - } -} diff --git a/src/main/java/li/cil/oc2/api/bus/device/object/Callback.java b/src/main/java/li/cil/oc2/api/bus/device/object/Callback.java index 1e4b7fd5..f8ed367e 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/object/Callback.java +++ b/src/main/java/li/cil/oc2/api/bus/device/object/Callback.java @@ -1,6 +1,6 @@ package li.cil.oc2.api.bus.device.object; -import li.cil.oc2.api.bus.device.DeviceMethod; +import li.cil.oc2.api.bus.device.rpc.RPCMethod; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -8,10 +8,10 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * Utility annotation to allow generating lists of {@link DeviceMethod}s using + * Utility annotation to allow generating lists of {@link RPCMethod}s using * {@link Callbacks#collectMethods(Object)}. *

- * Intended to be used in classes instances of which are used as a target of {@link ObjectDeviceInterface}. + * Intended to be used in classes instances of which are used as a target of {@link ObjectDevice}. *

* Method parameters are serialized and deserialized using Gson. When using custom * parameter types it may be necessary to register a custom type adapter for them diff --git a/src/main/java/li/cil/oc2/api/bus/device/object/Callbacks.java b/src/main/java/li/cil/oc2/api/bus/device/object/Callbacks.java index 22a19c05..a44b5d64 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/object/Callbacks.java +++ b/src/main/java/li/cil/oc2/api/bus/device/object/Callbacks.java @@ -1,21 +1,24 @@ package li.cil.oc2.api.bus.device.object; -import li.cil.oc2.api.bus.device.DeviceMethod; +import li.cil.oc2.api.bus.device.rpc.AbstractRPCMethod; +import li.cil.oc2.api.bus.device.rpc.RPCMethod; +import li.cil.oc2.api.bus.device.rpc.RPCParameter; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.util.Strings; +import javax.annotation.Nullable; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; +import java.util.*; import java.util.stream.Collectors; /** - * Provides automated extraction of {@link DeviceMethod}s from instances of + * Provides automated extraction of {@link RPCMethod}s from instances of * class with methods annotated with the {@link Callback} annotation. *

- * Prefer using {@link ObjectDeviceInterface} instead of using this class directly. + * Prefer using {@link ObjectDevice} instead of using this class directly. */ public final class Callbacks { private static final Logger LOGGER = LogManager.getLogger(); @@ -23,9 +26,9 @@ public final class Callbacks { /** * Collects all methods annotated with {@link Callback} in the specified object - * and generated {@link DeviceMethod}s for each one. + * and generated {@link RPCMethod}s for each one. *

- * The generated {@link DeviceMethod} will be bound to the passed object and + * The generated {@link RPCMethod} will be bound to the passed object and * can be called without needing to pass the object. *

* For example: @@ -42,10 +45,10 @@ public final class Callbacks { * @param methodContainer an instance of a class with annotated methods. * @return the list of methods extracted from the specified object. */ - public static List collectMethods(final Object methodContainer) { + public static List collectMethods(final Object methodContainer) { final List reflectedMethods = getMethods(methodContainer.getClass()); - final ArrayList methods = new ArrayList<>(); + final ArrayList methods = new ArrayList<>(); for (final Method method : reflectedMethods) { try { methods.add(new ObjectDeviceMethod(methodContainer, method)); @@ -59,7 +62,7 @@ public final class Callbacks { /** * Returns whether any {@link Callback} annotated methods are present on the specified - * object without creating bound {@link DeviceMethod} instances. + * object without creating bound {@link RPCMethod} instances. *

* The specified {@code object} can be an instance or a {@link Class}. * @@ -80,4 +83,94 @@ public final class Callbacks { .filter(m -> m.isAnnotationPresent(Callback.class)) .collect(Collectors.toList())); } + + private static final class ObjectDeviceMethod extends AbstractRPCMethod { + private final MethodHandle handle; + private final String description; + private final String returnValueDescription; + + public ObjectDeviceMethod(final Object target, final Method method) throws IllegalAccessException { + super(method.getName(), + Objects.requireNonNull(method.getAnnotation(Callback.class), "Method without Callback annotation.").synchronize(), + method.getReturnType(), + getParameters(method)); + + final Callback annotation = method.getAnnotation(Callback.class); + this.handle = MethodHandles.lookup().unreflect(method).bindTo(target); + this.description = Strings.isNotBlank(annotation.description()) ? annotation.description() : null; + this.returnValueDescription = Strings.isNotBlank(annotation.returnValueDescription()) ? annotation.returnValueDescription() : null; + } + + @Nullable + @Override + public Object invoke(final Object... parameters) throws Throwable { + return handle.invokeWithArguments(parameters); + } + + @Override + public Optional getDescription() { + return Optional.ofNullable(description); + } + + @Override + public Optional getReturnValueDescription() { + return Optional.ofNullable(returnValueDescription); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final ObjectDeviceMethod that = (ObjectDeviceMethod) o; + return handle.equals(that.handle); + } + + @Override + public int hashCode() { + return Objects.hash(handle); + } + + @Override + public String toString() { + return handle.toString(); + } + + private static RPCParameter[] getParameters(final Method method) { + return Arrays.stream(method.getParameters()) + .map(ReflectionParameter::new) + .toArray(RPCParameter[]::new); + } + + private static final class ReflectionParameter implements RPCParameter { + private final Class type; + @Nullable private final String name; + @Nullable private final String description; + + public ReflectionParameter(final java.lang.reflect.Parameter parameter) { + this.type = parameter.getType(); + + final Parameter annotation = parameter.getAnnotation(Parameter.class); + final boolean hasName = annotation != null && Strings.isNotBlank(annotation.value()); + final boolean hasDescription = annotation != null && Strings.isNotBlank(annotation.description()); + + this.name = hasName ? annotation.value() : null; + this.description = hasDescription ? annotation.description() : null; + } + + @Override + public Class getType() { + return type; + } + + @Override + public Optional getName() { + return Optional.ofNullable(name); + } + + @Override + public Optional getDescription() { + return Optional.ofNullable(description); + } + } + } } diff --git a/src/main/java/li/cil/oc2/api/bus/device/object/NamedDevice.java b/src/main/java/li/cil/oc2/api/bus/device/object/NamedDevice.java index ba19ba5c..2227db85 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/object/NamedDevice.java +++ b/src/main/java/li/cil/oc2/api/bus/device/object/NamedDevice.java @@ -6,14 +6,14 @@ import net.minecraft.tileentity.TileEntity; import java.util.Collection; /** - * This interface is used to declare additional type names for a device on targets of an {@link ObjectDeviceInterface}. + * This interface is used to declare additional type names for a device on targets of an {@link ObjectDevice}. *

- * In particular {@link Block}s and {@link TileEntity}s that contain {@link Callback} methods may implement + * For example: {@link Block}s and {@link TileEntity}s that contain {@link Callback} methods may implement * this interface to provide additional type names. */ public interface NamedDevice { /** - * A list of additional type names to associate with any {@link ObjectDeviceInterface} used + * A list of additional type names to associate with any {@link ObjectDevice} used * to make available methods in an instance of a class implementing this interface. * * @return the list of additional type names. diff --git a/src/main/java/li/cil/oc2/api/bus/device/object/ObjectDeviceInterface.java b/src/main/java/li/cil/oc2/api/bus/device/object/ObjectDevice.java similarity index 74% rename from src/main/java/li/cil/oc2/api/bus/device/object/ObjectDeviceInterface.java rename to src/main/java/li/cil/oc2/api/bus/device/object/ObjectDevice.java index a3fbd407..3e60a055 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/object/ObjectDeviceInterface.java +++ b/src/main/java/li/cil/oc2/api/bus/device/object/ObjectDevice.java @@ -1,7 +1,7 @@ package li.cil.oc2.api.bus.device.object; -import li.cil.oc2.api.bus.device.DeviceInterface; -import li.cil.oc2.api.bus.device.DeviceMethod; +import li.cil.oc2.api.bus.device.rpc.RPCDevice; +import li.cil.oc2.api.bus.device.rpc.RPCMethod; import javax.annotation.Nullable; import java.util.ArrayList; @@ -9,14 +9,14 @@ import java.util.Collections; import java.util.List; /** - * A reflection based implementation of {@link DeviceInterface} using the {@link Callback} - * annotation to discover {@link DeviceMethod}s in a target object via + * A reflection based implementation of {@link RPCDevice} using the {@link Callback} + * annotation to discover {@link RPCMethod}s in a target object via * {@link Callbacks#collectMethods(Object)}. */ -public final class ObjectDeviceInterface implements DeviceInterface { +public final class ObjectDevice implements RPCDevice { private final Object object; private final ArrayList typeNames; - private final List methods; + private final List methods; private final String className; /** @@ -26,7 +26,7 @@ public final class ObjectDeviceInterface implements DeviceInterface { * @param object the object containing methods provided by this device. * @param typeNames the type names of the device. */ - public ObjectDeviceInterface(final Object object, final List typeNames) { + public ObjectDevice(final Object object, final List typeNames) { this.object = object; this.typeNames = new ArrayList<>(typeNames); this.methods = Callbacks.collectMethods(object); @@ -40,12 +40,12 @@ public final class ObjectDeviceInterface implements DeviceInterface { /** * Creates a new object device with methods in the specified object and the specified * type name. For convenience, the type name may be {@code null}, in which case using - * this constructor is equivalent to using {@link #ObjectDeviceInterface(Object)}. + * this constructor is equivalent to using {@link #ObjectDevice(Object)}. * * @param object the object containing methods provided by this device. * @param typeName the type name of the device. */ - public ObjectDeviceInterface(final Object object, @Nullable final String typeName) { + public ObjectDevice(final Object object, @Nullable final String typeName) { this(object, typeName != null ? Collections.singletonList(typeName) : Collections.emptyList()); } @@ -54,7 +54,7 @@ public final class ObjectDeviceInterface implements DeviceInterface { * * @param object the object containing the methods provided by this device. */ - public ObjectDeviceInterface(final Object object) { + public ObjectDevice(final Object object) { this(object, Collections.emptyList()); } @@ -64,7 +64,7 @@ public final class ObjectDeviceInterface implements DeviceInterface { } @Override - public List getMethods() { + public List getMethods() { return methods; } @@ -72,7 +72,7 @@ public final class ObjectDeviceInterface implements DeviceInterface { public boolean equals(@Nullable final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - final ObjectDeviceInterface that = (ObjectDeviceInterface) o; + final ObjectDevice that = (ObjectDevice) o; return object.equals(that.object); } diff --git a/src/main/java/li/cil/oc2/api/bus/device/object/ObjectDeviceMethod.java b/src/main/java/li/cil/oc2/api/bus/device/object/ObjectDeviceMethod.java deleted file mode 100644 index 958991a6..00000000 --- a/src/main/java/li/cil/oc2/api/bus/device/object/ObjectDeviceMethod.java +++ /dev/null @@ -1,108 +0,0 @@ -package li.cil.oc2.api.bus.device.object; - -import li.cil.oc2.api.bus.device.AbstractDeviceMethod; -import li.cil.oc2.api.bus.device.DeviceMethodParameter; -import org.apache.logging.log4j.util.Strings; - -import javax.annotation.Nullable; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.Objects; -import java.util.Optional; - -/** - * Not intended to be instantiated directly, see {@link Callbacks}. - * - * @see Callbacks - */ -public final class ObjectDeviceMethod extends AbstractDeviceMethod { - private final MethodHandle handle; - private final String description; - private final String returnValueDescription; - - public ObjectDeviceMethod(final Object target, final Method method) throws IllegalAccessException { - super(method.getName(), - Objects.requireNonNull(method.getAnnotation(Callback.class), "Method without Callback annotation.").synchronize(), - method.getReturnType(), - getParameters(method)); - - final Callback annotation = method.getAnnotation(Callback.class); - this.handle = MethodHandles.lookup().unreflect(method).bindTo(target); - this.description = Strings.isNotBlank(annotation.description()) ? annotation.description() : null; - this.returnValueDescription = Strings.isNotBlank(annotation.returnValueDescription()) ? annotation.returnValueDescription() : null; - } - - @Nullable - @Override - public Object invoke(final Object... parameters) throws Throwable { - return handle.invokeWithArguments(parameters); - } - - @Override - public Optional getDescription() { - return Optional.ofNullable(description); - } - - @Override - public Optional getReturnValueDescription() { - return Optional.ofNullable(returnValueDescription); - } - - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - final ObjectDeviceMethod that = (ObjectDeviceMethod) o; - return handle.equals(that.handle); - } - - @Override - public int hashCode() { - return Objects.hash(handle); - } - - @Override - public String toString() { - return handle.toString(); - } - - private static DeviceMethodParameter[] getParameters(final Method method) { - return Arrays.stream(method.getParameters()) - .map(ReflectionParameter::new) - .toArray(DeviceMethodParameter[]::new); - } - - private static final class ReflectionParameter implements DeviceMethodParameter { - private final Class type; - @Nullable private final String name; - @Nullable private final String description; - - public ReflectionParameter(final java.lang.reflect.Parameter parameter) { - this.type = parameter.getType(); - - final li.cil.oc2.api.bus.device.object.Parameter annotation = parameter.getAnnotation(li.cil.oc2.api.bus.device.object.Parameter.class); - final boolean hasName = annotation != null && Strings.isNotBlank(annotation.value()); - final boolean hasDescription = annotation != null && Strings.isNotBlank(annotation.description()); - - this.name = hasName ? annotation.value() : null; - this.description = hasDescription ? annotation.description() : null; - } - - @Override - public Class getType() { - return type; - } - - @Override - public Optional getName() { - return Optional.ofNullable(name); - } - - @Override - public Optional getDescription() { - return Optional.ofNullable(description); - } - } -} diff --git a/src/main/java/li/cil/oc2/api/bus/device/rpc/AbstractRPCMethod.java b/src/main/java/li/cil/oc2/api/bus/device/rpc/AbstractRPCMethod.java new file mode 100644 index 00000000..365b4265 --- /dev/null +++ b/src/main/java/li/cil/oc2/api/bus/device/rpc/AbstractRPCMethod.java @@ -0,0 +1,50 @@ +package li.cil.oc2.api.bus.device.rpc; + +/** + * Convenience base class for {@link RPCMethod} implementations. + */ +public abstract class AbstractRPCMethod implements RPCMethod { + protected final String name; + protected final boolean synchronize; + protected final Class returnType; + protected final RPCParameter[] parameters; + + protected AbstractRPCMethod(final String name, final boolean synchronize, final Class returnType, final RPCParameter... parameters) { + this.name = name; + this.synchronize = synchronize; + this.returnType = returnType; + this.parameters = parameters; + } + + protected AbstractRPCMethod(final String name, final Class returnType, final RPCParameter... parameters) { + this(name, false, returnType, parameters); + } + + protected AbstractRPCMethod(final String name, final boolean synchronize, final RPCParameter... parameters) { + this(name, synchronize, void.class, parameters); + } + + protected AbstractRPCMethod(final String name, final RPCParameter... parameters) { + this(name, false, void.class, parameters); + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean isSynchronized() { + return synchronize; + } + + @Override + public Class getReturnType() { + return returnType; + } + + @Override + public RPCParameter[] getParameters() { + return parameters; + } +} diff --git a/src/main/java/li/cil/oc2/api/bus/device/DeviceInterface.java b/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCDevice.java similarity index 53% rename from src/main/java/li/cil/oc2/api/bus/device/DeviceInterface.java rename to src/main/java/li/cil/oc2/api/bus/device/rpc/RPCDevice.java index c217b83d..ad8a302e 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/DeviceInterface.java +++ b/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCDevice.java @@ -1,29 +1,28 @@ -package li.cil.oc2.api.bus.device; +package li.cil.oc2.api.bus.device.rpc; -import li.cil.oc2.api.bus.device.object.ObjectDeviceInterface; -import li.cil.oc2.api.provider.DeviceInterfaceProvider; +import li.cil.oc2.api.bus.Device; +import li.cil.oc2.api.bus.device.object.ObjectDevice; +import li.cil.oc2.api.provider.DeviceProvider; import java.util.List; /** - * Implementations act as an interface for a device. + * Provides an interface for an RPC device, describing the methods that can be + * called on it and the type names it can be detected by/is compatible with. *

- * A {@link DeviceInterface} represents a single view onto some device. One device - * may have multiple {@code DeviceInterfaces} providing different methods for the + * A {@link RPCDevice} may represent a single view onto some device or be a + * collection of multiple aggregated {@link RPCDevice}s. One underlying device + * may have multiple {@link RPCDevice}s providing different methods for the * device. This allows specifying general purpose interfaces which provide logic * for some aspect of an underlying device which may be shared with other devices. *

* The easiest and hence recommended way of implementing this interface is to use - * the {@link ObjectDeviceInterface} class. - *

- * Note that it is strongly encouraged for implementations to provide an overloaded - * {@link #equals(Object)} and {@link #hashCode()} so that identical devices can be - * detected. + * the {@link ObjectDevice} class. * - * @see ObjectDeviceInterface - * @see DeviceInterfaceProvider + * @see ObjectDevice + * @see DeviceProvider */ -public interface DeviceInterface { +public interface RPCDevice extends Device { /** * A list of type names identifying this interface. *

@@ -39,5 +38,5 @@ public interface DeviceInterface { /** * The list of methods provided by this interface. */ - List getMethods(); + List getMethods(); } diff --git a/src/main/java/li/cil/oc2/api/bus/device/DeviceMethod.java b/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCMethod.java similarity index 88% rename from src/main/java/li/cil/oc2/api/bus/device/DeviceMethod.java rename to src/main/java/li/cil/oc2/api/bus/device/rpc/RPCMethod.java index 6fe117b4..97747a96 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/DeviceMethod.java +++ b/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCMethod.java @@ -1,24 +1,24 @@ -package li.cil.oc2.api.bus.device; +package li.cil.oc2.api.bus.device.rpc; import li.cil.oc2.api.bus.DeviceBusController; -import li.cil.oc2.api.bus.device.object.ObjectDeviceInterface; +import li.cil.oc2.api.bus.device.object.ObjectDevice; import javax.annotation.Nullable; import java.util.Optional; /** - * Represents a single method that can be exposed by a {@link DeviceInterface}. + * Represents a single method that can be exposed by a {@link RPCDevice}. *

* The easiest and hence recommended way of implementing this interface is to use - * the {@link ObjectDeviceInterface} class. + * the {@link ObjectDevice} class. *

* Method parameters are serialized and deserialized using Gson. When using custom * parameter types it may be necessary to register a custom type adapter for them * via {@link li.cil.oc2.api.API#IMC_ADD_DEVICE_METHOD_PARAMETER_TYPE_ADAPTER}. * - * @see ObjectDeviceInterface + * @see ObjectDevice */ -public interface DeviceMethod { +public interface RPCMethod { /** * The name of the method. *

@@ -41,7 +41,7 @@ public interface DeviceMethod { /** * The list of parameters this method accepts. */ - DeviceMethodParameter[] getParameters(); + RPCParameter[] getParameters(); /** * Called to run this method. diff --git a/src/main/java/li/cil/oc2/api/bus/device/DeviceMethodParameter.java b/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCParameter.java similarity index 79% rename from src/main/java/li/cil/oc2/api/bus/device/DeviceMethodParameter.java rename to src/main/java/li/cil/oc2/api/bus/device/rpc/RPCParameter.java index 9f8d14b0..2e8c8c6c 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/DeviceMethodParameter.java +++ b/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCParameter.java @@ -1,18 +1,18 @@ -package li.cil.oc2.api.bus.device; +package li.cil.oc2.api.bus.device.rpc; import li.cil.oc2.api.bus.DeviceBusController; import java.util.Optional; /** - * Describes a single parameter of a {@link DeviceMethod}. + * Describes a single parameter of a {@link RPCMethod}. */ -public interface DeviceMethodParameter { +public interface RPCParameter { /** * The type of this parameter. *

* This is used by {@link DeviceBusController}s to convert parameters from a lower - * level representation before passing it to {@link DeviceMethod#invoke(Object...)}. + * level representation before passing it to {@link RPCMethod#invoke(Object...)}. * As such, the types used must be kept simple. As a rule of thumb, only primitives * and POJOs should be used. * diff --git a/src/main/java/li/cil/oc2/api/bus/device/rpc/package-info.java b/src/main/java/li/cil/oc2/api/bus/device/rpc/package-info.java new file mode 100644 index 00000000..b3bef420 --- /dev/null +++ b/src/main/java/li/cil/oc2/api/bus/device/rpc/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +package li.cil.oc2.api.bus.device.rpc; + +import mcp.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/li/cil/oc2/api/provider/DeviceInterfaceProvider.java b/src/main/java/li/cil/oc2/api/provider/DeviceProvider.java similarity index 68% rename from src/main/java/li/cil/oc2/api/provider/DeviceInterfaceProvider.java rename to src/main/java/li/cil/oc2/api/provider/DeviceProvider.java index ba193e0d..69458796 100644 --- a/src/main/java/li/cil/oc2/api/provider/DeviceInterfaceProvider.java +++ b/src/main/java/li/cil/oc2/api/provider/DeviceProvider.java @@ -1,23 +1,24 @@ package li.cil.oc2.api.provider; -import li.cil.oc2.api.bus.device.DeviceInterface; -import li.cil.oc2.api.bus.device.object.ObjectDeviceInterface; +import li.cil.oc2.api.bus.Device; +import li.cil.oc2.api.bus.device.object.ObjectDevice; +import li.cil.oc2.api.bus.device.rpc.RPCDevice; import net.minecraftforge.common.util.LazyOptional; /** - * Allows querying for device interfaces given some context. + * Allows querying for devices given some context. *

* See the specializations of {@link DeviceQuery} for possible queries. *

- * Returning a device interface does not transfer ownership of the device in terms + * Returning a devices does not transfer ownership of the device in terms * of responsibility for persistence. Callers of this method will not attempt * to persist objects returned by this method. It is the responsibility of the provider * to ensure persistence where required. *