Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add StaticType-based generic bounds for peer config utils #1359

Merged
merged 1 commit into from
Oct 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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