diff --git a/src/main/java/li/cil/oc2/common/bus/DeviceBusControllerImpl.java b/src/main/java/li/cil/oc2/common/bus/DeviceBusControllerImpl.java index cb1ef0b4..aa7a31b1 100644 --- a/src/main/java/li/cil/oc2/common/bus/DeviceBusControllerImpl.java +++ b/src/main/java/li/cil/oc2/common/bus/DeviceBusControllerImpl.java @@ -1,6 +1,7 @@ package li.cil.oc2.common.bus; -import com.google.gson.*; +import com.google.gson.Gson; +import com.google.gson.JsonArray; import li.cil.ceres.api.Serialized; import li.cil.oc2.api.bus.DeviceBusController; import li.cil.oc2.api.bus.DeviceBusElement; @@ -8,7 +9,12 @@ import li.cil.oc2.api.device.Device; import li.cil.oc2.api.device.DeviceMethod; import li.cil.oc2.api.device.DeviceMethodParameter; import li.cil.oc2.api.device.IdentifiableDevice; +import li.cil.oc2.common.device.DeviceMethodParameterTypeAdapters; import li.cil.oc2.common.util.TileEntityUtils; +import li.cil.oc2.serialization.serializers.DeviceJsonSerializer; +import li.cil.oc2.serialization.serializers.DeviceMethodJsonSerializer; +import li.cil.oc2.serialization.serializers.MessageJsonDeserializer; +import li.cil.oc2.serialization.serializers.MethodInvocationJsonDeserializer; import li.cil.sedna.api.device.Steppable; import li.cil.sedna.api.device.serial.SerialDevice; import net.minecraft.tileentity.TileEntity; @@ -20,7 +26,6 @@ import net.minecraft.world.World; import javax.annotation.Nullable; import java.io.ByteArrayInputStream; import java.io.InputStreamReader; -import java.lang.reflect.Type; import java.nio.ByteBuffer; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -33,22 +38,14 @@ public class DeviceBusControllerImpl implements DeviceBusController, Steppable { } private static final int MAX_BUS_ELEMENT_COUNT = 128; - private static final int DEFAULT_MAX_MESSAGE_SIZE = 32 * 1024; + private static final int DEFAULT_MAX_MESSAGE_SIZE = 4 * 1024; private static final byte[] MESSAGE_DELIMITER = "\0".getBytes(); - // Device -> VM - private static final String MESSAGE_TYPE_STATUS = "status"; - private static final String MESSAGE_TYPE_RESULT = "result"; - private static final String MESSAGE_TYPE_ERROR = "error"; - - private static final String ERROR_MESSAGE_TOO_LARGE = "message too large"; - private static final String ERROR_UNKNOWN_MESSAGE_TYPE = "unknown message type"; - private static final String ERROR_UNKNOWN_DEVICE = "unknown device"; - private static final String ERROR_UNKNOWN_METHOD = "unknown method"; - private static final String ERROR_INVALID_PARAMETER_SIGNATURE = "invalid parameter signature"; - - // VM -> Device - private static final String MESSAGE_TYPE_INVOKE_METHOD = "invoke"; + public static final String ERROR_MESSAGE_TOO_LARGE = "message too large"; + public static final String ERROR_UNKNOWN_MESSAGE_TYPE = "unknown message type"; + public static final String ERROR_UNKNOWN_DEVICE = "unknown device"; + public static final String ERROR_UNKNOWN_METHOD = "unknown method"; + public static final String ERROR_INVALID_PARAMETER_SIGNATURE = "invalid parameter signature"; private final Set elements = new HashSet<>(); private final ConcurrentHashMap devices = new ConcurrentHashMap<>(); @@ -68,12 +65,11 @@ public class DeviceBusControllerImpl implements DeviceBusController, Steppable { public DeviceBusControllerImpl(final SerialDevice serialDevice, final int maxMessageSize) { this.serialDevice = serialDevice; this.transmitBuffer = ByteBuffer.allocate(maxMessageSize); - this.gson = new GsonBuilder() - .serializeNulls() - .registerTypeAdapter(MethodInvocation.class, new MethodInvocationDeserializer()) - .registerTypeAdapter(Message.class, new MessageDeserializer()) - .registerTypeAdapter(Device.class, new DeviceSerializer()) - .registerTypeAdapter(DeviceMethod.class, new DeviceMethodSerializer()) + this.gson = DeviceMethodParameterTypeAdapters.beginBuildGson() + .registerTypeAdapter(MethodInvocation.class, new MethodInvocationJsonDeserializer()) + .registerTypeAdapter(Message.class, new MessageJsonDeserializer()) + .registerTypeAdapter(Device.class, new DeviceJsonSerializer()) + .registerTypeAdapter(DeviceMethod.class, new DeviceMethodJsonSerializer()) .create(); } @@ -171,7 +167,7 @@ public class DeviceBusControllerImpl implements DeviceBusController, Steppable { final ScanEdge edgeIn = new ScanEdge(edge.position, face); seenEdges.add(edgeIn); - final ScanEdge edgeOut = new ScanEdge(edge.position, face.getOpposite()); + final ScanEdge edgeOut = new ScanEdge(edge.position.offset(face), face.getOpposite()); if (seenEdges.add(edgeOut)) { queue.add(edgeOut); } @@ -256,11 +252,11 @@ public class DeviceBusControllerImpl implements DeviceBusController, Steppable { try { final Message message = gson.fromJson(stream, Message.class); switch (message.type) { - case MESSAGE_TYPE_STATUS: { + case Message.MESSAGE_TYPE_STATUS: { writeStatus(); break; } - case MESSAGE_TYPE_INVOKE_METHOD: { + case Message.MESSAGE_TYPE_INVOKE_METHOD: { assert message.data != null : "MethodInvocation deserializer produced null data."; processMethodInvocation((MethodInvocation) message.data, false); break; @@ -317,7 +313,7 @@ public class DeviceBusControllerImpl implements DeviceBusController, Steppable { try { final Object result = method.invoke(parameters); - writeMessage(MESSAGE_TYPE_RESULT, result); + writeMessage(Message.MESSAGE_TYPE_RESULT, result); } catch (final Throwable e) { writeError(e.getMessage()); } @@ -329,11 +325,11 @@ public class DeviceBusControllerImpl implements DeviceBusController, Steppable { } private void writeStatus() { - writeMessage(MESSAGE_TYPE_STATUS, devices.values().toArray(new Device[0])); + writeMessage(Message.MESSAGE_TYPE_STATUS, devices.values().toArray(new Device[0])); } private void writeError(final String message) { - writeMessage(MESSAGE_TYPE_ERROR, message); + writeMessage(Message.MESSAGE_TYPE_ERROR, message); } private void writeMessage(final String type, @Nullable final Object data) { @@ -381,7 +377,15 @@ public class DeviceBusControllerImpl implements DeviceBusController, Steppable { } } - private static final class Message { + public static final class Message { + // Device -> VM + public static final String MESSAGE_TYPE_STATUS = "status"; + public static final String MESSAGE_TYPE_RESULT = "result"; + public static final String MESSAGE_TYPE_ERROR = "error"; + + // VM -> Device + public static final String MESSAGE_TYPE_INVOKE_METHOD = "invoke"; + public final String type; @Nullable public final Object data; @@ -404,93 +408,4 @@ public class DeviceBusControllerImpl implements DeviceBusController, Steppable { } } - private static final class MessageDeserializer implements JsonDeserializer { - @Override - public Message deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException { - final JsonObject jsonObject = json.getAsJsonObject(); - final String messageType = jsonObject.get("type").getAsString(); - final Object messageData; - switch (messageType) { - case MESSAGE_TYPE_STATUS: { - messageData = null; - break; - } - case MESSAGE_TYPE_INVOKE_METHOD: { - messageData = context.deserialize(jsonObject.getAsJsonObject("data"), MethodInvocation.class); - break; - } - default: { - throw new JsonParseException(ERROR_UNKNOWN_MESSAGE_TYPE); - } - } - - return new Message(messageType, messageData); - } - } - - private static final class MethodInvocationDeserializer implements JsonDeserializer { - @Override - public MethodInvocation deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException { - final JsonObject jsonObject = json.getAsJsonObject(); - final UUID deviceId = context.deserialize(jsonObject.get("deviceId"), UUID.class); - final String methodName = jsonObject.get("name").getAsString(); - final JsonArray parameters = jsonObject.getAsJsonArray("parameters"); - return new MethodInvocation(deviceId, methodName, parameters != null ? parameters : new JsonArray()); - } - } - - private static final class DeviceSerializer implements JsonSerializer { - @Override - public JsonElement serialize(final IdentifiableDevice src, final Type typeOfSrc, final JsonSerializationContext context) { - if (src == null) { - return JsonNull.INSTANCE; - } - - final JsonObject deviceJson = new JsonObject(); - deviceJson.add("deviceId", context.serialize(src.getUniqueId())); - deviceJson.add("typeNames", context.serialize(src.getTypeNames())); - - final JsonArray methodsJson = new JsonArray(); - deviceJson.add("methods", methodsJson); - for (final DeviceMethod method : src.getMethods()) { - methodsJson.add(context.serialize(method, DeviceMethod.class)); - } - - return deviceJson; - } - } - - private static final class DeviceMethodSerializer implements JsonSerializer { - @Override - public JsonElement serialize(final DeviceMethod method, final Type typeOfMethod, final JsonSerializationContext context) { - if (method == null) { - return JsonNull.INSTANCE; - } - - final JsonObject methodJson = new JsonObject(); - methodJson.addProperty("name", method.getName()); - methodJson.addProperty("returnType", method.getReturnType().getSimpleName()); - - method.getDescription().ifPresent(s -> methodJson.addProperty("description", s)); - method.getReturnValueDescription().ifPresent(s -> methodJson.addProperty("returnValueDescription", s)); - - final JsonArray parametersJson = new JsonArray(); - methodJson.add("parameters", parametersJson); - - final DeviceMethodParameter[] parameters = method.getParameters(); - for (final DeviceMethodParameter parameter : parameters) { - final JsonObject parameterJson = new JsonObject(); - - parameter.getName().ifPresent(s -> parameterJson.addProperty("name", s)); - parameter.getDescription().ifPresent(s -> parameterJson.addProperty("description", s)); - - final Class type = parameter.getType(); - parameterJson.addProperty("type", type.getSimpleName()); - - parametersJson.add(parameterJson); - } - - return methodJson; - } - } } diff --git a/src/main/java/li/cil/oc2/serialization/serializers/DeviceJsonSerializer.java b/src/main/java/li/cil/oc2/serialization/serializers/DeviceJsonSerializer.java new file mode 100644 index 00000000..2e6088bf --- /dev/null +++ b/src/main/java/li/cil/oc2/serialization/serializers/DeviceJsonSerializer.java @@ -0,0 +1,28 @@ +package li.cil.oc2.serialization.serializers; + +import com.google.gson.*; +import li.cil.oc2.api.device.DeviceMethod; +import li.cil.oc2.api.device.IdentifiableDevice; + +import java.lang.reflect.Type; + +public final class DeviceJsonSerializer implements JsonSerializer { + @Override + public JsonElement serialize(final IdentifiableDevice src, final Type typeOfSrc, final JsonSerializationContext context) { + if (src == null) { + return JsonNull.INSTANCE; + } + + final JsonObject deviceJson = new JsonObject(); + deviceJson.add("deviceId", context.serialize(src.getUniqueId())); + deviceJson.add("typeNames", context.serialize(src.getTypeNames())); + + final JsonArray methodsJson = new JsonArray(); + deviceJson.add("methods", methodsJson); + for (final DeviceMethod method : src.getMethods()) { + methodsJson.add(context.serialize(method, DeviceMethod.class)); + } + + return deviceJson; + } +} diff --git a/src/main/java/li/cil/oc2/serialization/serializers/DeviceMethodJsonSerializer.java b/src/main/java/li/cil/oc2/serialization/serializers/DeviceMethodJsonSerializer.java new file mode 100644 index 00000000..dc40a1a7 --- /dev/null +++ b/src/main/java/li/cil/oc2/serialization/serializers/DeviceMethodJsonSerializer.java @@ -0,0 +1,41 @@ +package li.cil.oc2.serialization.serializers; + +import com.google.gson.*; +import li.cil.oc2.api.device.DeviceMethod; +import li.cil.oc2.api.device.DeviceMethodParameter; + +import java.lang.reflect.Type; + +public final class DeviceMethodJsonSerializer implements JsonSerializer { + @Override + public JsonElement serialize(final DeviceMethod method, final Type typeOfMethod, final JsonSerializationContext context) { + if (method == null) { + return JsonNull.INSTANCE; + } + + final JsonObject methodJson = new JsonObject(); + methodJson.addProperty("name", method.getName()); + methodJson.addProperty("returnType", method.getReturnType().getSimpleName()); + + method.getDescription().ifPresent(s -> methodJson.addProperty("description", s)); + method.getReturnValueDescription().ifPresent(s -> methodJson.addProperty("returnValueDescription", s)); + + final JsonArray parametersJson = new JsonArray(); + methodJson.add("parameters", parametersJson); + + final DeviceMethodParameter[] parameters = method.getParameters(); + for (final DeviceMethodParameter parameter : parameters) { + final JsonObject parameterJson = new JsonObject(); + + parameter.getName().ifPresent(s -> parameterJson.addProperty("name", s)); + parameter.getDescription().ifPresent(s -> parameterJson.addProperty("description", s)); + + final Class type = parameter.getType(); + parameterJson.addProperty("type", type.getSimpleName()); + + parametersJson.add(parameterJson); + } + + return methodJson; + } +} diff --git a/src/main/java/li/cil/oc2/serialization/serializers/MessageJsonDeserializer.java b/src/main/java/li/cil/oc2/serialization/serializers/MessageJsonDeserializer.java new file mode 100644 index 00000000..326f1ef5 --- /dev/null +++ b/src/main/java/li/cil/oc2/serialization/serializers/MessageJsonDeserializer.java @@ -0,0 +1,30 @@ +package li.cil.oc2.serialization.serializers; + +import com.google.gson.*; +import li.cil.oc2.common.bus.DeviceBusControllerImpl; + +import java.lang.reflect.Type; + +public final class MessageJsonDeserializer implements JsonDeserializer { + @Override + public DeviceBusControllerImpl.Message deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException { + final JsonObject jsonObject = json.getAsJsonObject(); + final String messageType = jsonObject.get("type").getAsString(); + final Object messageData; + switch (messageType) { + case DeviceBusControllerImpl.Message.MESSAGE_TYPE_STATUS: { + messageData = null; + break; + } + case DeviceBusControllerImpl.Message.MESSAGE_TYPE_INVOKE_METHOD: { + messageData = context.deserialize(jsonObject.getAsJsonObject("data"), DeviceBusControllerImpl.MethodInvocation.class); + break; + } + default: { + throw new JsonParseException(DeviceBusControllerImpl.ERROR_UNKNOWN_MESSAGE_TYPE); + } + } + + return new DeviceBusControllerImpl.Message(messageType, messageData); + } +} diff --git a/src/main/java/li/cil/oc2/serialization/serializers/MethodInvocationJsonDeserializer.java b/src/main/java/li/cil/oc2/serialization/serializers/MethodInvocationJsonDeserializer.java new file mode 100644 index 00000000..13a76d6e --- /dev/null +++ b/src/main/java/li/cil/oc2/serialization/serializers/MethodInvocationJsonDeserializer.java @@ -0,0 +1,18 @@ +package li.cil.oc2.serialization.serializers; + +import com.google.gson.*; +import li.cil.oc2.common.bus.DeviceBusControllerImpl; + +import java.lang.reflect.Type; +import java.util.UUID; + +public final class MethodInvocationJsonDeserializer implements JsonDeserializer { + @Override + public DeviceBusControllerImpl.MethodInvocation deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException { + final JsonObject jsonObject = json.getAsJsonObject(); + final UUID deviceId = context.deserialize(jsonObject.get("deviceId"), UUID.class); + final String methodName = jsonObject.get("name").getAsString(); + final JsonElement parameters = jsonObject.get("parameters"); + return new DeviceBusControllerImpl.MethodInvocation(deviceId, methodName, parameters != null && parameters.isJsonArray() ? parameters.getAsJsonArray() : new JsonArray()); + } +}