/* * Copyright 2013-2015 Cel Skeggs, 2016 Alexander Mackworth * * This file is part of the CCRE, the Common Chicken Runtime Engine. * * The CCRE is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * The CCRE is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with the CCRE. If not, see <http://www.gnu.org/licenses/>. */ package ccre.util; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.io.UnsupportedEncodingException; import java.util.List; import ccre.verifier.FlowPhase; import ccre.verifier.IgnoredPhase; /** * A class for utilities that don't fit anywhere else. * * @author skeggsc */ public class Utils { /** * Calculate a value with a deadzone. If the value is within the specified * deadzone, the result will be zero instead. * * The result is undefined if deadzone is negative, NaN, or infinite. * * @param value the value * @param deadzone the deadzone size * @return the deadzoned version of the value */ @FlowPhase public static float deadzone(float value, float deadzone) { return Math.abs(value) >= deadzone ? value : Float.isNaN(value) ? Float.NaN : 0.0f; } /** * Run a cycle of ramping code on the previous ramping value, target value, * and acceleration limit. * * This will return the target value unless it's outside of the limit away * from the previous ramping value, in which case it will be as close as * possible. * * If the ramping constant is zero, no ramping will be applied - the input * will be copied to the result. * * @param previous The previous ramping value. * @param target The target value. * @param limit The acceleration limit. * @return The new value from the ramping cycle */ @FlowPhase public static float updateRamping(float previous, float target, float limit) { float reallimit; if (limit <= 0) { if (limit == 0) { return target; } reallimit = -limit; } else { reallimit = limit; } if (target > previous + reallimit) { return previous + reallimit; } else if (target < previous - reallimit) { return previous - reallimit; } else { return target; } } /** * Extracts the big-endian integer starting at offset from array. This is * equivalent to: * <code>((array[offset] & 0xff) << 24) | ((array[offset+1] & 0xff) << 16) | ((array[offset+2] & 0xff) << 8) | (array[offset+3] & 0xff)</code> * * @param array The array to extract data from. * @param offset The offset in the array of the most significant byte. * @return The integer extracted from the array. */ @IgnoredPhase public static int bytesToInt(byte[] array, int offset) { int highWord = ((array[offset] & 0xff) << 24) | ((array[offset + 1] & 0xff) << 16); int lowWord = ((array[offset + 2] & 0xff) << 8) | (array[offset + 3] & 0xff); return highWord | lowWord; } /** * Extracts the floating-point number starting at offset from array. This is * equivalent to: * <code>Float.intBitsToFloat(Utils.bytesToInt(array, offset))</code> * * @param array The array to extract data from. * @param offset The offset in the array of the most significant byte of the * intermediate integer. * @return The float extracted from the array. */ @IgnoredPhase public static float bytesToFloat(byte[] array, int offset) { return Float.intBitsToFloat(Utils.bytesToInt(array, offset)); } private Utils() { } /** * Convert the specified Throwable to a String that contains what would have * been printed by printThrowable. * * Printing this value is equivalent to just calling printThrowable * originally. * * @param thr the throwable to print. * @return the String version of the throwable, including the traceback, or * null if the throwable was null. */ @FlowPhase public static String toStringThrowable(Throwable thr) { if (thr == null) { return null; } ByteArrayOutputStream out = new ByteArrayOutputStream(); thr.printStackTrace(new PrintStream(out)); return out.toString(); } /** * Get diagnostic information for someone in the call stack of this method, * at a given index. * * Index 0 is the caller of this method; 1 is the caller of that method, * etc. * * This should contain, at the very least, the class, but should also * contain the method, source file, and line number if possible. * * @param index which frame to report. * @return a CallerInfo for the specified caller, or null. */ @IgnoredPhase public static CallerInfo getMethodCaller(int index) { int traceIndex = index + 1; StackTraceElement[] trace = new Throwable().getStackTrace(); if (traceIndex <= 0 || traceIndex >= trace.length || trace[traceIndex] == null) { return null; } else { StackTraceElement elem = trace[traceIndex]; return new CallerInfo(elem.getClassName(), elem.getMethodName(), elem.getFileName(), elem.getLineNumber()); } } /** * Convert <code>string</code> to UTF-8 bytes. * * This method is useful so that one doesn't have to handle throwing of * {@link UnsupportedEncodingException}, which should never practically * happen. * * @param string the string to convert. * @return the UTF-8 bytes for <code>string</code>. */ @IgnoredPhase public static byte[] getBytes(String string) { try { return string.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { // http://stackoverflow.com/questions/6030059/url-decoding-unsupportedencodingexception-in-java throw new AssertionError("UTF-8 is unknown", e); } } /** * Convert UTF-8 bytes to a String. * * This method is useful so that one doesn't have to handle throwing of * {@link UnsupportedEncodingException}, which should never practically * happen. * * @param data the UTF-8 bytes to convert. * @param offset the location in <code>bytes</code> to start at. * @param count the number of bytes to process. * @return the UTF-8 bytes for <code>string</code>. */ @IgnoredPhase public static String fromBytes(byte[] data, int offset, int count) { try { return new String(data, offset, count, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new AssertionError("UTF-8 is unknown", e); } } /** * Join the strings in <code>strings</code> together in sequence with * <code>separator</code> between them. * * For example, <code>["abc", "def", "hij"]</code> joined with * <code>"EJ"</code> as the separator would yield * <code>"abcEJdefEJhij"</code>. * * @param strings the strings to join together * @param separator the separator to include * @return the joined strings */ public static String joinStrings(List<String> strings, String separator) { if (strings == null || separator == null) { throw new NullPointerException(); } if (strings.isEmpty()) { return ""; } StringBuilder builder = new StringBuilder(strings.get(0)); for (String element : strings.subList(1, strings.size())) { if (element == null) { throw new NullPointerException(); } builder.append(separator); builder.append(element); } return builder.toString(); } /** * Convert an InputStream that may include carriage returns into an * InputStream that does not. This is often useful for converting from * windows-style line endings to unix-style line endings. * * @param in the InputStream to process * @return the processed InputStream. */ public static InputStream stripCarriageReturns(InputStream in) { return new InputStream() { @Override public int read() throws IOException { int b = in.read(); while (b == '\r') { b = in.read(); } return b; } @Override public int read(byte[] b, int off, int len) throws IOException { byte[] temp = new byte[len]; int length = in.read(temp); if (length == -1) { return -1; } int out = off; for (int i = 0; i < length; i++) { if (temp[i] != '\r') { b[out++] = temp[i]; } } return out; } @Override public void close() throws IOException { in.close(); } @Override public int read(byte[] b) throws IOException { return this.read(b, 0, b.length); } }; } }