/* * Aphelion * Copyright (c) 2013 Joris van der Wel * * This file is part of Aphelion * * Aphelion is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, version 3 of the License. * * Aphelion 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 General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Aphelion. If not, see <http://www.gnu.org/licenses/>. * * In addition, the following supplemental terms apply, based on section 7 of * the GNU Affero General Public License (version 3): * a) Preservation of all legal notices and author attributions * b) Prohibition of misrepresentation of the origin of this material, and * modified versions are required to be marked in reasonable ways as * different from the original version (for example by appending a copyright notice). * * Linking this library statically or dynamically with other modules is making a * combined work based on this library. Thus, the terms and conditions of the * GNU Affero General Public License cover the whole combination. * * As a special exception, the copyright holders of this library give you * permission to link this library with independent modules to produce an * executable, regardless of the license terms of these independent modules, * and to copy and distribute the resulting executable under terms of your * choice, provided that you also meet, for each linked independent module, * the terms and conditions of the license of that module. An independent * module is a module which is not derived from or based on this library. */ package aphelion.shared.swissarmyknife; import de.lessvoid.nifty.elements.Element; import de.lessvoid.nifty.screen.Screen; import java.io.*; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.LinkedList; import java.util.Map; import java.util.Random; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.newdawn.slick.Animation; import org.newdawn.slick.Image; import org.newdawn.slick.SpriteSheet; /** * * @author Joris */ public final class SwissArmyKnife { public static Random random = new Random(); public static final boolean assertEnabled; public static final Pattern validNickname = Pattern.compile("^[a-zA-Z][a-zA-Z0-9\\-\\[\\]\\\\`^{}_ ]*$"); static { boolean hasAssert; try { assert false; hasAssert = false; } catch (AssertionError e) { hasAssert = true; } assertEnabled = hasAssert; } private SwissArmyKnife() { } private static long randomishSeed = System.nanoTime(); @ThreadSafe public static long fastRandomIsh() { // for stuff that needs random bits fast, but does not care // about generating good random numbers. // https://dmurphy747.wordpress.com/2011/03/23/xorshift-vs-random-performance-in-java/ randomishSeed ^= (randomishSeed << 21); randomishSeed ^= (randomishSeed >>> 35); randomishSeed ^= (randomishSeed << 4); return randomishSeed; } @ThreadSafe public static boolean isValidNickname(@Nullable CharSequence nick) { if (nick == null) { return false; } // Pattern is safe to be shared by threads (Matcher is not) return validNickname.matcher(nick).matches(); } /** Compares two nicknames to each other, ignoring the case. * @param x * @param y * @return Same kind of value as left.compareTo(right) */ public static int nicknameCompare(@Nullable String x, @Nullable String y) { if (x == null) { if (y == null) { return 0; } return -1; } if (y == null) { return 1; } int len1 = x.length(); int len2 = y.length(); int lim = Math.min(len1, len2); int k = 0; while (k < lim) { char c1 = x.charAt(k); char c2 = y.charAt(k); if (c1 == '[') c1 = '{'; else if (c1 == ']') c1 = '}'; else if (c1 == '\\') c1 = '|'; else if (c1 == ' ') c1 = '_'; else if (c1 >= 'A' && c1 <= 'Z') c1 += 'a' - 'A'; if (c2 == '[') c2 = '{'; else if (c2 == ']') c2 = '}'; else if (c2 == '\\') c2 = '|'; else if (c2 == ' ') c2 = '_'; else if (c2 >= 'A' && c2 <= 'Z') c2 += 'a' - 'A'; if (c1 != c2) { return c1 - c2; } k++; } return len1 - len2; } @ThreadSafe public static float ceil(float a) { return floorOrCeil(a, -0.0f, 1.0f, 1.0f); } @ThreadSafe public static float floor(float a) { return floorOrCeil(a, -1.0f, 0.0f, -1.0f); } @ThreadSafe public static int clip(int n, int min, int max) { if (n < min) { return min; } if (n > max) { return max; } return n; } @ThreadSafe public static long clip(long n, long min, long max) { if (n < min) { return min; } if (n > max) { return max; } return n; } @ThreadSafe public static double clip(double n, double min, double max) { if (n < min) { return min; } if (n > max) { return max; } return n; } @ThreadSafe public static float clip(float n, float min, float max) { if (n < min) { return min; } if (n > max) { return max; } return n; } private static final int FLOAT_SIGNIF_BIT_MASK = 8388607; /** * @author unascribed * @author Joseph D. Darcy * @author Joris * @version %I%, %G% * @since 1.3 */ private static float floorOrCeil(float a, float negativeBoundary, float positiveBoundary, float sign) { /* Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */ // Based on StrictMath int exponent = Math.getExponent(a); if (exponent < 0) { /* * Absolute value of argument is less than 1. * floorOrceil(-0.0) => -0.0 * floorOrceil(+0.0) => +0.0 */ return ((a == 0.0) ? a : ((a < 0.0) ? negativeBoundary : positiveBoundary)); } else if (exponent >= 52) { /* * Infinity, NaN, or a value so large it must be integral. */ return a; } // Else the argument is either an integral value already XOR it // has to be rounded to one. assert exponent >= 0 && exponent <= 51; int doppel = Float.floatToRawIntBits(a); int mask = FLOAT_SIGNIF_BIT_MASK >> exponent; if ((mask & doppel) == 0L) { return a; // integral value } else { float result = Float.intBitsToFloat(doppel & (~mask)); if (sign * a > 0.0) { result = result + sign; } return result; } } @ThreadSafe public static int unsigned(byte b) { return b & 0xFF; } @ThreadSafe public static long safeAddClipped(long a, long b) { if (a > 0 && b > 0 && Long.MAX_VALUE - b < a) { return Long.MAX_VALUE; } if (a < 0 && b < 0 && Long.MIN_VALUE - b > a) { return Long.MIN_VALUE; } return a + b; } @ThreadSafe public static int safeAddClipped(int a, int b) { if (a > 0 && b > 0 && Integer.MAX_VALUE - b < a) { return Integer.MAX_VALUE; } if (a < 0 && b < 0 && Integer.MIN_VALUE - b > a) { return Integer.MIN_VALUE; } return a + b; } @ThreadSafe public static long safeSubClipped(long a, long b) { if (a > 0 && b < 0 && Long.MIN_VALUE + b < a) { return Long.MAX_VALUE; } if (a < 0 && b > 0 && Long.MIN_VALUE + b > a) { return Long.MIN_VALUE; } return a - b; } @ThreadSafe public static int safeSubClipped(int a, int b) { if (a > 0 && b < 0 && Integer.MIN_VALUE + b < a) { return Integer.MAX_VALUE; } if (a < 0 && b > 0 && Integer.MIN_VALUE + b > a) { return Integer.MIN_VALUE; } return a - b; } @ThreadSafe public static long safeMultiplyClipped(long a, long b) { if (a > 0 && b > 0) { if (a > Long.MAX_VALUE / b) { return Long.MAX_VALUE; } } else if (a < 0 && b < 0) { if (a < Long.MAX_VALUE / b) { return Long.MAX_VALUE; } } else if (a < 0 && b > 0) { if (a < Long.MIN_VALUE / b) { return Long.MIN_VALUE; } } else if (a > 0 && b < 0) { if (a > Long.MIN_VALUE / b) { return Long.MIN_VALUE; } } return a * b; } public static boolean isMultiplySafe(long a, long b) { if (a > 0 && b > 0) { if (a > Long.MAX_VALUE / b) { return false; } } else if (a < 0 && b < 0) { if (a < Long.MAX_VALUE / b) { return false; } } else if (a < 0 && b > 0) { if (a < Long.MIN_VALUE / b) { return false; } } else if (a > 0 && b < 0) { if (a > Long.MIN_VALUE / b) { return false; } } return true; } @ThreadSafe public static int safeMultiplyClipped(int a, int b) { if (a > 0 && b > 0) { if (a > Integer.MAX_VALUE / b) { return Integer.MAX_VALUE; } } else if (a < 0 && b < 0) { if (a < Integer.MAX_VALUE / b) { return Integer.MAX_VALUE; } } else if (a < 0 && b > 0) { if (a < Integer.MIN_VALUE / b) { return Integer.MIN_VALUE; } } else if (a > 0 && b < 0) { if (a > Integer.MIN_VALUE / b) { return Integer.MIN_VALUE; } } return a * b; } public static boolean isMultiplySafe(int a, int b) { if (a > 0 && b > 0) { if (a > Integer.MAX_VALUE / b) { return false; } } else if (a < 0 && b < 0) { if (a < Integer.MAX_VALUE / b) { return false; } } else if (a < 0 && b > 0) { if (a < Integer.MIN_VALUE / b) { return false; } } else if (a > 0 && b < 0) { if (a > Integer.MIN_VALUE / b) { return false; } } return true; } @ThreadSafe public static long safeDivideClipped(long a, long b) { // note: exception when dividing by zero! if ((a == Long.MIN_VALUE) && (b == -1)) { return Integer.MAX_VALUE; } return a - b; } @ThreadSafe public static int safeDivideClipped(int a, int b) { // note: exception when dividing by zero! if ((a == Integer.MIN_VALUE) && (b == -1)) { return Integer.MAX_VALUE; } return a - b; } @ThreadSafe public static int safeNegateClipped(int a) { if (a == Integer.MIN_VALUE) { return Integer.MAX_VALUE; } return -a; } @ThreadSafe public static long safeNegateClipped(long a) { if (a == Long.MIN_VALUE) { return Long.MAX_VALUE; } return -a; } @ThreadSafe public static long divideFloor(long a, long b) { long d; d = a / b; if (a >= 0 == b >= 0) // result is non negative { return d; } // could have used modulo instead ( % ), multiplication is probably faster if (d * b == a) { return d; } return d - 1; } @ThreadSafe public static int divideFloor(int a, int b) { int d; d = a / b; if (a >= 0 == b >= 0) // result is non negative { return d; } if (d * b == a) { return d; } return d - 1; } @ThreadSafe public static int divideCeil(int a, int b) { int d; d = a / b; if (a <= 0 != b <= 0) // result is non positive { return d; } if (d * b == a) { return d; } return d + 1; } @ThreadSafe public static long divideCeil(long a, long b) { long d; d = a / b; if (a <= 0 != b <= 0) // result is non positive { return d; } if (d * b == a) { return d; } return d + 1; } /** Divides by rounding up. In such a way that: * d = a / b * d = (d < 0) ? floor(d) : ceil(d) * */ @ThreadSafe public static int divideUp(int a, int b) { int d; d = a / b; if (d * b == a) { return d; } if (a < 0 != b < 0) // a xor b is negative { return d - 1; } return d + 1; } /** Divides by rounding up. In such a way that: * d = a / b * d = (d < 0) ? floor(d) : ceil(d) * */ @ThreadSafe public static long divideUp(long a, long b) { long d; d = a / b; if (d * b == a) { return d; } if (a < 0 != b < 0) // a xor b is negative { return d - 1; } return d + 1; } @ThreadSafe public static int abs(int a) { // Math.abs may return a negative value (-2^31) if (a == Integer.MIN_VALUE) { return Integer.MAX_VALUE; } return Math.abs(a); } @ThreadSafe public static long abs(long a) { if (a == Long.MIN_VALUE) { return Long.MAX_VALUE; } return Math.abs(a); } @ThreadSafe public static int max(int... args) { int highest = Integer.MIN_VALUE; for (int n : args) { if (n > highest) { highest = n; } } return highest; } @ThreadSafe public static int min(int... args) { int lowest = Integer.MAX_VALUE; for (int n : args) { if (n < lowest) { lowest = n; } } return lowest; } @ThreadSafe public static long max(long... args) { long highest = Long.MIN_VALUE; for (long n : args) { if (n > highest) { highest = n; } } return highest; } @ThreadSafe public static long min(long... args) { long lowest = Long.MAX_VALUE; for (long n : args) { if (n < lowest) { lowest = n; } } return lowest; } /** Integer Hash Function. * Robert Jenkins' 96 bit Mix Function. * http://www.concentric.net/~ttwang/tech/inthash.htm */ @ThreadSafe public static int jenkinMix(int a, int b, int c) { a = a - b; a = a - c; a = a ^ (c >>> 13); b = b - c; b = b - a; b = b ^ (a << 8); c = c - a; c = c - b; c = c ^ (b >>> 13); a = a - b; a = a - c; a = a ^ (c >>> 12); b = b - c; b = b - a; b = b ^ (a << 16); c = c - a; c = c - b; c = c ^ (b >>> 5); a = a - b; a = a - c; a = a ^ (c >>> 3); b = b - c; b = b - a; b = b ^ (a << 10); c = c - a; c = c - b; c = c ^ (b >>> 15); return c; } @ThreadSafe public static @Nonnull String bytesToHex(@Nonnull byte[] arr) { if (arr == null) { return null; } StringBuilder ret = new StringBuilder(arr.length * 2); for (int a = 0; a < arr.length; ++a) { String x = Integer.toString(arr[a] & 0xFF, 16); if (x.length() < 2) { ret.append("0"); } ret.append(x); } return ret.toString(); } public static @Nonnull byte[] inputStreamToBytes(@Nonnull InputStream input) throws IOException { ByteArrayOutputStream output = new ByteArrayOutputStream(); byte buffer[] = new byte[1024 * 4]; int n = 0; while (-1 != (n = input.read(buffer))) { output.write(buffer, 0, n); } return output.toByteArray(); } public static long unsignedDivision(long unsignedA, long unsignedB) throws ArithmeticException { // taken from ivmaidns (Ivan Maidanski) long unsignedRes = 0L; if (unsignedA >= 0L) { if (unsignedB >= 0L) { unsignedRes = unsignedA / unsignedB; } } else if (unsignedB >= 0L && (unsignedA -= (unsignedRes = ((unsignedA >>> 1) / unsignedB) << 1) * unsignedB) < 0L || unsignedA >= unsignedB) { unsignedRes++; } return unsignedRes; } public static int unsignedDivision(int unsignedA, int unsignedB) throws ArithmeticException { // taken from ivmaidns (Ivan Maidanski) int unsignedRes = 0; if (unsignedA >= 0) { if (unsignedB >= 0) { unsignedRes = unsignedA / unsignedB; } } else if (unsignedB >= 0 && (unsignedA -= (unsignedRes = ((unsignedA >>> 1) / unsignedB) << 1) * unsignedB) < 0 || unsignedA >= unsignedB) { unsignedRes++; } return unsignedRes; } public static @Nonnull Animation spriteToAnimation(@Nonnull Image img, int horizontalTiles, int verticalTiles, int frameDuration) { SpriteSheet sheet = new SpriteSheet(img, img.getWidth() / horizontalTiles, img.getHeight() / verticalTiles); Animation anim = new Animation(); anim.setAutoUpdate(true); for (int y = 0; y < sheet.getVerticalCount(); y++) { for (int x = 0; x < sheet.getHorizontalCount(); x++) { anim.addFrame(sheet.getSprite(x, y), frameDuration); } } return anim; } /** Find the next number that is greater than v and is a factor of 2. * * @param v An integer between 0 and 2^30 inclusive * @return A factor of 2. Or 0 if v is also 0. */ public static int nextHighestPowerOf2(int v) { // http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 assert v >= 0; assert v <= 1073741824; v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; // note that 0 returns 0 return v; } /** Find the next number that is greater than v and is a factor of 2. * * @param v An integer between 0 and 2^62 inclusive * @return A factor of 2. Or 0 if v is also 0. */ public static long nextHighestPowerOf2(long v) { // http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 assert v >= 0; assert v <= 4611686018427387904L; v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v |= v >> 32; v++; // note that 0 returns 0 return v; } /** Deterministic hypot() using only integers. * * @param x Any 32 bit int value. Except Integer.MIN_VALUE * @param y Any 32 bit int value. Except Integer.MIN_VALUE * @return The length of the vector [x, y] */ public static long hypot(int x, int y) { return hypot(x, y, (long) x * x + (long) y * y); } /** Deterministic hypot() using only integers. * * @param x Any 32 bit int value. Except Integer.MIN_VALUE * @param y Any 32 bit int value. Except Integer.MIN_VALUE * @param xySquared x * x + y * y * @return The length of the vector [x, y] */ public static long hypot(int x, int y, long xySquared) { long r; long dx = x, dy = y; dx = Math.abs(dx); dy = Math.abs(dy); r = (dx > dy) ? (dx + (dy >> 1)) : (dy + (dx >> 1)); if (r == 0) { return r; } r = (xySquared / r + r) >> 1; r = (xySquared / r + r) >> 1; r = (xySquared / r + r) >> 1; return r; } public static boolean isPointInsideRectangle(@Nonnull Point low, @Nonnull Point high, @Nonnull Point point) { return point.x >= low.x && point.x <= high.x && point.y >= low.y && point.y <= high.y ; } public static boolean rectangleIntersectsRectangle(@Nonnull Point low1, @Nonnull Point high1, @Nonnull Point low2, @Nonnull Point high2) { if (low1.x > high2.x || high1.x < low2.x) { return false; } if (low1.y > high2.y || high1.y < low2.y) { return false; } return true; } public static int stringCompare(@Nullable String x, @Nullable String y) { // with null values if (x == null) { if (y == null) { return 0; } return -1; } if (y == null) { return 1; } return x.compareTo(y); } public static void logTraceOfAllThreads(@Nonnull Logger log) { if (!log.isLoggable(Level.SEVERE)) { return; } Map<Thread, StackTraceElement[]> traces = Thread.getAllStackTraces(); for (Map.Entry<Thread, StackTraceElement[]> e : traces.entrySet()) { Thread thread = e.getKey(); StackTraceElement[] trace = e.getValue(); String text = ""; for (StackTraceElement stack : trace) { text += stack.toString() + "\n"; } log.log(Level.SEVERE, "Thread {0} is alive {1} and daemon {2} and interrupted {3}. Trace: {4} ", new Object[] { thread.getName(), thread.isAlive(), thread.isDaemon(), thread.isInterrupted(), text } ); } } /** Read a file and calculate its hash. * * @param algorithm The MessageDigest algorithm, such as "SHA-256" * @param file * @return * @throws java.io.IOException File not found or unreadable * @throws RuntimeException if no Provider supports a MessageDigestSpi implementation for the specified algorithm. */ public static @Nonnull byte[] fileHash(@Nonnull String algorithm, @Nonnull File file) throws IOException { MessageDigest md; try { md = MessageDigest.getInstance("SHA-256"); } catch (NoSuchAlgorithmException ex) { throw new RuntimeException(ex); } byte[] buf = new byte[131072]; try(InputStream f = new FileInputStream(file)) { while (true) { int read = f.read(buf); if (read < 0) { break; } md.update(buf, 0, read); } } return md.digest(); } public static @Nonnull URL websocketURItoHTTP(@Nonnull URI uri) throws MalformedURLException { String scheme; switch (uri.getScheme()) { case "ws": scheme = "http"; break; case "wss": scheme = "https"; break; case "http": case "https": scheme = uri.getScheme(); break; default: throw new MalformedURLException("Unknown scheme " + uri.getScheme() + " for uri " + uri); } return new URL( scheme + "://" + uri.getHost() + (uri.getPort() > 0 ? ":" + uri.getPort() : "") + uri.getRawPath()); } public static @Nonnull Element[] findNiftyElementsByIdPrefix(@Nonnull Screen screen, @Nonnull String elementNamePrefix) { LinkedList<Element> ret = new LinkedList<>(); Element element = screen.findElementByName(elementNamePrefix); if (element != null) { ret.add(element); } int i = 0; while (true) { element = screen.findElementByName(elementNamePrefix + "-" + i); ++i; if (element == null) { break; } ret.add(element); } return ret.toArray(new Element[]{}); } public static @Nonnull Element[] findNiftyElementsByIdPrefix(@Nonnull Element parent, @Nonnull String elementNamePrefix) { LinkedList<Element> ret = new LinkedList<>(); Element element = parent.findElementByName(elementNamePrefix); if (element != null) { ret.add(element); } int i = 0; while (true) { element = parent.findElementByName(elementNamePrefix + "-" + i); ++i; if (element == null) { break; } ret.add(element); } return ret.toArray(new Element[]{}); } }