Small performance optimisations
[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.util.Collections;
7 import java.util.HashMap;
8 import java.util.Map;
9 import java.util.Objects;
10 import com.google.gson.JsonElement;
11 import com.google.gson.JsonNull;
12 import com.google.gson.JsonObject;
13
14 import net.mograsim.logic.model.model.ViewModelModifiable;
15 import net.mograsim.logic.model.model.components.GUIComponent;
16 import net.mograsim.logic.model.model.components.submodels.SubmodelComponent;
17 import net.mograsim.logic.model.snippets.CodeSnippetSupplier;
18 import net.mograsim.logic.model.util.JsonHandler;
19
20 public class IndirectGUIComponentCreator
21 {
22         private static final Map<String, String> standardComponentIDs = new HashMap<>();
23         private static final Map<String, String> standardComponentIDsUnmodifiable = Collections.unmodifiableMap(standardComponentIDs);
24
25         private static final Map<String, ComponentSupplier> componentSuppliers = new HashMap<>();
26         private static final Map<String, ResourceLoader> resourceLoaders = new HashMap<>();
27         private static final Map<String, JsonObject> componentCache = new HashMap<>();
28
29         static
30         {
31                 loadStandardComponentIDs(IndirectGUIComponentCreator.class.getResourceAsStream("standardComponentIDMapping.json"));
32         }
33
34         public static void loadStandardComponentIDs(InputStream standardComponentIdMappingStream)
35         {
36                 try (InputStream s = standardComponentIdMappingStream)
37                 {
38                         if (s == null)
39                                 throw new IOException("Resource not found");
40                         Map<String, String> tmp = JsonHandler.readJson(s, Map.class);
41                         // don't use putAll to apply sanity checks
42                         tmp.forEach((st, id) ->
43                         {
44                                 try
45                                 {
46                                         addStandardComponentID(st, id);
47                                 }
48                                 catch (IllegalArgumentException e)
49                                 {
50                                         System.err.println("Component ID mapping contained illegal entry: " + e.getMessage());
51                                 }
52                         });
53                 }
54                 catch (IOException e)
55                 {
56                         System.err.println("Failed to initialize standard snippet ID mapping: " + e.getMessage());
57                 }
58         }
59
60         public static void addStandardComponentID(String standardComponentID, String associatedComponentID)
61         {
62                 if (!associatedComponentID.matches("(file|class|resource):.+"))
63                         throw new IllegalArgumentException("Unrecognized component ID format: " + associatedComponentID);
64                 standardComponentIDs.put(standardComponentID, associatedComponentID);
65         }
66
67         public static Map<String, String> getStandardComponentIDs()
68         {
69                 return standardComponentIDsUnmodifiable;
70         }
71
72         public static void setComponentSupplier(String className, ComponentSupplier componentSupplier)
73         {
74                 componentSuppliers.put(className, componentSupplier);
75         }
76
77         public static GUIComponent createComponent(ViewModelModifiable model, String id)
78         {
79                 return createComponent(model, id, (String) null);
80         }
81
82         public static GUIComponent createComponent(ViewModelModifiable model, String id, String name)
83         {
84                 return createComponent(model, id, JsonNull.INSTANCE, name);
85         }
86
87         public static GUIComponent createComponent(ViewModelModifiable model, String id, JsonElement params)
88         {
89                 return createComponent(model, id, params, null);
90         }
91
92         public static GUIComponent createComponent(ViewModelModifiable model, String id, JsonElement params, String name)
93         {
94                 if (id != null)
95                 {
96                         if (componentCache.containsKey(id))
97                                 return loadComponentFromJsonObject(model, id, name, componentCache.get(id));
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                                                 if (resID.endsWith(".json"))
134                                                 {
135                                                         JsonObject jsonContents = JsonHandler.readJson(loader.loadResource(resID), JsonObject.class);
136                                                         return loadComponentFromJsonObject(model, id, name, jsonContents);
137                                                 }
138                                                 if (!componentSuppliers.containsKey(resID))
139                                                         loader.loadClass(resID);
140                                                 ComponentSupplier componentSupplier = componentSuppliers.get(resID);
141                                                 if (componentSupplier != null)
142                                                         return componentSupplier.create(model, params, name);
143                                                 throw new IllegalArgumentException("Component supplier not found for ID " + id + " (class cannot initialize?)");
144                                         }
145                                         catch (IOException e)
146                                         {
147                                                 throw new UncheckedIOException(e);
148                                         }
149                                         catch (ClassCastException | ReflectiveOperationException e)
150                                         {
151                                                 throw new IllegalArgumentException("class not found / invaild resource loader specified:" + parts[1], e);
152                                         }
153                                 } else if (resolvedID.startsWith("file:"))
154                                 {
155                                         try
156                                         {
157                                                 String filename = resolvedID.substring(5);
158                                                 JsonObject jsonContents = JsonHandler.readJson(filename, JsonObject.class);
159                                                 return loadComponentFromJsonObject(model, id, name, jsonContents);
160                                         }
161                                         catch (IOException e)
162                                         {
163                                                 throw new UncheckedIOException(e);
164                                         }
165                                 } else
166                                 {
167                                         throw new IllegalArgumentException("unable to resolve/interpret id" + resolvedID);
168                                 }
169                         }
170                 }
171                 throw new RuntimeException("Could not get component supplier for ID " + id);
172         }
173
174         public static String resolveID(String id)
175         {
176                 if (id.matches("(file|class|resource):.+"))
177                         return id;
178                 return standardComponentIDs.get(id);
179         }
180
181         private static SubmodelComponent loadComponentFromJsonObject(ViewModelModifiable model, String id, String name, JsonObject jsonContents)
182         {
183                 componentCache.putIfAbsent(id, jsonContents);
184                 SerializablePojo jsonContentsAsSerializablePojo = JsonHandler.parser.fromJson(jsonContents, SerializablePojo.class);
185                 if (jsonContentsAsSerializablePojo.version == null)
186                         return LegacySubmodelComponentSerializer.deserialize(model,
187                                         JsonHandler.parser.fromJson(jsonContents, LegacySubmodelComponentParams.class), name, id, null);
188                 return SubmodelComponentSerializer.deserialize(model, JsonHandler.parser.fromJson(jsonContents, SubmodelComponentParams.class),
189                                 name, id, null);
190         }
191
192         public static void registerResourceLoader(ResourceLoader resourceLoader)
193         {
194                 registerResourceLoader(resourceLoader, resourceLoader.getClass());
195         }
196
197         public static void registerResourceLoader(ResourceLoader resourceLoader, Class<?> reference)
198         {
199                 resourceLoaders.put(reference.getName(), Objects.requireNonNull(resourceLoader));
200         }
201
202         public static void registerResourceLoader(ResourceLoader resourceLoader, String reference)
203         {
204                 resourceLoaders.put(reference, Objects.requireNonNull(resourceLoader));
205         }
206
207         private static void tryLoadComponentClass(String componentClassName)
208         {
209                 CodeSnippetSupplier.tryInvokeStaticInitializer(componentClassName, "Error loading component class %s: %s\n");
210         }
211
212         public static interface ComponentSupplier
213         {
214                 public GUIComponent create(ViewModelModifiable model, JsonElement params, String name);
215         }
216 }