Restructured JSON (de)serializing: ViewModels can be (de)serialized too
authorDaniel Kirschten <daniel.kirschten@gmx.de>
Fri, 30 Aug 2019 10:43:33 +0000 (12:43 +0200)
committerDaniel Kirschten <daniel.kirschten@gmx.de>
Fri, 30 Aug 2019 10:43:33 +0000 (12:43 +0200)
net.mograsim.logic.model.editor/src/net/mograsim/logic/model/editor/SaveLoadManager.java
net.mograsim.logic.model/src/net/mograsim/logic/model/serializing/IndirectGUIComponentCreator.java
net.mograsim.logic.model/src/net/mograsim/logic/model/serializing/LegacySubmodelComponentParams.java [new file with mode: 0644]
net.mograsim.logic.model/src/net/mograsim/logic/model/serializing/LegacySubmodelComponentSerializer.java [new file with mode: 0644]
net.mograsim.logic.model/src/net/mograsim/logic/model/serializing/SerializablePojo.java [new file with mode: 0644]
net.mograsim.logic.model/src/net/mograsim/logic/model/serializing/SubmodelComponentParams.java
net.mograsim.logic.model/src/net/mograsim/logic/model/serializing/SubmodelComponentSerializer.java
net.mograsim.logic.model/src/net/mograsim/logic/model/serializing/ViewModelParams.java [new file with mode: 0644]
net.mograsim.logic.model/src/net/mograsim/logic/model/serializing/ViewModelSerializer.java [new file with mode: 0644]
net.mograsim.logic.model/src/net/mograsim/logic/model/util/JsonHandler.java
net.mograsim.logic.model/src/net/mograsim/logic/model/util/Version.java

index 412313e..1e3d271 100644 (file)
@@ -97,7 +97,8 @@ public class SaveLoadManager
                fdShell.dispose();
                if (result != null)
                {
-                       new Editor((DeserializedSubmodelComponent) SubmodelComponentSerializer.deserialize(new ViewModelModifiable(), result));
+                       new Editor((DeserializedSubmodelComponent) IndirectGUIComponentCreator.createComponent(new ViewModelModifiable(),
+                                       "file:" + result));
                }
        }
 }
index 2b747b2..6ff8adb 100644 (file)
@@ -9,6 +9,7 @@ import java.util.Map;
 
 import com.google.gson.JsonElement;
 import com.google.gson.JsonNull;
+import com.google.gson.JsonObject;
 
 import net.mograsim.logic.model.model.ViewModelModifiable;
 import net.mograsim.logic.model.model.components.GUIComponent;
