From 1087f2dc45ace3ff6cbbb61d989e45b1ab23a28d Mon Sep 17 00:00:00 2001 From: Branden Butler Date: Sun, 22 Oct 2023 16:54:13 -0500 Subject: [PATCH] Add StaticType-based generic bounds for peer config utils (#1359) --- .../java/org/myrobotlab/codec/CodecUtils.java | 14 ++++++-- .../org/myrobotlab/framework/Service.java | 25 +++++++++----- .../org/myrobotlab/framework/StaticType.java | 26 ++++++++++++++ .../java/org/myrobotlab/service/InMoov2.java | 4 +++ .../java/org/myrobotlab/service/Runtime.java | 34 ++++++++++++++----- .../org/myrobotlab/service/InMoov2Test.java | 5 +-- 6 files changed, 88 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/myrobotlab/codec/CodecUtils.java b/src/main/java/org/myrobotlab/codec/CodecUtils.java index cb7698d26a..bd512189b8 100644 --- a/src/main/java/org/myrobotlab/codec/CodecUtils.java +++ b/src/main/java/org/myrobotlab/codec/CodecUtils.java @@ -5,6 +5,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.InvalidObjectException; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.Serializable; @@ -1481,6 +1482,10 @@ public static boolean isLocal(String name, String id) { return name.substring(name.indexOf("@") + 1).equals(id); } + public static ServiceConfig readServiceConfig(String filename) throws IOException { + return readServiceConfig(filename, new StaticType<>() {}); + } + /** * Read a YAML file given by the filename and convert it into a ServiceConfig * object by deserialization. @@ -1491,10 +1496,15 @@ public static boolean isLocal(String name, String id) { * @throws IOException * if reading the file fails */ - public static ServiceConfig readServiceConfig(String filename) throws IOException { + public static C readServiceConfig(String filename, StaticType type) throws IOException { String data = Files.readString(Paths.get(filename)); Yaml yaml = new Yaml(); - return yaml.load(data); + C parsed = yaml.load(data); + if (type.asClass().isAssignableFrom(parsed.getClass())) { + return parsed; + } else { + throw new InvalidObjectException("Deserialized type was " + parsed.getClass() + ", expected " + type + ". Deserialized object: " + parsed); + } } /** diff --git a/src/main/java/org/myrobotlab/framework/Service.java b/src/main/java/org/myrobotlab/framework/Service.java index d9290b1527..a84d9b8f2a 100644 --- a/src/main/java/org/myrobotlab/framework/Service.java +++ b/src/main/java/org/myrobotlab/framework/Service.java @@ -1421,23 +1421,28 @@ public T getConfig() { * i01."opencv" * @return */ - public ServiceConfig getPeerConfig(String peerKey) { + public

P getPeerConfig(String peerKey, StaticType

type) { String peerName = getPeerName(peerKey); if (peerName == null) { error("peer name not found for peer key %s", peerKey); return null; } - ServiceInterface si = Runtime.getService(peerName); + // Java generics don't let us create a new StaticType using + // P here because the type variable is erased, so we have to cast anyway for now + ConfigurableService

si = (ConfigurableService

) Runtime.getService(peerName); if (si != null) { // peer is currently running - get its config - return si.getConfig(); + P c = si.getConfig(); + if (type.asClass().isAssignableFrom(c.getClass())) { + return c; + } } // peer is not currently running attempt to read from config Runtime runtime = Runtime.getInstance(); // read current service config for this peer service - ServiceConfig sc = runtime.readServiceConfig(peerName); + P sc = runtime.readServiceConfig(peerName, type); if (sc == null) { error("peer service %s is defined, but %s.yml not available on filesystem", peerKey, peerName); return null; @@ -1446,7 +1451,7 @@ public ServiceConfig getPeerConfig(String peerKey) { } public void setPeerConfigValue(String peerKey, String fieldname, Object value) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { - ServiceConfig sc = getPeerConfig(peerKey); + ServiceConfig sc = getPeerConfig(peerKey, new StaticType<>() {}); if (sc == null) { error("invalid config for peer key %s field name %s", peerKey, fieldname); return; @@ -1455,7 +1460,7 @@ public void setPeerConfigValue(String peerKey, String fieldname, Object value) t field.set(sc, value); savePeerConfig(peerKey, sc); String peerName = getPeerName(peerKey); - ConfigurableService cs = (ConfigurableService) Runtime.getService(peerName); + var cs = Runtime.getConfigurableService(peerName, new StaticType>() {}); if (cs != null) { cs.apply(sc); // TODO - look for applies if its read from the file system // it needs to update Runtime.plan @@ -2845,6 +2850,10 @@ public void apply() { apply((T) sc); } + public void applyPeerConfig(String peerKey, ServiceConfig config) { + applyPeerConfig(peerKey, config, new StaticType<>() {}); + } + /** * Apply the config to a peer, regardless if the peer is currently running or * not @@ -2852,13 +2861,13 @@ public void apply() { * @param peerKey * @param config */ - public void applyPeerConfig(String peerKey, ServiceConfig config) { + public

void applyPeerConfig(String peerKey, P config, StaticType> configServiceType) { String peerName = getPeerName(peerKey); Runtime.getPlan().put(peerName, config); // meh - templating is not very helpful here - ConfigurableService si = (ConfigurableService) Runtime.getService(peerName); + ConfigurableService

si = Runtime.getService(peerName, configServiceType); if (si != null) { si.apply(config); } diff --git a/src/main/java/org/myrobotlab/framework/StaticType.java b/src/main/java/org/myrobotlab/framework/StaticType.java index fddc2912f9..6de5b48504 100644 --- a/src/main/java/org/myrobotlab/framework/StaticType.java +++ b/src/main/java/org/myrobotlab/framework/StaticType.java @@ -81,6 +81,27 @@ public Type getType() { return storedType; } + /** + * Get the stored type as a Class object + * that can be used for checking cast + * compatibility. Note that the resulting + * Class object will not check for generic + * compatibility. If the stored type + * is not a Class type, then this method + * throws {@link IllegalStateException}. + * + * @return The internal stored type cast to a Class object + * @throws IllegalStateException if the stored type is not a Class. + */ + @SuppressWarnings("unchecked") + public Class asClass() { + if (storedType instanceof Class) { + return (Class) storedType; + } else { + throw new IllegalStateException("Stored type " + storedType + " is not a Class."); + } + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -96,6 +117,11 @@ public int hashCode() { return storedType != null ? storedType.hashCode() : 0; } + + @Override + public String toString() { + return storedType.toString(); + } /** * Function to recursively validate type parameters * to ensure they are all concrete so no type variables sneak in. diff --git a/src/main/java/org/myrobotlab/service/InMoov2.java b/src/main/java/org/myrobotlab/service/InMoov2.java index e9fa9d975e..e430ac75dd 100644 --- a/src/main/java/org/myrobotlab/service/InMoov2.java +++ b/src/main/java/org/myrobotlab/service/InMoov2.java @@ -16,6 +16,7 @@ import org.myrobotlab.framework.Platform; import org.myrobotlab.framework.Registration; import org.myrobotlab.framework.Service; +import org.myrobotlab.framework.StaticType; import org.myrobotlab.framework.Status; import org.myrobotlab.framework.interfaces.ServiceInterface; import org.myrobotlab.io.FileIO; @@ -28,6 +29,7 @@ import org.myrobotlab.service.abstracts.AbstractSpeechRecognizer; import org.myrobotlab.service.abstracts.AbstractSpeechSynthesis; import org.myrobotlab.service.config.InMoov2Config; +import org.myrobotlab.service.config.OpenCVConfig; import org.myrobotlab.service.config.ServiceConfig; import org.myrobotlab.service.data.JoystickData; import org.myrobotlab.service.data.LedDisplayData; @@ -1939,6 +1941,8 @@ public static void main(String[] args) { webgui.startService(); InMoov2 i01 = (InMoov2)Runtime.start("i01","InMoov2"); + OpenCVConfig ocvConfig = i01.getPeerConfig("opencv", new StaticType<>() {}); + ocvConfig.flip = true; i01.setPeerConfigValue("opencv", "flip", true); // i01.savePeerConfig("", null); diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index 8f2035fbd6..9a385bcd7d 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -61,6 +61,7 @@ import org.myrobotlab.framework.Registration; import org.myrobotlab.framework.Service; import org.myrobotlab.framework.ServiceReservation; +import org.myrobotlab.framework.StaticType; import org.myrobotlab.framework.Status; import org.myrobotlab.framework.interfaces.ConfigurableService; import org.myrobotlab.framework.interfaces.MessageListener; @@ -1152,6 +1153,14 @@ public static Map getRegistry() { return registry;// FIXME should return copy } + public static ServiceInterface getService(String inName) { + return getService(inName, new StaticType<>() {}); + } + + public static > S getConfigurableService(String inName, StaticType serviceType) { + return getService(inName, serviceType); + } + /** * Gets a running service with the specified name. If the name is null or * there's no such service with the specified name, returns null instead. @@ -1160,7 +1169,8 @@ public static Map getRegistry() { * The name of the service * @return The service if it exists, or null */ - public static ServiceInterface getService(String inName) { + @SuppressWarnings("unchecked") + public static S getService(String inName, StaticType serviceType) { if (inName == null) { return null; } @@ -1170,7 +1180,7 @@ public static ServiceInterface getService(String inName) { if (!registry.containsKey(name)) { return null; } else { - return registry.get(name); + return (S) registry.get(name); } } @@ -2572,7 +2582,7 @@ static public void startConfig(String configName) { Runtime runtime = Runtime.getInstance(); runtime.processingConfig = true; // multiple inbox threads not available runtime.invoke("publishStartConfig", configName); - RuntimeConfig rtConfig = (RuntimeConfig) runtime.readServiceConfig(runtime.getConfigName(), "runtime"); + RuntimeConfig rtConfig = runtime.readServiceConfig(runtime.getConfigName(), "runtime", new StaticType<>() {}); if (rtConfig == null) { runtime.error("cannot find %s%s%s", runtime.getConfigName(), fs, "runtime.yml"); return; @@ -4806,6 +4816,10 @@ synchronized private Plan loadService(Plan plan, String name, String type, boole return plan; } + + public ServiceConfig readServiceConfig(String name) { + return readServiceConfig(name, new StaticType<>() {}); + } /** * read a service's configuration, in the context @@ -4813,8 +4827,12 @@ synchronized private Plan loadService(Plan plan, String name, String type, boole * @param name * @return */ - public ServiceConfig readServiceConfig(String name) { - return readServiceConfig(null, name); + public C readServiceConfig(String name, StaticType configType) { + return readServiceConfig(null, name, configType); + } + + public ServiceConfig readServiceConfig(String configName, String name) { + return readServiceConfig(configName, name, new StaticType<>() {}); } /** @@ -4825,7 +4843,7 @@ public ServiceConfig readServiceConfig(String name) { * - name of config file within that dir e.g. {name}.yml * @return */ - public ServiceConfig readServiceConfig(String configName, String name) { + public C readServiceConfig(String configName, String name, StaticType configType) { // if config path set and yaml file exists - it takes precedence if (configName == null) { @@ -4839,10 +4857,10 @@ public ServiceConfig readServiceConfig(String configName, String name) { String filename = CONFIG_ROOT + fs + configName + fs + name + ".yml"; File check = new File(filename); - ServiceConfig sc = null; + C sc = null; if (check.exists()) { try { - sc = CodecUtils.readServiceConfig(filename); + sc = CodecUtils.readServiceConfig(filename, configType); } catch (ConstructorException e) { error("%s invalid %s %s. Please remove it from the file.", name, filename, e.getCause().getMessage()); } catch (IOException e) { diff --git a/src/test/java/org/myrobotlab/service/InMoov2Test.java b/src/test/java/org/myrobotlab/service/InMoov2Test.java index 9be0e28537..c9ae337b69 100644 --- a/src/test/java/org/myrobotlab/service/InMoov2Test.java +++ b/src/test/java/org/myrobotlab/service/InMoov2Test.java @@ -3,6 +3,7 @@ import static org.junit.Assert.assertTrue; import org.junit.Test; +import org.myrobotlab.framework.StaticType; import org.myrobotlab.service.config.OpenCVConfig; public class InMoov2Test { @@ -14,11 +15,11 @@ public void testCvFilters() throws NoSuchFieldException, SecurityException, Ille // flip i01.setPeerConfigValue("opencv", "flip", true); - OpenCVConfig cvconfig = (OpenCVConfig)i01.getPeerConfig("opencv"); + OpenCVConfig cvconfig = i01.getPeerConfig("opencv", new StaticType<>() {}); assertTrue(cvconfig.flip); i01.setPeerConfigValue("opencv", "flip", false); - cvconfig = (OpenCVConfig)i01.getPeerConfig("opencv"); + cvconfig = i01.getPeerConfig("opencv", new StaticType<>() {}); assertFalse(cvconfig.flip); }