package robotbuilder; import java.awt.Desktop; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Files; import java.util.Properties; import java.util.concurrent.Callable; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; import org.apache.commons.lang3.SerializationException; import org.apache.commons.lang3.SerializationUtils; import org.apache.velocity.runtime.RuntimeConstants; import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader; import org.yaml.snakeyaml.Yaml; import robotbuilder.data.RobotComponent; import robotbuilder.data.properties.ParametersProperty; /** * * @author alex * @author Sam Carlberg */ public class Utils { /** * A helper to hide the difference between being in and being out of a jar. * * @param resource * @return The resource URL */ public static URL getResource(String resource) throws FileNotFoundException { URL url = ClasspathResourceLoader.class.getResource(resource); if (url == null) { throw new FileNotFoundException("Cannot load resource: " + resource); } return url; } /** * A helper to hide the difference between being in and being out of a jar. * * @param resource * @return The resource stream */ public static InputStream getResourceAsStream(String resource) { return ClasspathResourceLoader.class.getResourceAsStream(resource); } /** * Handle velocity template loader from either resource or file. * * @return */ public static Properties getVelocityProperties() { Properties p = new Properties(); p.setProperty("resource.loaders", "class"); p.setProperty("resource.loader.class.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); return p; } public static void browse(final String url) { try { Desktop.getDesktop().browse(new URI(url)); } catch (URISyntaxException | IOException ex) { Logger.getLogger(Utils.class.getName()).log(Level.SEVERE, null, ex); } catch (UnsupportedOperationException e) { new Thread(() -> { Process p; try { System.out.println("firefox " + url); p = Runtime.getRuntime().exec("firefox " + url); } catch (IOException e1) { e1.printStackTrace(); } }).start(); } } static void browse(URL url) { browse(url.toString()); } /** * Checks if the given expression throws an error. This will not throw an * exception, nor will it log a thrown exception. * * @param expression the expression to check * @return true if the expression does not throw an error, false if it does */ public static boolean doesNotError(Runnable expression) { try { expression.run(); return true; } catch (Exception e) { return false; } } public static boolean doesNotError(Callable<?> expression) { try { expression.call(); return true; } catch (Exception e) { return false; } } /** * Performs a deep copy of the given object. Note that the object must have * a default (zero-argument) constructor. * * @param <T> the type of the object to copy and return * @param original the object to make a copy of * @return a copy of the given object */ public static <T> T deepCopy(T original) { if (original == null) { return null; } Yaml y = new Yaml(); String yaml = y.dump(original); return (T) y.load(yaml); } /** * Performs a deep copy of the given object. This method is preferable to * the more general version because that relies on the object having a * default (zero-argument) constructor; however, this method only works for * serializable objects. * * @param <T> the type of the object to copy and return * @param original the object to copy * @return a deep copy of the given object */ public static <T extends Serializable> T deepCopy(T original) { if (original == null) { return null; } try { return SerializationUtils.clone(original); } catch (SerializationException notSerializable) { return (T) deepCopy((Object) original); } } /** * Gets the text in the given file. An empty String is returned if there is * an error getting the text. */ public static String getFileText(File file) { String text = ""; try { text = Files.lines(file.toPath()).collect(Collectors.joining("\n")); } catch (IOException ex) { // Couldn't read from file, return empty String } return text; } /** * Gets the text in the file at the given path. An empty String is returned * if there is an error getting the text. * * @param filePath the absolute path of the file */ public static String getFileText(String filePath) { return getFileText(new File(filePath)); } /** * Gets the extension for the given file, or an empty String if it has no * extension. For example, calling this on /a/b/c.d will output "d", and * calling this on /x/y/z will output "". */ public static String getFileExtension(File file) { String fileName = file.getName(); int dot = fileName.lastIndexOf('.'); if (dot == -1) { return ""; } return fileName.substring(dot + 1); } /** * Gets the parameter property associated with command used by the given * component. If it doesn't use a command, returns {@code null}. * * <p> * * This is useful for matching the command parameters of a component that * has some command (such as the command for a joystick button, or the * default command for a subsystem) to that command to make sure that it * always reflects the command. * * @param component the component to get command parameters for * @return */ public static ParametersProperty getParameters(RobotComponent component) { // One of: "Command", "Default Command", "Autonomous Command", <null> String commandType = component.getPropertyKeys().stream().filter(k -> k.endsWith("Command")).findFirst().orElse(null); if (component.getProperty(commandType) != null) { RobotComponent commandRoot = null; // will never actually be null for (RobotComponent c : component.getRobotTree().getRoot().getChildren()) { if (c.getName().equals("Commands")) { commandRoot = c; } } for (RobotComponent command : commandRoot.getChildren()) { if (command.getName().equals(component.getProperty(commandType).getValue())) { return (ParametersProperty) command.getProperty("Parameters"); } } } return new ParametersProperty(); } /** * Gets the parameters property of the given component. */ public static ParametersProperty getParametersProperty(RobotComponent component) { return component.getPropertyKeys().stream() .filter(k -> k.toLowerCase().endsWith("parameters")) .map(component::getProperty) .map(ParametersProperty.class::cast) .findFirst() .orElse(null); } /** * A python-style substring function that interprets a negative value for * {@code end} as the distance from the end of the string (e.g. * {@code substring("Hello, world!", 0, -1)} results in * {@code "Hello, world"}; substring("Hello, world!", 0, -8) results in * "Hello") * * @param str the string to get a substring of * @param start the start index of the substring; this must be >= 0 * @param end the end index of the substring; this must be >= start or >= * start - str.length() * @return */ public static String substring(String str, int start, int end) { if (start > 0 && end >= start) { return str.substring(start, end); } else if (end < 0 && str.length() + end >= start) { return str.substring(start, str.length() + end); } else { throw new IndexOutOfBoundsException("Base string: " + str + ", start: " + start + ", end: " + end); } } }