@@ -102,7 +103,14 @@ public class IndirectGUIComponentCreator
                                        throw new IllegalArgumentException("Can't give params to a component deserialized from a JSON file");
                                try
                                {
-                                       return SubmodelComponentSerializer.deserialize(model, resolvedID.substring(5), name, id, null);
+                                       String filename = resolvedID.substring(5);
+                                       JsonObject jsonContents = JsonHandler.readJson(filename, JsonObject.class);
+                                       SerializablePojo jsonContentsAsSerializablePojo = JsonHandler.parser.fromJson(jsonContents, SerializablePojo.class);
+                                       if (jsonContentsAsSerializablePojo.version == null)
+                                               return LegacySubmodelComponentSerializer.deserialize(model,
+                                                               JsonHandler.parser.fromJson(jsonContents, LegacySubmodelComponentParams.class), name, id, null);
+                                       return SubmodelComponentSerializer.deserialize(model,
+                                                       JsonHandler.parser.fromJson(jsonContents, SubmodelComponentParams.class), name, id, null);
                                }
                                catch (IOException e)
                                {
diff --git a/net.mograsim.logic.model/src/net/mograsim/logic/model/serializing/LegacySubmodelComponentParams.java b/net.mograsim.logic.model/src/net/mograsim/logic/model/serializing/LegacySubmodelComponentParams.java
new file mode 100644 (file)
index 0000000..c21fa86
--- /dev/null
@@ -0,0 +1,87 @@
+package net.mograsim.logic.model.serializing;
+
+import java.io.IOException;
+
+import com.google.gson.JsonElement;
+
+import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
+import net.mograsim.logic.model.model.components.submodels.SubmodelComponent;
+import net.mograsim.logic.model.util.JsonHandler;
+
+/**
+ * This class contains all the information necessary to create a new {@link SubmodelComponent}
+ */
+public class LegacySubmodelComponentParams
+{
+       // basic stuff
+       public double width, height;
+       public LegacyInterfacePinParams[] interfacePins;
+       public LegacySubmodelParameters submodel;
+
+       // functionality that needs to be expressed in Java code
+       public String symbolRendererSnippetID;
+       public JsonElement symbolRendererParams;
+
+       public String outlineRendererSnippetID;
+       public JsonElement outlineRendererParams;
+
+       public String highLevelStateHandlerSnippetID;
+       public JsonElement highLevelStateHandlerParams;
+
+       public static class LegacyInterfacePinParams
+       {
+               public Point location;
+               public String name;
+               public int logicWidth;
+       }
+
+       public static class LegacySubmodelParameters
+       {
+               public double innerScale;
+               public LegacyInnerComponentParams[] subComps;
+               public LegacyInnerWireParams[] innerWires;
+
+               public static class LegacyInnerComponentParams
+               {
+                       public String id;
+                       public String name;
+                       public Point pos;
+                       public JsonElement params;
+               }
+
+               public static class LegacyInnerWireParams
+               {
+                       public LegacyInnerPinParams pin1, pin2;
+                       public String name;
+                       public Point[] path;
+
+                       public static class LegacyInnerPinParams
+                       {
+                               public String compName;
+                               public String pinName;
+                       }
+               }
+       }
+
+       public static LegacySubmodelComponentParams readJson(String path) throws IOException
+       {
+               return JsonHandler.readJson(path, LegacySubmodelComponentParams.class);
+       }
+
+       /**
+        * Writes this {@link LegacySubmodelComponentParams} object into a file in json format. The correct file extension is important! Check
+        * {@link LegacySubmodelComponentParams}.fileExtension
+        */
+       public void writeJson(String path)
+       {
+               try
+               {
+                       JsonHandler.writeJson(this, path);
+               }
+               catch (IOException e)
+               {
+                       System.err.println("Failed to write SubComponentParams to file");
+                       e.printStackTrace();
+               }
+       }
+}
\ No newline at end of file
diff --git a/net.mograsim.logic.model/src/net/mograsim/logic/model/serializing/LegacySubmodelComponentSerializer.java b/net.mograsim.logic.model/src/net/mograsim/logic/model/serializing/LegacySubmodelComponentSerializer.java
new file mode 100644 (file)
index 0000000..2ede422
--- /dev/null
@@ -0,0 +1,312 @@
+package net.mograsim.logic.model.serializing;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+import com.google.gson.JsonElement;
+
+import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
+import net.mograsim.logic.model.model.ViewModelModifiable;
+import net.mograsim.logic.model.model.components.GUIComponent;
+import net.mograsim.logic.model.model.components.submodels.SubmodelComponent;
+import net.mograsim.logic.model.model.wires.GUIWire;
+import net.mograsim.logic.model.model.wires.MovablePin;
+import net.mograsim.logic.model.model.wires.Pin;
+import net.mograsim.logic.model.serializing.LegacySubmodelComponentParams.LegacyInterfacePinParams;
+import net.mograsim.logic.model.serializing.LegacySubmodelComponentParams.LegacySubmodelParameters;
+import net.mograsim.logic.model.serializing.LegacySubmodelComponentParams.LegacySubmodelParameters.LegacyInnerComponentParams;
+import net.mograsim.logic.model.serializing.LegacySubmodelComponentParams.LegacySubmodelParameters.LegacyInnerWireParams;
+import net.mograsim.logic.model.serializing.LegacySubmodelComponentParams.LegacySubmodelParameters.LegacyInnerWireParams.LegacyInnerPinParams;
+import net.mograsim.logic.model.snippets.HighLevelStateHandler;
+import net.mograsim.logic.model.snippets.Renderer;
+import net.mograsim.logic.model.snippets.SubmodelComponentSnippetSuppliers;
+import net.mograsim.logic.model.util.JsonHandler;
+
+/**
+ * Creates {@link SubmodelComponent}s from {@link LegacySubmodelComponentParams}
+ * 
+ * @author Fabian Stemmler
+ * @author Daniel Kirschten
+ */
+public final class LegacySubmodelComponentSerializer
+{
+       // convenience methods
+
+       /**
+        * Like {@link #deserialize(ViewModelModifiable, LegacySubmodelComponentParams)}, but first reading the
+        * {@link LegacySubmodelComponentParams} from the given file path.
+        * 
+        * @author Daniel Kirschten
+        */
+       public static SubmodelComponent deserialize(ViewModelModifiable model, String sourcePath) throws IOException
+       {
+               return deserialize(model, JsonHandler.readJson(sourcePath, LegacySubmodelComponentParams.class));
+       }
+
+       /**
+        * Like {@link #deserialize(ViewModelModifiable, LegacySubmodelComponentParams, String, JsonElement)}, but first reading the
+        * {@link LegacySubmodelComponentParams} from the given file path.
+        * 
+        * @author Daniel Kirschten
+        */
+       public static SubmodelComponent deserialize(ViewModelModifiable model, String sourcePath, String idForSerializingOverride,
+                       JsonElement paramsForSerializingOverride) throws IOException
+       {
+               return deserialize(model, JsonHandler.readJson(sourcePath, LegacySubmodelComponentParams.class), idForSerializingOverride,
+                               paramsForSerializingOverride);
+       }
+
+       /**
+        * Like {@link #deserialize(ViewModelModifiable, LegacySubmodelComponentParams, String)}, but first reading the
+        * {@link LegacySubmodelComponentParams} from the given file path.
+        * 
+        * @author Daniel Kirschten
+        */
+       public static SubmodelComponent deserialize(ViewModelModifiable model, String sourcePath, String name) throws IOException
+       {
+               return deserialize(model, JsonHandler.readJson(sourcePath, LegacySubmodelComponentParams.class), name);
+       }
+
+       /**
+        * Like {@link #deserialize(ViewModelModifiable, LegacySubmodelComponentParams, String, String, JsonElement)}, but first reading the
+        * {@link LegacySubmodelComponentParams} from the given file path.
+        * 
+        * @author Daniel Kirschten
+        */
+       public static SubmodelComponent deserialize(ViewModelModifiable model, String sourcePath, String name, String idForSerializingOverride,
+                       JsonElement paramsForSerializingOverride) throws IOException
+       {
+               return deserialize(model, JsonHandler.readJson(sourcePath, LegacySubmodelComponentParams.class), name, idForSerializingOverride,
+                               paramsForSerializingOverride);
+       }
+
+       /**
+        * {@link #deserialize(ViewModelModifiable, LegacySubmodelComponentParams, String, String, JsonElement)} with no
+        * <code>idForSerializingOverride</code> set and using the default name.
+        * 
+        * @author Daniel Kirschten
+        */
+       public static SubmodelComponent deserialize(ViewModelModifiable model, LegacySubmodelComponentParams params)
+       {
+               return deserialize(model, params, null, null, null);
+       }
+
+       /**
+        * {@link #deserialize(ViewModelModifiable, LegacySubmodelComponentParams, String, String, JsonElement)} using the default name.
+        * 
+        * @author Daniel Kirschten
+        */
+       public static SubmodelComponent deserialize(ViewModelModifiable model, LegacySubmodelComponentParams params,
+                       String idForSerializingOverride, JsonElement paramsForSerializingOverride)
+       {
+               return deserialize(model, params, null, idForSerializingOverride, paramsForSerializingOverride);
+       }
+
+       /**
+        * {@link #deserialize(ViewModelModifiable, LegacySubmodelComponentParams, String, String, JsonElement)} with no
+        * <code>idForSerializingOverride</code> set.
+        * 
+        * @author Daniel Kirschten
+        */
+       public static SubmodelComponent deserialize(ViewModelModifiable model, LegacySubmodelComponentParams params, String name)
+       {
+               return deserialize(model, params, name, null, null);
+       }
+
+       /**
+        * Like {@link #serialize(SubmodelComponent)}, but instead of returning the generated {@link LegacySubmodelComponentParams} they are
+        * written to a file at the given path.
+        * 
+        * @author Daniel Kirschten
+        */
+       public static void serialize(SubmodelComponent comp, String targetPath) throws IOException
+       {
+               JsonHandler.writeJson(serialize(comp), targetPath);
+       }
+
+       /**
+        * Like {@link #serialize(SubmodelComponent, Function)}, but instead of returning the generated {@link LegacySubmodelComponentParams}
+        * they are written to a file at the given path.
+        * 
+        * @author Daniel Kirschten
+        */
+       public static void serialize(SubmodelComponent comp, IdentifierGetter idGetter, String targetPath) throws IOException
+       {
+               JsonHandler.writeJson(serialize(comp, idGetter), targetPath);
+       }
+
+       /**
+        * {@link #serialize(SubmodelComponent, Function)} using a default {@link IdentifierGetter} (see <code>IdentifierGetter</code>'s
+        * {@link IdentifierGetter#IdentifierGetter() default constructor})
+        * 
+        * @author Daniel Kirschten
+        */
+       public static LegacySubmodelComponentParams serialize(SubmodelComponent comp)
+       {
+               return serialize(comp, new IdentifierGetter());
+       }
+
+       // "core" methods
+       /**
+        * Creates a {@link SubmodelComponent} from the specified {@link LegacySubmodelComponentParams} with the given name.
+        * <p>
+        * When serializing a <code>SubmodelComponent</code>, it is undesired for every subcomponent to be serialized with its complete inner
+        * structure. Instead, these sub-<code>SubmodelComponent</code>s should be serialized with the ID and params which were used to
+        * determine the <code>SubmodelComponentParams</code> defining the sub-<code>SubmodelComponent</code>. Because of this, it is possible
+        * to override the ID and params used in {@link #serialize(SubmodelComponent, Function) serialize(...)} to describe this subcomponent.
+        * See there for details.
+        * 
+        * @author Fabian Stemmler
+        * @author Daniel Kirschten
+        */
+       @SuppressWarnings("unused") // for GUIWire being created
+       public static SubmodelComponent deserialize(ViewModelModifiable model, LegacySubmodelComponentParams params, String name,
+                       String idForSerializingOverride, JsonElement paramsForSerializingOverride)
+       {
+               DeserializedSubmodelComponent comp = new DeserializedSubmodelComponent(model, name, idForSerializingOverride,
+                               paramsForSerializingOverride);
+               comp.setSubmodelScale(params.submodel.innerScale);
+               comp.setSize(params.width, params.height);
+               for (LegacyInterfacePinParams iPinParams : params.interfacePins)
+                       comp.addSubmodelInterface(
+                                       new MovablePin(comp, iPinParams.name, iPinParams.logicWidth, iPinParams.location.x, iPinParams.location.y));
+               LegacySubmodelParameters submodelParams = params.submodel;
+               ViewModelModifiable submodelModifiable = comp.getSubmodelModifiable();
+               Map<String, GUIComponent> componentsByName = submodelModifiable.getComponentsByName();
+               GUIComponent[] components = new GUIComponent[submodelParams.subComps.length];
+               for (int i = 0; i < components.length; i++)
+               {
+                       LegacyInnerComponentParams cParams = submodelParams.subComps[i];
+                       components[i] = IndirectGUIComponentCreator.createComponent(submodelModifiable, cParams.id, cParams.params, cParams.name);
+                       components[i].moveTo(cParams.pos.x, cParams.pos.y);
+               }
+
+               for (int i = 0; i < submodelParams.innerWires.length; i++)
+               {
+                       LegacyInnerWireParams innerWire = submodelParams.innerWires[i];
+                       new GUIWire(submodelModifiable, innerWire.name, componentsByName.get(innerWire.pin1.compName).getPin(innerWire.pin1.pinName),
+                                       componentsByName.get(innerWire.pin2.compName).getPin(innerWire.pin2.pinName), innerWire.path);
+               }
+               comp.setSymbolRenderer(SubmodelComponentSnippetSuppliers.symbolRendererSupplier.getSnippetSupplier(params.symbolRendererSnippetID)
+                               .create(comp, params.symbolRendererParams));
+               comp.setOutlineRenderer(SubmodelComponentSnippetSuppliers.outlineRendererSupplier
+                               .getSnippetSupplier(params.outlineRendererSnippetID).create(comp, params.outlineRendererParams));
+               comp.setHighLevelStateHandler(SubmodelComponentSnippetSuppliers.highLevelStateHandlerSupplier
+                               .getSnippetSupplier(params.highLevelStateHandlerSnippetID).create(comp, params.highLevelStateHandlerParams));
+               return comp;
+       }
+
+       /**
+        * Returns {@link LegacySubmodelComponentParams}, which describe this {@link SubmodelComponent}. <br>
+        * Subcomponents are serialized in the following way: <br>
+        * If a subcomponent is a <code>SubmodelComponent</code> which has been deserialized, and it has an
+        * {@link DeserializedSubmodelComponent#idForSerializingOverride idForSerializingOverride} set (e.g. non-null; see
+        * {@link #deserialize(ViewModelModifiable, LegacySubmodelComponentParams, String, String, JsonElement) deserialize(...)}), this ID and
+        * the component's {@link DeserializedSubmodelComponent#paramsForSerializingOverride paramsForSerializingOverride} are written.<br>
+        * If this case doesn't apply (e.g. if the subcomponent is not a <code>SubmodelComponent</code>; or it is a
+        * <code>SubmodelComponent</code>, but hasn't been deserialized; or it has no
+        * {@link DeserializedSubmodelComponent#idForSerializingOverride idForSerializingOverride} set), the ID defined by <code>idGetter</code>
+        * and the params obtained by {@link GUIComponent#getParamsForSerializing() getParams()} are written.<br>
+        * CodeSnippets are serialized using the ID defined by <code>idGetter</code> and the params obtained by the respective
+        * <coce>getParamsForSerializing</code> methods ({@link Renderer#getParamsForSerializing()}).
+        * 
+        * @author Fabian Stemmler
+        * @author Daniel Kirschten
+        */
+       public static LegacySubmodelComponentParams serialize(SubmodelComponent comp, IdentifierGetter idGetter)
+       {
+               LegacySubmodelParameters submodelParams = new LegacySubmodelParameters();
+               submodelParams.innerScale = comp.getSubmodelScale();
+
+               Map<String, GUIComponent> components = new HashMap<>(comp.submodel.getComponentsByName());
+               components.remove(SubmodelComponent.SUBMODEL_INTERFACE_NAME);
+               LegacyInnerComponentParams[] componentParams = new LegacyInnerComponentParams[components.size()];
+               int i1 = 0;
+               for (GUIComponent innerComponent : components.values())
+               {
+                       LegacyInnerComponentParams innerComponentParams = new LegacyInnerComponentParams();
+                       componentParams[i1] = innerComponentParams;
+                       innerComponentParams.pos = new Point(innerComponent.getPosX(), innerComponent.getPosY());
+                       DeserializedSubmodelComponent innerCompCasted;
+                       if (innerComponent instanceof DeserializedSubmodelComponent
+                                       && (innerCompCasted = (DeserializedSubmodelComponent) innerComponent).idForSerializingOverride != null)
+                       {
+                               innerComponentParams.id = innerCompCasted.idForSerializingOverride;
+                               innerComponentParams.params = innerCompCasted.paramsForSerializingOverride;
+                       } else
+                       {
+                               innerComponentParams.id = idGetter.componentIDs.apply(innerComponent);
+                               innerComponentParams.params = innerComponent.getParamsForSerializing(idGetter);
+                       }
+                       innerComponentParams.name = innerComponent.name;
+                       i1++;
+               }
+               submodelParams.subComps = componentParams;
+
+               Collection<GUIWire> wires = comp.submodel.getWiresByName().values();
+               LegacyInnerWireParams wireParams[] = new LegacyInnerWireParams[wires.size()];
+               i1 = 0;
+               for (GUIWire innerWire : wires)
+               {
+                       LegacyInnerWireParams innerWireParams = new LegacyInnerWireParams();
+                       wireParams[i1] = innerWireParams;
+                       LegacyInnerPinParams pin1Params = new LegacyInnerPinParams(), pin2Params = new LegacyInnerPinParams();
+
+                       pin1Params.pinName = innerWire.getPin1().name;
+                       pin1Params.compName = innerWire.getPin1().component.name;
+                       pin2Params.pinName = innerWire.getPin2().name;
+                       pin2Params.compName = innerWire.getPin2().component.name;
+                       innerWireParams.name = innerWire.name;
+                       innerWireParams.pin1 = pin1Params;
+                       innerWireParams.pin2 = pin2Params;
+                       innerWireParams.path = innerWire.getPath();
+                       i1++;
+               }
+               submodelParams.innerWires = wireParams;
+
+               LegacySubmodelComponentParams params = new LegacySubmodelComponentParams();
+               params.submodel = submodelParams;
+
+               params.width = comp.getWidth();
+               params.height = comp.getHeight();
+
+               LegacyInterfacePinParams[] iPins = new LegacyInterfacePinParams[comp.getPins().size()];
+               int i = 0;
+               for (Pin p : comp.getPins().values())
+               {
+                       LegacyInterfacePinParams iPinParams = new LegacyInterfacePinParams();
+                       iPins[i] = iPinParams;
+                       iPinParams.location = p.getRelPos();
+                       iPinParams.name = p.name;
+                       iPinParams.logicWidth = p.logicWidth;
+                       i++;
+               }
+               params.interfacePins = iPins;
+
+               Renderer symbolRenderer = comp.getSymbolRenderer();
+               if (symbolRenderer != null)
+               {
+                       params.symbolRendererSnippetID = idGetter.symbolRendererIDs.apply(symbolRenderer);
+                       params.symbolRendererParams = symbolRenderer.getParamsForSerializingJSON(idGetter);
+               }
+
+               Renderer outlineRenderer = comp.getOutlineRenderer();
+               if (outlineRenderer != null)
+               {
+                       params.outlineRendererSnippetID = idGetter.outlineRendererIDs.apply(outlineRenderer);
+                       params.outlineRendererParams = outlineRenderer.getParamsForSerializingJSON(idGetter);
+               }
+
+               HighLevelStateHandler highLevelStateHandler = comp.getHighLevelStateHandler();
+               if (highLevelStateHandler != null)
+               {
+                       params.highLevelStateHandlerSnippetID = idGetter.highLevelStateHandlerIDs.apply(highLevelStateHandler);
+                       params.highLevelStateHandlerParams = highLevelStateHandler.getParamsForSerializingJSON(idGetter);
+               }
+
+               return params;
+       }
+}
\ No newline at end of file
diff --git a/net.mograsim.logic.model/src/net/mograsim/logic/model/serializing/SerializablePojo.java b/net.mograsim.logic.model/src/net/mograsim/logic/model/serializing/SerializablePojo.java
new file mode 100644 (file)
index 0000000..4cc94d7
--- /dev/null
@@ -0,0 +1,19 @@
+package net.mograsim.logic.model.serializing;
+
+import net.mograsim.logic.model.util.Version;
+
+/**
+ * This class is the superclass of all POJOs that can be serialized to JSON.
+ * 
+ * @author Daniel Kirschten
+ *
+ */
+public class SerializablePojo
+{
+       public Version version;
+
+       public SerializablePojo(Version version)
+       {
+               this.version = version;
+       }
+}
\ No newline at end of file
index d47b2d9..155b658 100644 (file)
@@ -1,22 +1,21 @@
 package net.mograsim.logic.model.serializing;
 
-import java.io.IOException;
-
 import com.google.gson.JsonElement;
 
 import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
 import net.mograsim.logic.model.model.components.submodels.SubmodelComponent;
-import net.mograsim.logic.model.util.JsonHandler;
+import net.mograsim.logic.model.util.Version;
 
 /**
  * This class contains all the information necessary to create a new {@link SubmodelComponent}
  */
-public class SubmodelComponentParams
+public class SubmodelComponentParams extends SerializablePojo
 {
        // basic stuff
        public double width, height;
        public InterfacePinParams[] interfacePins;
-       public SubmodelParameters submodel;
+       public double innerScale;
+       public ViewModelParams submodel;
 
        // functionality that needs to be expressed in Java code
        public String symbolRendererSnippetID;
@@ -28,60 +27,15 @@ public class SubmodelComponentParams
        public String highLevelStateHandlerSnippetID;
        public JsonElement highLevelStateHandlerParams;
 
+       public SubmodelComponentParams(Version version)
+       {
+               super(version);
+       }
+
        public static class InterfacePinParams
        {
                public Point location;
                public String name;
                public int logicWidth;
        }
-
-       public static class SubmodelParameters
-       {
-               public double innerScale;
-               public InnerComponentParams[] subComps;
-               public InnerWireParams[] innerWires;
-
-               public static class InnerComponentParams
-               {
-                       public String id;
-                       public String name;
-                       public Point pos;
-                       public JsonElement params;
-               }
-
-               public static class InnerWireParams
-               {
-                       public InnerPinParams pin1, pin2;
-                       public String name;
-                       public Point[] path;
-
-                       public static class InnerPinParams
-                       {
-                               public String compName;
-                               public String pinName;
-                       }
-               }
-       }
-
-       public static SubmodelComponentParams readJson(String path) throws IOException
-       {
-               return JsonHandler.readJson(path, SubmodelComponentParams.class);
-       }
-
-       /**
-        * Writes this {@link SubmodelComponentParams} object into a file in json format. The correct file extension is important! Check
-        * {@link SubmodelComponentParams}.fileExtension
-        */
-       public void writeJson(String path)
-       {
-               try
-               {
-                       JsonHandler.writeJson(this, path);
-               }
-               catch (IOException e)
-               {
-                       System.err.println("Failed to write SubComponentParams to file");
-                       e.printStackTrace();
-               }
-       }
 }
\ No newline at end of file
index 077c591..126e2aa 100644 (file)
@@ -1,29 +1,19 @@
 package net.mograsim.logic.model.serializing;
 
 import java.io.IOException;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.function.Function;
 
 import com.google.gson.JsonElement;
 
-import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
 import net.mograsim.logic.model.model.ViewModelModifiable;
-import net.mograsim.logic.model.model.components.GUIComponent;
 import net.mograsim.logic.model.model.components.submodels.SubmodelComponent;
-import net.mograsim.logic.model.model.wires.GUIWire;
 import net.mograsim.logic.model.model.wires.MovablePin;
 import net.mograsim.logic.model.model.wires.Pin;
 import net.mograsim.logic.model.serializing.SubmodelComponentParams.InterfacePinParams;
-import net.mograsim.logic.model.serializing.SubmodelComponentParams.SubmodelParameters;
-import net.mograsim.logic.model.serializing.SubmodelComponentParams.SubmodelParameters.InnerComponentParams;
-import net.mograsim.logic.model.serializing.SubmodelComponentParams.SubmodelParameters.InnerWireParams;
-import net.mograsim.logic.model.serializing.SubmodelComponentParams.SubmodelParameters.InnerWireParams.InnerPinParams;
 import net.mograsim.logic.model.snippets.HighLevelStateHandler;
 import net.mograsim.logic.model.snippets.Renderer;
 import net.mograsim.logic.model.snippets.SubmodelComponentSnippetSuppliers;
 import net.mograsim.logic.model.util.JsonHandler;
+import net.mograsim.logic.model.util.Version;
 
 /**
  * Creates {@link SubmodelComponent}s from {@link SubmodelComponentParams}
@@ -33,6 +23,7 @@ import net.mograsim.logic.model.util.JsonHandler;
  */
 public final class SubmodelComponentSerializer
 {
+       public static final Version CURRENT_JSON_VERSION = Version.parseSemver("0.1.4");
        // convenience methods
 
        /**
@@ -128,8 +119,8 @@ public final class SubmodelComponentSerializer
        }
 
        /**
-        * Like {@link #serialize(SubmodelComponent, Function)}, but instead of returning the generated {@link SubmodelComponentParams} they are
-        * written to a file at the given path.
+        * Like {@link #serialize(SubmodelComponent, IdentifierGetter)}, but instead of returning the generated {@link SubmodelComponentParams}
+        * they are written to a file at the given path.
         * 
         * @author Daniel Kirschten
         */
@@ -139,7 +130,7 @@ public final class SubmodelComponentSerializer
        }
 
        /**
-        * {@link #serialize(SubmodelComponent, Function)} using a default {@link IdentifierGetter} (see <code>IdentifierGetter</code>'s
+        * {@link #serialize(SubmodelComponent, IdentifierGetter)} using a default {@link IdentifierGetter} (see <code>IdentifierGetter</code>'s
         * {@link IdentifierGetter#IdentifierGetter() default constructor})
         * 
         * @author Daniel Kirschten
@@ -156,8 +147,8 @@ public final class SubmodelComponentSerializer
         * When serializing a <code>SubmodelComponent</code>, it is undesired for every subcomponent to be serialized with its complete inner
         * structure. Instead, these sub-<code>SubmodelComponent</code>s should be serialized with the ID and params which were used to
         * determine the <code>SubmodelComponentParams</code> defining the sub-<code>SubmodelComponent</code>. Because of this, it is possible
-        * to override the ID and params used in {@link #serialize(SubmodelComponent, Function) serialize(...)} to describe this subcomponent.
-        * See there for details.
+        * to override the ID and params used in {@link #serialize(SubmodelComponent, IdentifierGetter) serialize(...)} to describe this
+        * subcomponent. See there for details.
         * 
         * @author Fabian Stemmler
         * @author Daniel Kirschten
@@ -168,28 +159,13 @@ public final class SubmodelComponentSerializer
        {
                DeserializedSubmodelComponent comp = new DeserializedSubmodelComponent(model, name, idForSerializingOverride,
                                paramsForSerializingOverride);
-               comp.setSubmodelScale(params.submodel.innerScale);
+               comp.setSubmodelScale(params.innerScale);
                comp.setSize(params.width, params.height);
                for (InterfacePinParams iPinParams : params.interfacePins)
                        comp.addSubmodelInterface(
                                        new MovablePin(comp, iPinParams.name, iPinParams.logicWidth, iPinParams.location.x, iPinParams.location.y));
-               SubmodelParameters submodelParams = params.submodel;
                ViewModelModifiable submodelModifiable = comp.getSubmodelModifiable();
-               Map<String, GUIComponent> componentsByName = submodelModifiable.getComponentsByName();
-               GUIComponent[] components = new GUIComponent[submodelParams.subComps.length];
-               for (int i = 0; i < components.length; i++)
-               {
-                       InnerComponentParams cParams = submodelParams.subComps[i];
-                       components[i] = IndirectGUIComponentCreator.createComponent(submodelModifiable, cParams.id, cParams.params, cParams.name);
-                       components[i].moveTo(cParams.pos.x, cParams.pos.y);
-               }
-
-               for (int i = 0; i < submodelParams.innerWires.length; i++)
-               {
-                       InnerWireParams innerWire = submodelParams.innerWires[i];
-                       new GUIWire(submodelModifiable, innerWire.name, componentsByName.get(innerWire.pin1.compName).getPin(innerWire.pin1.pinName),
-                                       componentsByName.get(innerWire.pin2.compName).getPin(innerWire.pin2.pinName), innerWire.path);
-               }
+               ViewModelSerializer.deserialize(comp.getSubmodelModifiable(), params.submodel);
                comp.setSymbolRenderer(SubmodelComponentSnippetSuppliers.symbolRendererSupplier.getSnippetSupplier(params.symbolRendererSnippetID)
                                .create(comp, params.symbolRendererParams));
                comp.setOutlineRenderer(SubmodelComponentSnippetSuppliers.outlineRendererSupplier
@@ -201,15 +177,8 @@ public final class SubmodelComponentSerializer
 
        /**
         * Returns {@link SubmodelComponentParams}, which describe this {@link SubmodelComponent}. <br>
-        * Subcomponents are serialized in the following way: <br>
-        * If a subcomponent is a <code>SubmodelComponent</code> which has been deserialized, and it has an
-        * {@link DeserializedSubmodelComponent#idForSerializingOverride idForSerializingOverride} set (e.g. non-null; see
-        * {@link #deserialize(ViewModelModifiable, SubmodelComponentParams, String, String, JsonElement) deserialize(...)}), this ID and the
-        * component's {@link DeserializedSubmodelComponent#paramsForSerializingOverride paramsForSerializingOverride} are written.<br>
-        * If this case doesn't apply (e.g. if the subcomponent is not a <code>SubmodelComponent</code>; or it is a
-        * <code>SubmodelComponent</code>, but hasn't been deserialized; or it has no
-        * {@link DeserializedSubmodelComponent#idForSerializingOverride idForSerializingOverride} set), the ID defined by <code>idGetter</code>
-        * and the params obtained by {@link GUIComponent#getParamsForSerializing() getParams()} are written.<br>
+        * See {@link ViewModelSerializer#serialize(net.mograsim.logic.model.model.ViewModel, IdentifierGetter)
+        * ViewModelSerializer.serialize(...)} for how subcomponents are serialized.<br>
         * CodeSnippets are serialized using the ID defined by <code>idGetter</code> and the params obtained by the respective
         * <coce>getParamsForSerializing</code> methods ({@link Renderer#getParamsForSerializing()}).
         * 
@@ -218,57 +187,9 @@ public final class SubmodelComponentSerializer
         */
        public static SubmodelComponentParams serialize(SubmodelComponent comp, IdentifierGetter idGetter)
        {
-               SubmodelParameters submodelParams = new SubmodelParameters();
-               submodelParams.innerScale = comp.getSubmodelScale();
-
-               Map<String, GUIComponent> components = new HashMap<>(comp.submodel.getComponentsByName());
-               components.remove(SubmodelComponent.SUBMODEL_INTERFACE_NAME);
-               InnerComponentParams[] componentParams = new InnerComponentParams[components.size()];
-               int i1 = 0;
-               for (GUIComponent innerComponent : components.values())
-               {
-                       InnerComponentParams innerComponentParams = new InnerComponentParams();
-                       componentParams[i1] = innerComponentParams;
-                       innerComponentParams.pos = new Point(innerComponent.getPosX(), innerComponent.getPosY());
-                       DeserializedSubmodelComponent innerCompCasted;
-                       if (innerComponent instanceof DeserializedSubmodelComponent
-                                       && (innerCompCasted = (DeserializedSubmodelComponent) innerComponent).idForSerializingOverride != null)
-                       {
-                               innerComponentParams.id = innerCompCasted.idForSerializingOverride;
-                               innerComponentParams.params = innerCompCasted.paramsForSerializingOverride;
-                       } else
-                       {
-                               innerComponentParams.id = idGetter.componentIDs.apply(innerComponent);
-                               innerComponentParams.params = innerComponent.getParamsForSerializing(idGetter);
-                       }
-                       innerComponentParams.name = innerComponent.name;
-                       i1++;
-               }
-               submodelParams.subComps = componentParams;
-
-               Collection<GUIWire> wires = comp.submodel.getWiresByName().values();
-               InnerWireParams wireParams[] = new InnerWireParams[wires.size()];
-               i1 = 0;
-               for (GUIWire innerWire : wires)
-               {
-                       InnerWireParams innerWireParams = new InnerWireParams();
-                       wireParams[i1] = innerWireParams;
-                       InnerPinParams pin1Params = new InnerPinParams(), pin2Params = new InnerPinParams();
-
-                       pin1Params.pinName = innerWire.getPin1().name;
-                       pin1Params.compName = innerWire.getPin1().component.name;
-                       pin2Params.pinName = innerWire.getPin2().name;
-                       pin2Params.compName = innerWire.getPin2().component.name;
-                       innerWireParams.name = innerWire.name;
-                       innerWireParams.pin1 = pin1Params;
-                       innerWireParams.pin2 = pin2Params;
-                       innerWireParams.path = innerWire.getPath();
-                       i1++;
-               }
-               submodelParams.innerWires = wireParams;
-
-               SubmodelComponentParams params = new SubmodelComponentParams();
-               params.submodel = submodelParams;
+               SubmodelComponentParams params = new SubmodelComponentParams(CURRENT_JSON_VERSION);
+               params.innerScale = comp.getSubmodelScale();
+               params.submodel = ViewModelSerializer.serialize(comp.submodel, idGetter);
 
                params.width = comp.getWidth();
                params.height = comp.getHeight();
diff --git a/net.mograsim.logic.model/src/net/mograsim/logic/model/serializing/ViewModelParams.java b/net.mograsim.logic.model/src/net/mograsim/logic/model/serializing/ViewModelParams.java
new file mode 100644 (file)
index 0000000..6f4f82c
--- /dev/null
@@ -0,0 +1,38 @@
+package net.mograsim.logic.model.serializing;
+
+import com.google.gson.JsonElement;
+
+import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
+import net.mograsim.logic.model.util.Version;
+
+public class ViewModelParams extends SerializablePojo
+{
+       public ComponentParams[] components;
+       public WireParams[] wires;
+
+       public ViewModelParams(Version version)
+       {
+               super(version);
+       }
+
+       public static class ComponentParams
+       {
+               public String id;
+               public String name;
+               public Point pos;
+               public JsonElement params;
+       }
+
+       public static class WireParams
+       {
+               public PinParams pin1, pin2;
+               public String name;
+               public Point[] path;
+
+               public static class PinParams
+               {
+                       public String compName;
+                       public String pinName;
+               }
+       }
+}
\ No newline at end of file
diff --git a/net.mograsim.logic.model/src/net/mograsim/logic/model/serializing/ViewModelSerializer.java b/net.mograsim.logic.model/src/net/mograsim/logic/model/serializing/ViewModelSerializer.java
new file mode 100644 (file)
index 0000000..ffd18c9
--- /dev/null
@@ -0,0 +1,186 @@
+package net.mograsim.logic.model.serializing;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.gson.JsonElement;
+
+import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
+import net.mograsim.logic.model.model.ViewModel;
+import net.mograsim.logic.model.model.ViewModelModifiable;
+import net.mograsim.logic.model.model.components.GUIComponent;
+import net.mograsim.logic.model.model.components.submodels.SubmodelComponent;
+import net.mograsim.logic.model.model.wires.GUIWire;
+import net.mograsim.logic.model.serializing.ViewModelParams.ComponentParams;
+import net.mograsim.logic.model.serializing.ViewModelParams.WireParams;
+import net.mograsim.logic.model.serializing.ViewModelParams.WireParams.PinParams;
+import net.mograsim.logic.model.util.JsonHandler;
+import net.mograsim.logic.model.util.Version;
+
+public class ViewModelSerializer
+{
+       public static final Version CURRENT_JSON_VERSION = Version.parseSemver("0.1.1");
+
+       // convenience methods
+       /**
+        * Like {@link #deserialize(ViewModelParams)}, but first reading the {@link ViewModelParams} from the given file path.
+        * 
+        * @author Daniel Kirschten
+        */
+       public static ViewModelModifiable deserialize(String sourcePath) throws IOException
+       {
+               return deserialize(JsonHandler.readJson(sourcePath, ViewModelParams.class));
+       }
+
+       /**
+        * Like {@link #deserialize(ViewModelModifiable, ViewModelParams)}, but first reading the {@link ViewModelParams} from the given file
+        * path.
+        * 
+        * @author Daniel Kirschten
+        */
+       public static void deserialize(ViewModelModifiable model, String sourcePath) throws IOException
+       {
+               deserialize(model, JsonHandler.readJson(sourcePath, ViewModelParams.class));
+       }
+
+       /**
+        * Like {@link #deserialize(ViewModelModifiable, ViewModelParams)}, but using a newly created {@link ViewModelModifiable}.
+        * 
+        * @author Daniel Kirschten
+        */
+       public static ViewModelModifiable deserialize(ViewModelParams params)
+       {
+               ViewModelModifiable model = new ViewModelModifiable();
+               deserialize(model, params);
+               return model;
+       }
+
+       /**
+        * Like {@link #serialize(ViewModel)}, but instead of returning the generated {@link ViewModelParams} they are written to a file at the
+        * given path.
+        * 
+        * @author Daniel Kirschten
+        */
+       public static void serialize(ViewModel model, String targetPath) throws IOException
+       {
+               JsonHandler.writeJson(serialize(model), targetPath);
+       }
+
+       /**
+        * Like {@link #serialize(ViewModel, IdentifierGetter)}, but instead of returning the generated {@link ViewModelParams} they are written
+        * to a file at the given path.
+        * 
+        * @author Daniel Kirschten
+        */
+       public static void serialize(ViewModel model, IdentifierGetter idGetter, String targetPath) throws IOException
+       {
+               JsonHandler.writeJson(serialize(model, idGetter), targetPath);
+       }
+
+       /**
+        * {@link #serialize(ViewModel, IdentifierGetter)} using a default {@link IdentifierGetter} (see <code>IdentifierGetter</code>'s
+        * {@link IdentifierGetter#IdentifierGetter() default constructor})
+        * 
+        * @author Daniel Kirschten
+        */
+       public static ViewModelParams serialize(ViewModel model)
+       {
+               return serialize(model, new IdentifierGetter());
+       }
+
+       // "core" methods
+       /**
+        * Deserializes components and wires from the specified {@link ViewModelParams} and adds them to the given {@link ViewModelModifiable}.
+        * 
+        * @author Fabian Stemmler
+        * @author Daniel Kirschten
+        */
+       @SuppressWarnings("unused") // for GUIWire being created
+       public static void deserialize(ViewModelModifiable model, ViewModelParams params)
+       {
+               Map<String, GUIComponent> componentsByName = model.getComponentsByName();
+               GUIComponent[] components = new GUIComponent[params.components.length];
+               for (int i = 0; i < components.length; i++)
+               {
+                       ComponentParams compParams = params.components[i];
+                       components[i] = IndirectGUIComponentCreator.createComponent(model, compParams.id, compParams.params, compParams.name);
+                       components[i].moveTo(compParams.pos.x, compParams.pos.y);
+               }
+
+               for (int i = 0; i < params.wires.length; i++)
+               {
+                       WireParams wire = params.wires[i];
+                       new GUIWire(model, wire.name, componentsByName.get(wire.pin1.compName).getPin(wire.pin1.pinName),
+                                       componentsByName.get(wire.pin2.compName).getPin(wire.pin2.pinName), wire.path);
+               }
+       }
+
+       /**
+        * Returns {@link ViewModelModifiable}, which describe the components and wires in the given {@link ViewModel}. <br>
+        * Components are serialized in the following way: <br>
+        * If a component is a <code>SubmodelComponent</code> which has been deserialized, and it has an
+        * {@link DeserializedSubmodelComponent#idForSerializingOverride idForSerializingOverride} set (e.g. non-null; see
+        * {@link SubmodelComponentSerializer#deserialize(ViewModelModifiable, SubmodelComponentParams, String, String, JsonElement)
+        * SubmodelComponentSerializer.deserialize(...)}), this ID and the component's
+        * {@link DeserializedSubmodelComponent#paramsForSerializingOverride paramsForSerializingOverride} are written.<br>
+        * If this case doesn't apply (e.g. if the component is not a <code>SubmodelComponent</code>; or it is a <code>SubmodelComponent</code>,
+        * but hasn't been deserialized; or it has no {@link DeserializedSubmodelComponent#idForSerializingOverride idForSerializingOverride}
+        * set), the ID defined by <code>idGetter</code> and the params obtained by {@link GUIComponent#getParamsForSerializing() getParams()}
+        * are written.
+        * 
+        * @author Fabian Stemmler
+        * @author Daniel Kirschten
+        */
+       public static ViewModelParams serialize(ViewModel model, IdentifierGetter idGetter)
+       {
+               ViewModelParams modelParams = new ViewModelParams(CURRENT_JSON_VERSION);
+
+               Map<String, GUIComponent> components = new HashMap<>(model.getComponentsByName());
+               components.remove(SubmodelComponent.SUBMODEL_INTERFACE_NAME);
+               Set<ComponentParams> componentsParams = new HashSet<>();
+               for (GUIComponent component : components.values())
+               {
+                       ComponentParams compParams = new ComponentParams();
+                       componentsParams.add(compParams);
+                       compParams.pos = new Point(component.getPosX(), component.getPosY());
+                       DeserializedSubmodelComponent innerCompCasted;
+                       if (component instanceof DeserializedSubmodelComponent
+                                       && (innerCompCasted = (DeserializedSubmodelComponent) component).idForSerializingOverride != null)
+                       {
+                               compParams.id = innerCompCasted.idForSerializingOverride;
+                               compParams.params = innerCompCasted.paramsForSerializingOverride;
+                       } else
+                       {
+                               compParams.id = idGetter.componentIDs.apply(component);
+                               compParams.params = component.getParamsForSerializing(idGetter);
+                       }
+                       compParams.name = component.name;
+               }
+               modelParams.components = componentsParams.toArray(ComponentParams[]::new);
+
+               Collection<GUIWire> wires = model.getWiresByName().values();
+               Set<WireParams> wiresParams = new HashSet<>();
+               for (GUIWire innerWire : wires)
+               {
+                       WireParams innerWireParams = new WireParams();
+                       wiresParams.add(innerWireParams);
+                       PinParams pin1Params = new PinParams(), pin2Params = new PinParams();
+
+                       pin1Params.pinName = innerWire.getPin1().name;
+                       pin1Params.compName = innerWire.getPin1().component.name;
+                       pin2Params.pinName = innerWire.getPin2().name;
+                       pin2Params.compName = innerWire.getPin2().component.name;
+                       innerWireParams.name = innerWire.name;
+                       innerWireParams.pin1 = pin1Params;
+                       innerWireParams.pin2 = pin2Params;
+                       innerWireParams.path = innerWire.getPath();
+               }
+               modelParams.wires = wiresParams.toArray(WireParams[]::new);
+
+               return modelParams;
+       }
+}
\ No newline at end of file
index 8dffc94..4f60bdf 100644 (file)
@@ -14,8 +14,7 @@ import com.google.gson.JsonElement;
 
 public class JsonHandler
 {
-       // TODO: write versions differently
-       private static Gson parser = new GsonBuilder().setPrettyPrinting().create();
+       public final static Gson parser = new GsonBuilder().setPrettyPrinting().create();
 
        public static <T> T readJson(String path, Class<T> type) throws IOException
        {
@@ -38,7 +37,7 @@ public class JsonHandler
 
        public static <T> T fromJson(String src, Class<T> type)
        {
-               // TODO actually parse and compare version
+               // throw away legacy version line
                String rawJson = src.lines().dropWhile(s -> s.length() == 0 || s.charAt(0) != '{').collect(Collectors.joining());
                return parser.fromJson(rawJson, type);
        }
@@ -53,7 +52,7 @@ public class JsonHandler
 
        public static String toJson(Object o)
        {
-               return String.format("mograsim version: %s\n%s", Version.jsonCompVersion.toString(), parser.toJson(o));
+               return parser.toJson(o);
        }
 
        public static JsonElement toJsonTree(Object o)
index b0a7298..ac42e13 100644 (file)
@@ -1,8 +1,17 @@
 package net.mograsim.logic.model.util;
 
+import java.io.IOException;
+
+import com.google.gson.TypeAdapter;
+import com.google.gson.annotations.JsonAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+import net.mograsim.logic.model.util.Version.VersionJSONAdapter;
+
+@JsonAdapter(VersionJSONAdapter.class)
 public final class Version
 {
-       public final static Version jsonCompVersion = new Version(0, 1, 3);
        public final int major, minor, patch;
 
        public Version(int major, int minor, int patch)
@@ -20,10 +29,22 @@ public final class Version
 
        @Override
        public String toString()
+       {
+               return toSemverString();
+       }
+
+       public String toSemverString()
        {
                return major + "." + minor + "." + patch;
        }
 
+       public static Version parseSemver(String semver)
+       {
+               String[] semverParts = semver.split("\\.");
+               return new Version(Integer.parseInt(semverParts[0]), semverParts.length > 1 ? Integer.parseInt(semverParts[1]) : 0,
+                               semverParts.length > 2 ? Integer.parseInt(semverParts[2]) : 0);
+       }
+
        @Override
        public int hashCode()
        {
@@ -54,7 +75,7 @@ public final class Version
 
        public boolean is(int major)
        {
-               return major != this.major;
+               return major == this.major;
        }
 
        public boolean is(int major, int minor)
@@ -66,4 +87,19 @@ public final class Version
        {
                return is(major, minor) && this.patch == patch;
        }
+
+       static class VersionJSONAdapter extends TypeAdapter<Version>
+       {
+               @Override
+               public void write(JsonWriter out, Version value) throws IOException
+               {
+                       out.value(value.toSemverString());
+               }
+
+               @Override
+               public Version read(JsonReader in) throws IOException
+               {
+                       return parseSemver(in.nextString());
+               }
+       }
 }
\ No newline at end of file