A proposal for resolving the loading problem of json files
[Mograsim.git] / net.mograsim.logic.model / src / net / mograsim / logic / model / serializing / IndirectGUIComponentCreator.java
1 package net.mograsim.logic.model.serializing;
2
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.io.UncheckedIOException;
6 import java.lang.reflect.InvocationTargetException;
7 import java.util.Collections;
8 import java.util.HashMap;
9 import java.util.Map;
10 import java.util.Objects;
11 import java.util.function.Supplier;
12
13 import com.google.gson.JsonElement;
14 import com.google.gson.JsonNull;
15 import com.google.gson.JsonObject;
16
17 import net.mograsim.logic.model.model.ViewModelModifiable;
18 import net.mograsim.logic.model.model.components.GUIComponent;
19 import net.mograsim.logic.model.model.components.submodels.SubmodelComponent;
20 import net.mograsim.logic.model.snippets.CodeSnippetSupplier;
21 import net.mograsim.logic.model.util.JsonHandler;
22
23 public class IndirectGUIComponentCreator
24 {
25         private static final Map<String, String> standardComponentIDs = new HashMap<>();
26         private static final Map<String, String> standardComponentIDsUnmodifiable = Collections.unmodifiableMap(standardComponentIDs);
27
28         private static final Map<String, ComponentSupplier> componentSuppliers = new HashMap<>();
29         private static final Map<String, ResourceLoader> resourceLoaders = new HashMap<>();
30
31         static
32         {
33                 loadStandardComponentIDs(IndirectGUIComponentCreator.class.getResourceAsStream("standardComponentIDMapping.json"));
34         }
35
36         public static void loadStandardComponentIDs(InputStream standardComponentIdMappingStream)
37         {
38                 try (InputStream s = standardComponentIdMappingStream)
39                 {
40                         if (s == null)
41                                 throw new IOException("Resource not found");
42                         Map<String, String> tmp = JsonHandler.readJson(s, Map.class);
43                         // don't use putAll to apply sanity checks
44                         tmp.forEach((st, id) ->
45                         {
46                                 try
47                                 {
48                                         addStandardComponentID(st, id);
49                                 }
50                                 catch (IllegalArgumentException e)
51                                 {
52                                         System.err.println("Component ID mapping contained illegal entry: " + e.getMessage());
53                                 }
54                         });
55                 }
56                 catch (IOException e)
57                 {
58                         System.err.println("Failed to initialize standard snippet ID mapping: " + e.getMessage());
59                 }
60         }
61
62         public static void addStandardComponentID(String standardComponentID, String associatedComponentID)
63         {
64                 if (!associatedComponentID.matches("(file|class|resource):.+"))
65                         throw new IllegalArgumentException("Unrecognized component ID format: " + associatedComponentID);
66                 standardComponentIDs.put(standardComponentID, associatedComponentID);
67         }
68
69         public static Map<String, String> getStandardComponentIDs()
70         {
71                 return standardComponentIDsUnmodifiable;
72         }
73
74         public static void setComponentSupplier(String className, ComponentSupplier componentSupplier)
75         {
76                 componentSuppliers.put(className, componentSupplier);
77         }
78
79         public static GUIComponent createComponent(ViewModelModifiable model, String id)
80         {
81                 return createComponent(model, id, (String) null);
82         }
83
84         public static GUIComponent createComponent(ViewModelModifiable model, String id, String name)
85         {
86                 return createComponent(model, id, JsonNull.INSTANCE, name);
87         }
88
89         public static GUIComponent createComponent(ViewModelModifiable model, String id, JsonElement params)
90         {
91                 return createComponent(model, id, params, null);
92         }
93
94         public static GUIComponent createComponent(ViewModelModifiable model, String id, JsonElement params, String name)
95         {
96                 if (id != null)
97                 {
98                         String resolvedID = resolveID(id);
99                         if (resolvedID != null)
100                         {
101                                 if (resolvedID.startsWith("class:"))
102                                 {
103                                         String className = resolvedID.substring(6);
104                                         tryLoadComponentClass(className);
105                                         ComponentSupplier componentSupplier = componentSuppliers.get(className);
106                                         if (componentSupplier != null)
107                                                 return componentSupplier.create(model, params, name);
108                                         throw new IllegalArgumentException("Component supplier not found for ID " + id + " (resolved: " + resolvedID + ")");
109                                 } else if (params != null && !JsonNull.INSTANCE.equals(params))
110                                         throw new IllegalArgumentException("Can't give params to a component deserialized from a JSON file");
111                                 if (resolvedID.startsWith("resource:"))
112                                 {
113                                         String[] parts = resolvedID.split(":");
114                                         if (parts.length != 3)
115                                                 throw new IllegalArgumentException("invaild resource id: " + resolvedID);
116                                         String rLoadID = parts[1];
117                                         String resID = parts[2];
118                                         try
119                                         {
120                                                 ResourceLoader loader;
121                                                 if (!resourceLoaders.containsKey(rLoadID))
122                                                 {
123                                                         Class<?> c = Class.forName(rLoadID);
124                                                         if (ResourceLoader.class.isAssignableFrom(c))
125                                                                 loader = (ResourceLoader) c.getConstructor().newInstance();
126                                                         else
127                                                                 loader = (ResourceLoader) Objects.requireNonNull(c.getMethod("resourceLoader").invoke(null));
128                                                         resourceLoaders.put(rLoadID, loader);
129                                                 } else
130                                                 {
131                                                         loader = Objects.requireNonNull(resourceLoaders.get(parts[1]));
132                                                 }
133                                                 JsonObject jsonContents = JsonHandler.readJson(loader.loadResource(resID), JsonObject.class);
134                                                 return loadComponentFromJsonObject(model, id, name, jsonContents);
135                                         }
136                                         catch (IOException e)
137                                         {
138                                                 throw new UncheckedIOException(e);
139                                         }
140                                         catch (ClassCastException | ReflectiveOperationException e)
141                                         {
142                                                 throw new IllegalArgumentException("invaild resource loader specified:" + parts[1], e);
143                                         }
144                                 } else if (resolvedID.startsWith("file:"))
145                                 {
146                                         try
147                                         {
148                                                 String filename = resolvedID.substring(5);
149                                                 JsonObject jsonContents = JsonHandler.readJson(filename, JsonObject.class);
150                                                 return loadComponentFromJsonObject(model, id, name, jsonContents);
151                                         }
152                                         catch (IOException e)
153                                         {
154                                                 throw new UncheckedIOException(e);
155                                         }
156                                 } else
157                                 {
158                                         throw new IllegalArgumentException("unable to resolve/interpret id" + resolvedID);
159                                 }
160                         }
161                 }
162                 throw new RuntimeException("Could not get component supplier for ID " + id);
163         }
164
165         public static String resolveID(String id)
166         {
167                 if (id.matches("(file|class|resource):.+"))
168                         return id;
169                 return standardComponentIDs.get(id);
170         }
171
172         private static SubmodelComponent loadComponentFromJsonObject(ViewModelModifiable model, String id, String name, JsonObject jsonContents)
173         {
174                 SerializablePojo jsonContentsAsSerializablePojo = JsonHandler.parser.fromJson(jsonContents, SerializablePojo.class);
175                 if (jsonContentsAsSerializablePojo.version == null)
176                         return LegacySubmodelComponentSerializer.deserialize(model,
177                                         JsonHandler.parser.fromJson(jsonContents, LegacySubmodelComponentParams.class), name, id, null);
178                 return SubmodelComponentSerializer.deserialize(model, JsonHandler.parser.fromJson(jsonContents, SubmodelComponentParams.class),
179                                 name, id, null);
180         }
181
182         public static void registerResourceLoader(ResourceLoader resourceLoader)
183         {
184                 registerResourceLoader(resourceLoader, resourceLoader.getClass());
185         }
186
187         public static void registerResourceLoader(ResourceLoader resourceLoader, Class<?> reference)
188         {
189                 resourceLoaders.put(reference.getName(), Objects.requireNonNull(resourceLoader));
190         }
191
192         public static void registerResourceLoader(ResourceLoader resourceLoader, String reference)
193         {
194                 resourceLoaders.put(reference, Objects.requireNonNull(resourceLoader));
195         }
196
197         private static void tryLoadComponentClass(String componentClassName)
198         {
199                 CodeSnippetSupplier.tryInvokeStaticInitializer(componentClassName, "Error loading component class %s: %s\n");
200         }
201
202         public static interface ComponentSupplier
203         {
204                 public GUIComponent create(ViewModelModifiable model, JsonElement params, String name);
205         }
206 }