Skip to content

Commit

Permalink
Add StaticType-based generic bounds for peer config utils (#1359)
Browse files Browse the repository at this point in the history
  • Loading branch information
AutonomicPerfectionist authored Oct 22, 2023
1 parent 2674733 commit 1087f2d
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 20 deletions.
14 changes: 12 additions & 2 deletions src/main/java/org/myrobotlab/codec/CodecUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand All @@ -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 extends ServiceConfig> C readServiceConfig(String filename, StaticType<C> 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);
}
}

/**
Expand Down
25 changes: 17 additions & 8 deletions src/main/java/org/myrobotlab/framework/Service.java
Original file line number Diff line number Diff line change
Expand Up @@ -1421,23 +1421,28 @@ public T getConfig() {
* i01."opencv"
* @return
*/
public ServiceConfig getPeerConfig(String peerKey) {
public <P extends ServiceConfig> P getPeerConfig(String peerKey, StaticType<P> 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<P> si = (ConfigurableService<P>) 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;
Expand All @@ -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;
Expand All @@ -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<Service<ServiceConfig>>() {});
if (cs != null) {
cs.apply(sc); // TODO - look for applies if its read from the file system
// it needs to update Runtime.plan
Expand Down Expand Up @@ -2845,20 +2850,24 @@ 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
*
* @param peerKey
* @param config
*/
public void applyPeerConfig(String peerKey, ServiceConfig config) {
public <P extends ServiceConfig> void applyPeerConfig(String peerKey, P config, StaticType<Service<P>> configServiceType) {
String peerName = getPeerName(peerKey);

Runtime.getPlan().put(peerName, config);

// meh - templating is not very helpful here
ConfigurableService si = (ConfigurableService) Runtime.getService(peerName);
ConfigurableService<P> si = Runtime.getService(peerName, configServiceType);
if (si != null) {
si.apply(config);
}
Expand Down
26 changes: 26 additions & 0 deletions src/main/java/org/myrobotlab/framework/StaticType.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> asClass() {
if (storedType instanceof Class) {
return (Class<T>) storedType;
} else {
throw new IllegalStateException("Stored type " + storedType + " is not a Class.");
}
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
Expand All @@ -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.
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/org/myrobotlab/service/InMoov2.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);

Expand Down
34 changes: 26 additions & 8 deletions src/main/java/org/myrobotlab/service/Runtime.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -1152,6 +1153,14 @@ public static Map<String, ServiceInterface> getRegistry() {
return registry;// FIXME should return copy
}

public static ServiceInterface getService(String inName) {
return getService(inName, new StaticType<>() {});
}

public static <C extends ServiceConfig, S extends ServiceInterface & ConfigurableService<C>> S getConfigurableService(String inName, StaticType<S> 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.
Expand All @@ -1160,7 +1169,8 @@ public static Map<String, ServiceInterface> 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 extends ServiceInterface> S getService(String inName, StaticType<S> serviceType) {
if (inName == null) {
return null;
}
Expand All @@ -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);
}
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -4806,15 +4816,23 @@ 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
* of current config set name or default
* @param name
* @return
*/
public ServiceConfig readServiceConfig(String name) {
return readServiceConfig(null, name);
public <C extends ServiceConfig> C readServiceConfig(String name, StaticType<C> configType) {
return readServiceConfig(null, name, configType);
}

public ServiceConfig readServiceConfig(String configName, String name) {
return readServiceConfig(configName, name, new StaticType<>() {});
}

/**
Expand All @@ -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 extends ServiceConfig> C readServiceConfig(String configName, String name, StaticType<C> configType) {
// if config path set and yaml file exists - it takes precedence

if (configName == null) {
Expand All @@ -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) {
Expand Down
5 changes: 3 additions & 2 deletions src/test/java/org/myrobotlab/service/InMoov2Test.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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);

}
Expand Down

0 comments on commit 1087f2d

Please sign in to comment.