From 9ff1d962ac7cebd3b3ee03146689d23aa8d4311d Mon Sep 17 00:00:00 2001 From: Branden Butler Date: Thu, 7 Sep 2023 08:51:22 -0500 Subject: [PATCH] Fix misc bugs (#1338) * Fix incorrect duplicate deps checking, regen pom * Add StringUtil docstring and test * Fix incorrect full type creation * Add additional fallback lookup for service configs based on generic superclasses * Fix broken installation * Fix Sphinx, sort of --- pom.xml | 948 +++++++++--------- .../java/org/myrobotlab/codec/CodecUtils.java | 2 +- .../myrobotlab/framework/repo/IvyWrapper.java | 2 +- .../framework/repo/MavenWrapper.java | 8 +- .../framework/repo/ServiceData.java | 31 +- .../framework/repo/ServiceDependency.java | 21 + .../java/org/myrobotlab/service/Runtime.java | 9 +- .../java/org/myrobotlab/service/Sphinx.java | 60 +- .../service/config/ServiceConfig.java | 39 +- .../org/myrobotlab/string/StringUtil.java | 32 +- .../org/myrobotlab/string/StringUtilTest.java | 6 + 11 files changed, 600 insertions(+), 558 deletions(-) diff --git a/pom.xml b/pom.xml index f3502dffce..a1507381a2 100644 --- a/pom.xml +++ b/pom.xml @@ -1,102 +1,102 @@ - - - 4.0.0 - org.myrobotlab - mrl - 0.0.1-SNAPSHOT - MyRobotLab - Open Source Creative Machine Control - - - false - - - - 1.1. - - ${maven.build.timestamp} - yyyyMMddHHmm - ${timestamp} - ${version.prefix}${build.number} - ${git.branch} - ${NODE_NAME} - ${NODE_LABELS} - - - - 11 - 11 - UTF-8 - - - + + + 4.0.0 + org.myrobotlab + mrl + 0.0.1-SNAPSHOT + MyRobotLab + Open Source Creative Machine Control + + + false + + + + 1.1. + + ${maven.build.timestamp} + yyyyMMddHHmm + ${timestamp} + ${version.prefix}${build.number} + ${git.branch} + ${NODE_NAME} + ${NODE_LABELS} + + + + 11 + 11 + UTF-8 + + + @@ -135,9 +135,9 @@ https://m2.dv8tion.net/releases - - - + + + javazoom @@ -1734,375 +1734,375 @@ - - - org.mockito - mockito-core - 3.12.4 - test - - - - - - - false - src/main/resources - - - false - src/main/java - - ** - - - **/*.java - - - - - - false - src/test/resources - - - false - src/test/java - - ** - - - **/*.java - - - - src/main/resources - ${project.basedir} - - - - - - - - org.codehaus.mojo - properties-maven-plugin - 1.0.0 - - - org.apache.maven.plugins - maven-enforcer-plugin - 3.1.0 - - - - - - - - org.apache.maven.plugins - maven-enforcer-plugin - - - no-duplicate-declared-dependencies - - enforce - - - - - - - - - - - - org.codehaus.mojo - properties-maven-plugin - - - initialize - - read-project-properties - - - - build.properties - - - - - - - - - - org.apache.maven.plugins - maven-shade-plugin - 3.1.0 - - - package - - shade - - - myrobotlab - - true - myrobotlab-full - false - - - - - org.myrobotlab.service.Runtime - ${version} - ${version} - - ${build.number} - ${maven.build.timestamp} - ${agent.name} - ${user.name} - - - ${git.tags} - ${git.branch} - ${git.dirty} - ${git.remote.origin.url} - ${git.commit.id} - ${git.commit.id.abbrev} - ${git.commit.id.full} - ${git.commit.id.describe} - ${git.commit.id.describe-short} - ${git.commit.user.name} - ${git.commit.user.email} - - ${git.commit.time} - ${git.closest.tag.name} - ${git.closest.tag.commit.count} - ${git.build.user.name} - ${git.build.user.email} - ${git.build.time} - ${git.build.version} - - - - - - - *:* - - module-info.class - META-INF/*.SF - META-INF/*.DSA - META-INF/*.RSA - - - - - - - - - - org.apache.maven.plugins - maven-assembly-plugin - - - assembly.xml - - myrobotlab - false - - - - trigger-assembly - package - - single - - - - - - - true - org.apache.maven.plugins - maven-compiler-plugin - 2.3.2 - - 11 - 11 - true - true - -parameters - - - - - org.apache.maven.plugins - maven-resources-plugin - 2.4.3 - - - - pl.project13.maven - git-commit-id-plugin - 4.9.10 - - - initialize - get-the-git-infos - - revision - - - - - ${project.basedir}/.git - git - false - true - ${project.build.outputDirectory}/git.properties - - - false - false - -dirty - - - - - - maven-surefire-plugin - org.apache.maven.plugins - 2.22.2 - - -Djava.library.path=libraries/native -Djna.library.path=libraries/native - - **/*Test.java - - - **/integration/* - - - - - - - - org.apache.maven.plugins - maven-clean-plugin - 2.3 - - - - data/.myrobotlab - false - - - libraries - - ** - - false - - - data - - ** - - - - resource - - ** - - - - src/main/resources/resource/framework - - **/serviceData.json - - false - - - - - - - - - - - - org.apache.maven.plugins - maven-surefire-report-plugin - 2.22.2 - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.0.1 - - - - - myrobotlab - http://myrobotlab.org - - - github - https://github.com/MyRobotLab/myrobotlab/issues - - + + + org.mockito + mockito-core + 3.12.4 + test + + + + + + + false + src/main/resources + + + false + src/main/java + + ** + + + **/*.java + + + + + + false + src/test/resources + + + false + src/test/java + + ** + + + **/*.java + + + + src/main/resources + ${project.basedir} + + + + + + + + org.codehaus.mojo + properties-maven-plugin + 1.0.0 + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.1.0 + + + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + no-duplicate-declared-dependencies + + enforce + + + + + + + + + + + + org.codehaus.mojo + properties-maven-plugin + + + initialize + + read-project-properties + + + + build.properties + + + + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.1.0 + + + package + + shade + + + myrobotlab + + true + myrobotlab-full + false + + + + + org.myrobotlab.service.Runtime + ${version} + ${version} + + ${build.number} + ${maven.build.timestamp} + ${agent.name} + ${user.name} + + + ${git.tags} + ${git.branch} + ${git.dirty} + ${git.remote.origin.url} + ${git.commit.id} + ${git.commit.id.abbrev} + ${git.commit.id.full} + ${git.commit.id.describe} + ${git.commit.id.describe-short} + ${git.commit.user.name} + ${git.commit.user.email} + + ${git.commit.time} + ${git.closest.tag.name} + ${git.closest.tag.commit.count} + ${git.build.user.name} + ${git.build.user.email} + ${git.build.time} + ${git.build.version} + + + + + + + *:* + + module-info.class + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assembly.xml + + myrobotlab + false + + + + trigger-assembly + package + + single + + + + + + + true + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 11 + 11 + true + true + -parameters + + + + + org.apache.maven.plugins + maven-resources-plugin + 2.4.3 + + + + pl.project13.maven + git-commit-id-plugin + 4.9.10 + + + initialize + get-the-git-infos + + revision + + + + + ${project.basedir}/.git + git + false + true + ${project.build.outputDirectory}/git.properties + + + false + false + -dirty + + + + + + maven-surefire-plugin + org.apache.maven.plugins + 2.22.2 + + -Djava.library.path=libraries/native -Djna.library.path=libraries/native + + **/*Test.java + + + **/integration/* + + + + + + + + org.apache.maven.plugins + maven-clean-plugin + 2.3 + + + + data/.myrobotlab + false + + + libraries + + ** + + false + + + data + + ** + + + + resource + + ** + + + + src/main/resources/resource/framework + + **/serviceData.json + + false + + + + + + + + + + + + org.apache.maven.plugins + maven-surefire-report-plugin + 2.22.2 + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.0.1 + + + + + myrobotlab + http://myrobotlab.org + + + github + https://github.com/MyRobotLab/myrobotlab/issues + + diff --git a/src/main/java/org/myrobotlab/codec/CodecUtils.java b/src/main/java/org/myrobotlab/codec/CodecUtils.java index 6b37b525ee..cb7698d26a 100644 --- a/src/main/java/org/myrobotlab/codec/CodecUtils.java +++ b/src/main/java/org/myrobotlab/codec/CodecUtils.java @@ -237,7 +237,7 @@ public static String makeFullTypeName(String type) { return null; } if (!type.contains(".")) { - return String.format("org.myrobotlab.service.%s", type); + return ("Service".equals(type)) ? "org.myrobotlab.framework.Service" : String.format("org.myrobotlab.service.%s", type); } return type; } diff --git a/src/main/java/org/myrobotlab/framework/repo/IvyWrapper.java b/src/main/java/org/myrobotlab/framework/repo/IvyWrapper.java index 023411b3f8..5759c4866e 100644 --- a/src/main/java/org/myrobotlab/framework/repo/IvyWrapper.java +++ b/src/main/java/org/myrobotlab/framework/repo/IvyWrapper.java @@ -477,7 +477,7 @@ synchronized public void install(String location, String[] serviceTypes) { } if (err.size() > 0) { - log.error("had errors - repo will not be updated"); + log.error("had errors - repo will not be updated. Errors:\n{}", err); } else { // TODO - promote to Repo.setInstalled diff --git a/src/main/java/org/myrobotlab/framework/repo/MavenWrapper.java b/src/main/java/org/myrobotlab/framework/repo/MavenWrapper.java index c490e2e6ac..8374f92cdf 100644 --- a/src/main/java/org/myrobotlab/framework/repo/MavenWrapper.java +++ b/src/main/java/org/myrobotlab/framework/repo/MavenWrapper.java @@ -155,11 +155,11 @@ public void createPom(String location, String[] serviceTypes) throws IOException // If we haven't seen this dependency before, add it to our known // dependencies - if (!allDependencies.containsKey(serviceDependency.getKey())) - allDependencies.put(serviceDependency.getKey(), new ArrayList<>(List.of(serviceDependency))); + if (!allDependencies.containsKey(serviceDependency.getProjectCoordinates())) + allDependencies.put(serviceDependency.getProjectCoordinates(), new ArrayList<>(List.of(serviceDependency))); else { // We have seen it, so loop over all dependencies with matching keys - allDependencies.get(serviceDependency.getKey()).forEach(existingDependency -> { + allDependencies.get(serviceDependency.getProjectCoordinates()).forEach(existingDependency -> { // Check priority, if this dependency is higher priority than // existing, @@ -173,7 +173,7 @@ public void createPom(String location, String[] serviceTypes) throws IOException serviceDependency.setSkipped(true); }); // Add the dependency to the known dependencies - allDependencies.get(serviceDependency.getKey()).add(serviceDependency); + allDependencies.get(serviceDependency.getProjectCoordinates()).add(serviceDependency); } }); diff --git a/src/main/java/org/myrobotlab/framework/repo/ServiceData.java b/src/main/java/org/myrobotlab/framework/repo/ServiceData.java index d1f6818449..00c4406d4e 100644 --- a/src/main/java/org/myrobotlab/framework/repo/ServiceData.java +++ b/src/main/java/org/myrobotlab/framework/repo/ServiceData.java @@ -39,6 +39,16 @@ */ public class ServiceData implements Serializable { + /** + * the set of all categories + */ + public TreeMap categoryTypes = new TreeMap<>(); + + /** + * all services meta data is contained here + */ + public TreeMap serviceTypes = new TreeMap<>(); + static private ServiceData localInstance = null; static final public String LIBRARIES = "libraries"; @@ -57,7 +67,7 @@ public class ServiceData implements Serializable { private static final long serialVersionUID = 1L; - static private String serviceDataCacheFileName = LIBRARIES + File.separator + "serviceData.json"; + static private final String serviceDataCacheFileName = LIBRARIES + File.separator + "serviceData.json"; /** * clears all overrides. All services shall be using the standard hard co @@ -92,9 +102,8 @@ static public synchronized ServiceData generate() throws IOException { List services = FileIO.getServiceList(); log.info("found {} services", services.size()); - for (int i = 0; i < services.size(); ++i) { + for (String fullClassName : services) { - String fullClassName = services.get(i); log.debug("querying {}", fullClassName); try { @@ -112,7 +121,7 @@ static public synchronized ServiceData generate() throws IOException { sd.add(serviceType); for (String cat : serviceType.categories) { - Category category = null; + Category category; if (serviceType.isAvailable()) { if (sd.categoryTypes.containsKey(cat)) { category = sd.categoryTypes.get(cat); @@ -135,10 +144,10 @@ static public synchronized ServiceData generate() throws IOException { } static public List getDependencyKeys(String fullTypeName) { - List keys = new ArrayList(); + List keys = new ArrayList<>(); ServiceData sd = getLocalInstance(); if (!sd.serviceTypes.containsKey(fullTypeName)) { - log.error("{} not defined in service types"); + log.error("{} not defined in service types", fullTypeName); return keys; } @@ -278,16 +287,6 @@ static public Map getOverrides() { return planStore; } - /** - * the set of all categories - */ - TreeMap categoryTypes = new TreeMap(); - - /** - * all services meta data is contained here - */ - TreeMap serviceTypes = new TreeMap(); - public ServiceData() { } diff --git a/src/main/java/org/myrobotlab/framework/repo/ServiceDependency.java b/src/main/java/org/myrobotlab/framework/repo/ServiceDependency.java index 56322134d9..d8047f90dd 100644 --- a/src/main/java/org/myrobotlab/framework/repo/ServiceDependency.java +++ b/src/main/java/org/myrobotlab/framework/repo/ServiceDependency.java @@ -142,6 +142,27 @@ public String getKey() { return String.format("%s/%s/%s/%s", groupId, artifactId, version, ext); } + /** + * Gives the Maven coordinates for this dependency, + * which are the group ID, artifact ID, and version + * all separated by colons. + * @return The Maven coordinates for this dependency + */ + public String getCoordinates() { + return String.format("%s:%s:%s", groupId, artifactId, version); + } + + /** + * Gives the unique Maven coordinates of this dependency's project. + * This does not give the version, and is mainly used to determine + * if two dependencies are for the same project. + * + * @return The group ID and the artifact ID, separated by a colon + */ + public String getProjectCoordinates() { + return String.format("%s:%s", groupId, artifactId); + } + public void add(ServiceExclude serviceExclude) { excludes.add(serviceExclude); } diff --git a/src/main/java/org/myrobotlab/service/Runtime.java b/src/main/java/org/myrobotlab/service/Runtime.java index 9c57690e44..f6000f2a2f 100644 --- a/src/main/java/org/myrobotlab/service/Runtime.java +++ b/src/main/java/org/myrobotlab/service/Runtime.java @@ -694,12 +694,7 @@ static private synchronized ServiceInterface createService(String name, String t return null; } - String fullTypeName; - if (type.contains(".")) { - fullTypeName = type; - } else { - fullTypeName = String.format("org.myrobotlab.service.%s", type); - } + String fullTypeName = CodecUtils.makeFullTypeName(type); ServiceInterface si = Runtime.getService(fullName); if (si != null) { @@ -902,7 +897,7 @@ public static Runtime getInstance() { if (startYml.enable) { Runtime.load("runtime", "Runtime"); } - ((RuntimeConfig) runtime.config).add("runtime"); + runtime.config.add("runtime"); runtime.startService(); // platform virtual is higher priority than service virtual diff --git a/src/main/java/org/myrobotlab/service/Sphinx.java b/src/main/java/org/myrobotlab/service/Sphinx.java index af7a082a68..3be5294e08 100644 --- a/src/main/java/org/myrobotlab/service/Sphinx.java +++ b/src/main/java/org/myrobotlab/service/Sphinx.java @@ -41,6 +41,8 @@ import java.util.HashSet; import java.util.Map; +import edu.cmu.sphinx.api.Configuration; +import edu.cmu.sphinx.api.LiveSpeechRecognizer; import org.apache.commons.lang.StringUtils; import org.myrobotlab.framework.Message; import org.myrobotlab.framework.Service; @@ -57,7 +59,6 @@ import org.slf4j.Logger; import edu.cmu.sphinx.frontend.util.Microphone; -import edu.cmu.sphinx.recognizer.Recognizer; import edu.cmu.sphinx.result.Result; import edu.cmu.sphinx.util.props.ConfigurationManager; @@ -90,29 +91,28 @@ public void run() { String newPath = FileIO.getCfgDir() + File.separator + myService.getName() + ".xml"; File localGramFile = new File(newPath); + warn("CONFIG NOT IMPLEMENTED, ONLY SUPPORTS BASE EN-US"); + info("loading grammar file"); if (localGramFile.exists()) { info(String.format("grammar config %s", newPath)); - cm = new ConfigurationManager(newPath); } else { // resource in jar default info(String.format("grammar /resource/Sphinx/simple.xml")); - cm = new ConfigurationManager(this.getClass().getResource(FileIO.gluePaths(getResourceDir(), "/Sphinx/simple.xml"))); } info("starting recognizer"); // start the word recognizer - recognizer = (Recognizer) cm.lookup("recognizer"); - recognizer.allocate(); - - info("starting microphone"); - microphone = (Microphone) cm.lookup("microphone"); - if (!microphone.startRecording()) { - log.error("Cannot start microphone."); - recognizer.deallocate(); - } +// recognizer = cm.lookup("recognizer"); + Configuration configuration = new Configuration(); + + configuration.setAcousticModelPath("resource:/edu/cmu/sphinx/models/en-us/en-us"); + configuration.setDictionaryPath("resource:/edu/cmu/sphinx/models/en-us/cmudict-en-us.dict"); + configuration.setLanguageModelPath("resource:/edu/cmu/sphinx/models/en-us/en-us.lm.bin"); + recognizer = new LiveSpeechRecognizer(configuration); + recognizer.startRecognition(true); - SpeechRecognizerConfig c = (SpeechRecognizerConfig)config; + SpeechRecognizerConfig c = config; // loop the recognition until the program exits. @@ -121,7 +121,7 @@ public void run() { info("listening: %b", c.listening); invoke("listeningEvent", true); - Result result = recognizer.recognize(); + Result result = recognizer.getResult().getResult(); if (!c.listening) { // we could have stopped listening @@ -220,7 +220,7 @@ public void run() { public final static Logger log = LoggerFactory.getLogger(Sphinx.class.getCanonicalName()); transient Microphone microphone = null; transient ConfigurationManager cm = null; - transient Recognizer recognizer = null; + transient LiveSpeechRecognizer recognizer = null; transient SpeechProcessor speechProcessor = null; @@ -434,7 +434,7 @@ public boolean isRecording() { * */ public synchronized boolean onIsSpeaking(Boolean talking) { - SpeechRecognizerConfig c = (SpeechRecognizerConfig)config; + SpeechRecognizerConfig c = config; if (talking) { c.listening = false; @@ -501,7 +501,7 @@ public void lockOutAllGrammarExcept(String lockPhrase) { @Override public synchronized void pauseListening() { log.info("Pausing Listening"); - SpeechRecognizerConfig c = (SpeechRecognizerConfig)config; + SpeechRecognizerConfig c = config; c.listening = false; if (microphone != null && recognizer != null) { @@ -539,11 +539,7 @@ public void resumeListening() { SpeechRecognizerConfig c = (SpeechRecognizerConfig)config; c.listening = true; - if (microphone != null) { - // TODO: no idea if this does anything useful. - microphone.clear(); - microphone.startRecording(); - } + recognizer.startRecognition(true); } // FYI - grammar must be created BEFORE we start to listen @@ -587,8 +583,7 @@ private String cleanGrammar(String grammar) { } public void startRecordingx() { - microphone.clear(); - microphone.startRecording(); + recognizer.startRecognition(true); } /** @@ -598,13 +593,12 @@ public void startRecordingx() { */ @Override public void stopListening() { - if (microphone != null) { - microphone.stopRecording(); - microphone.clear(); - } - SpeechRecognizerConfig c = (SpeechRecognizerConfig)config; + SpeechRecognizerConfig c = config; c.listening = false; + if (recognizer != null) { + recognizer.stopRecognition(); + } if (speechProcessor != null) { speechProcessor.isRunning = false; } @@ -615,14 +609,6 @@ public void stopListening() { public void stopService() { super.stopService(); stopListening(); - if (recognizer != null) { - recognizer.deallocate(); - recognizer = null; - } - if (microphone != null) { - microphone.stopRecording(); - microphone = null; - } } @Override diff --git a/src/main/java/org/myrobotlab/service/config/ServiceConfig.java b/src/main/java/org/myrobotlab/service/config/ServiceConfig.java index 59e8f9ff9a..7696e57e2a 100755 --- a/src/main/java/org/myrobotlab/service/config/ServiceConfig.java +++ b/src/main/java/org/myrobotlab/service/config/ServiceConfig.java @@ -1,13 +1,19 @@ package org.myrobotlab.service.config; import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.TreeMap; import org.myrobotlab.codec.CodecUtils; import org.myrobotlab.framework.Peer; import org.myrobotlab.framework.Plan; +import org.myrobotlab.framework.Service; +import org.myrobotlab.framework.interfaces.ServiceInterface; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.service.Runtime; import org.slf4j.Logger; @@ -209,6 +215,12 @@ public Peer putPeerType(String peerKey, String fullName, String peerType) { } public static Plan getDefault(Plan plan, String name, String inType) { +// if ("Service".equals(inType) || Service.class.getCanonicalName().equals(inType)) { +// ServiceConfig sc = new ServiceConfig(); +// sc.type = inType; +// plan.put(name, sc); +// return plan; +// } try { // if (type == null) { @@ -227,11 +239,28 @@ public static Plan getDefault(Plan plan, String name, String inType) { // plan.merge(); config.getDefault(plan, name); - } catch (ClassNotFoundException e) { - log.info("could not find {} loading generalized ServiceConfig", inType); - ServiceConfig sc = new ServiceConfig(); - sc.type = inType; - plan.put(name, sc); + } catch (ClassNotFoundException cnfe) { + try { + Class serviceClass = Class.forName(CodecUtils.makeFullTypeName(inType)).asSubclass(Service.class); + Type superClass = serviceClass.getGenericSuperclass(); + if (superClass instanceof ParameterizedType) { + ParameterizedType genericSuperClass = (ParameterizedType) superClass; + System.out.println("Got generic superclass: " + genericSuperClass + " for service class " + serviceClass); + Class configClass = ((Class) genericSuperClass.getActualTypeArguments()[0]).asSubclass(ServiceConfig.class); + ServiceConfig newConfig = configClass.getConstructor().newInstance(); + newConfig.type = inType; + newConfig.getDefault(plan, name); + } else { + throw new NoSuchElementException("Superclass is not generic"); + } + + } catch (NoClassDefFoundError | ClassNotFoundException | NoSuchElementException | NoSuchMethodException | InstantiationException | + IllegalAccessException | InvocationTargetException | ClassCastException ignored) { + log.info("could not find config class for {}, loading generalized ServiceConfig", inType); + ServiceConfig sc = new ServiceConfig(); + sc.type = inType; + plan.put(name, sc); + } } catch (Exception e) { Runtime.getInstance().error(e); } diff --git a/src/main/java/org/myrobotlab/string/StringUtil.java b/src/main/java/org/myrobotlab/string/StringUtil.java index 06c152b8db..411e8a9322 100644 --- a/src/main/java/org/myrobotlab/string/StringUtil.java +++ b/src/main/java/org/myrobotlab/string/StringUtil.java @@ -155,10 +155,7 @@ public static String StringToMethodName(String english) { public static boolean isEmpty(String v) { // return true if the string is null or empty. - if (v == null || "".equals(v)) { - return true; - } - return false; + return v == null || v.isEmpty(); } // split a string into sub strings that are a maxlength @@ -220,11 +217,11 @@ public static String removeAccents(String text) { * list of the integer values of the bytes. This ensures that the human * readable string values for the bytes are considered unsigned. (range 0-255) * not (-128 to 127) - * + * * @param bytes * input array to convert * @return string representing the bytes as integers - * + * */ public static String byteArrayToIntString(byte[] bytes) { if (bytes.length == 0) { @@ -251,14 +248,23 @@ public static String intArrayToString(int[] ints) { return builder.toString(); } - public static String removeEnd(final String str, final String remove) { - if (str == null || str.length() == 0 || remove == null || remove.length() == 0) { - return str; - } - if (str.endsWith(remove)) { - return str.substring(0, str.length() - remove.length()); + + /** + * Removes a trailing substring from the given + * string if it exists as the last component + * of the given string. + * + * @param fullString The string to remove the end from + * @param toRemove The string to be removed if {@code fullString.endsWith(toRemove) == true} + * @return fullString with toRemove stripped from only the end. + */ + public static String removeEnd(String fullString, String toRemove) { + if (isEmpty(fullString) || isEmpty(toRemove) || !fullString.endsWith(toRemove)) { + return fullString; } - return str; + + + return fullString.substring(0, fullString.length() - toRemove.length()); } } diff --git a/src/test/java/org/myrobotlab/string/StringUtilTest.java b/src/test/java/org/myrobotlab/string/StringUtilTest.java index f75b693330..35403af57c 100644 --- a/src/test/java/org/myrobotlab/string/StringUtilTest.java +++ b/src/test/java/org/myrobotlab/string/StringUtilTest.java @@ -34,4 +34,10 @@ public void testChunkString() { assertEquals(text, StringUtils.join(result, " ")); } + + @Test + public void testRemoveEnd() { + String testStr = "python@test_runtime"; + assertEquals("python", StringUtil.removeEnd(testStr, "@test_runtime")); + } }