org.noroomattheinn.utils.Utils.java Source code

Java tutorial

Introduction

Here is the source code for org.noroomattheinn.utils.Utils.java

Source

/*
 * Utils.java - Copyright(c) 2013 Joe Pasqua
 * Provided under the MIT License. See the LICENSE file for details.
 * Created: Jul 8, 2013
 */

package org.noroomattheinn.utils;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.math.BigDecimal;
import java.nio.channels.FileLock;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import org.apache.commons.lang3.SystemUtils;
import org.noroomattheinn.tesla.Tesla;

/**
 * Utils: A collection of static utility methods across a wide variety of areas.
 *
 * @author Joe Pasqua <joe at NoRoomAtTheInn dot org>
 */

public class Utils {

    /*------------------------------------------------------------------------------
     *
     * Useful Types
     * 
     *----------------------------------------------------------------------------*/

    public enum UnitType {
        Metric, Imperial
    };

    public interface Callback<P, R> {
        public R call(P parameter);
    }

    public interface Predicate {
        boolean eval();
    }

    /*------------------------------------------------------------------------------
     *
     * Useful Constants
     * 
     *----------------------------------------------------------------------------*/

    public static final Logger logger = Logger.getLogger(Utils.class.getName());
    public static final Predicate alwaysFalse = new Predicate() {
        @Override
        public boolean eval() {
            return false;
        }
    };
    public static final double KilometersPerMile = 1.60934;

    /*------------------------------------------------------------------------------
     *
     * Utility Methods for Collections
     * 
     *----------------------------------------------------------------------------*/

    /**
     * Construct a new HashMap with a list of key/value pairs as arguments.
     * The arguments are interspersed: k,v,k,v,k,v,...
     * There must be an even number of arguments and each key must be of
     * type K and each value must be of type V.
     * 
     * @param <K>   The type of the keys
     * @param <V>   The type of the values
     * @param kv    An array of (key,value) pairs
     * @return      An initialized HashMap
     */
    public static <K, V> HashMap<K, V> newHashMap(Object... kv) {
        int length = kv.length;
        if (length % 2 != 0)
            throw new IllegalArgumentException("Mismatched keys and values");
        HashMap<K, V> m = new HashMap<>();
        fillMap(m, kv);
        return m;
    }

    /**
     * Construct a new HashMap using the elements of an Enumeration as the keys
     * and an array of objects of type V as the values. The array must be the same
     * length as the number of elements in the enumeration.
     * @param <E>   An Enumeration Type
     * @param <V>   An arbitrary value type
     * @param keys  An array of enums such as the one that would be returned
     *              by SomeEnum.values();
     * @param values    An array of values to go with the enum keys
     * @return      An initialized HashMap
     */
    public static <E extends Enum<E>, V> HashMap<E, V> newHashMap(E[] keys, V[] values) {
        int nKeys = keys.length;
        if (nKeys != values.length)
            throw new IllegalArgumentException("Mismatched keys and values");
        HashMap<E, V> m = new HashMap<>();
        for (int i = 0; i < nKeys; i++) {
            m.put(keys[i], values[i]);
        }
        return m;
    }

    /**
     * Fill an existing map with a list of key/value pairs.
     * The arguments are interspersed: k,v,k,v,k,v,...
     * There must be an even number of arguments and each key must be of
     * type K and each value must be of type V.
     * @param <K>   The type of the keys
     * @param <V>   The type of the values
     * @param m     The map to fill - must not be null
     * @param kv    An array of (key,value) pairs
     */
    public static <K, V> void fillMap(Map<K, V> m, Object... kv) {
        int length = kv.length;
        if (length % 2 != 0)
            throw new IllegalArgumentException("Mismatched keys and values");
        for (int i = 0; i < length;) {
            K key = cast(kv[i++]);
            V value = cast(kv[i++]);
            m.put(key, value);
        }
    }

    /*------------------------------------------------------------------------------
     *
     * Units and Unit Conversions
     * 
     *----------------------------------------------------------------------------*/

    public static double cToF(double temp) {
        return temp * 9.0 / 5.0 + 32;
    }

    public static double fToC(double temp) {
        return (temp - 32) * 5.0 / 9.0;
    }

    public static double kmToMiles(double k) {
        return k / KilometersPerMile;
    }

    public static double milesToKm(double m) {
        return m * KilometersPerMile;
    }

    public static double metersToFeet(double meters) {
        return meters * 3.28084;
    }

    public static String yesNo(boolean b) {
        return b ? "Yes" : "No";
    }

    /*------------------------------------------------------------------------------
     *
     * Language "Helpers"
     * 
     *----------------------------------------------------------------------------*/

    /*
     * Returns a member of an Enumeration corresponding to a String value. This
     * is based on <code>Enum.valueof()</code>, but is wrapped with a try block
     * to deal with the case where the String value does not correspond to a
     * value of the Enum. In this case the Enum value "Unknown" is used. So,
     * to use this method safely, the Enum type must have Unknown as an option.
     * <P>
     * This code is a little weird because it is both parameterized by the Enum
     * Type and the class is alo passed in. This is required to handle some
     * oddities of dealing with Generic Enums.
     * 
     * @param eClass    The class of the Enum for which we want an instance
     * @param val       The String that should correspond to an Enum value.
     * @return          An instance of the specified Enum type corresponding
     *                  to the specified prefix
     */
    public static <T extends Enum<T>> T stringToEnum(Class<T> eClass, String val) {
        try {
            if (val == null || val.isEmpty() || val.equals("null")) {
                val = "Unknown";
            }
            return Enum.valueOf(eClass, val);
        } catch (Exception e) {
            logger.info("Problem converting String (" + val + ") to Enum: " + e);
            return Enum.valueOf(eClass, "Unknown");
        }
    }

    /**
     * Perform an unchecked type coercion of an object to a target type. The
     * only real value of this method is that it is declared in one place with
     * the SuppressWarnings directive turned on.
     * @param <E>   The target type
     * @param obj   The object to coerce
     * @return      The supplied object in the specified type
     */
    @SuppressWarnings("unchecked")
    public static <E> E cast(Object obj) {
        return (E) obj;
    }

    /*------------------------------------------------------------------------------
     *
     * Thread-Related Methods
     * 
     *----------------------------------------------------------------------------*/

    public static void sleep(long timeInMillis, Predicate p) {
        long initialTime = System.currentTimeMillis();
        while (System.currentTimeMillis() - initialTime < timeInMillis) {
            if (p.eval()) {
                Tesla.logger.finest("Predicate satisfied - waking early");
                return;
            }
            try {
                Thread.sleep(500);
            } catch (InterruptedException ex) {
                return;
            }
        }
    }

    public static void sleep(long timeInMillis) {
        sleep(timeInMillis, alwaysFalse);
    }

    public static void yieldFor(long timeInMillis) {
        try {
            Thread.sleep(timeInMillis);
        } catch (InterruptedException ex) {
        }
    }

    /*------------------------------------------------------------------------------
     *
     * String Handling Methods
     * 
     *----------------------------------------------------------------------------*/

    public static String toB64(byte[] bytes) {
        return javax.xml.bind.DatatypeConverter.printBase64Binary(bytes);
    }

    public static byte[] fromB64(String s) {
        return javax.xml.bind.DatatypeConverter.parseBase64Binary(s);
    }

    public static String decodeB64(String s) {
        if (s == null)
            return null;
        try {
            return new String(fromB64(s), "UTF-8");
        } catch (UnsupportedEncodingException ex) {
            Logger.getLogger(Utils.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }
    }

    /*------------------------------------------------------------------------------
     *
     * Math Related Methods
     * 
     *----------------------------------------------------------------------------*/

    public static BigDecimal newBD(double val, int scale) {
        return (new BigDecimal(val)).setScale(scale, BigDecimal.ROUND_HALF_UP);
    }

    public static double percentChange(double oldValue, double newValue) {
        if (oldValue == 0)
            return 1.0;
        return Math.abs((oldValue - newValue) / oldValue);
    }

    public static <T extends Comparable<T>> T clamp(T val, T min, T max) {
        if (val.compareTo(min) < 0) {
            return min;
        } else if (val.compareTo(max) > 0) {
            return max;
        } else {
            return val;
        }
    }

    /**
     * Rounds the given value to the specified number of decimal places.
     * The value is rounded using the given method which is any method defined
     * in BigDecimal. If x is infinite or NaN, then the value of x is returned
     * unchanged, regardless of the other parameters.
     * NOTE: Code is from org.apache.commons.math3.util.Precision
     * @param x         Value to round
     * @param scale     Number of digits to the right of the decimal point
     * @return 
     */
    public static double round(double x, int scale) {
        return round(x, scale, BigDecimal.ROUND_HALF_UP);
    }

    /**
     * Rounds the given value to the specified number of decimal places.
     * The value is rounded using the given method which is any method defined
     * in BigDecimal. If x is infinite or NaN, then the value of x is returned
     * unchanged, regardless of the other parameters.
     * NOTE: Code is from org.apache.commons.math3.util.Precision
     * @param x         Value to round
     * @param scale     Number of digits to the right of the decimal point
     * @roundingMethod  Rounding method as defined in BigDecimal.
     * @return 
     */
    public static double round(double x, int scale, int roundingMethod) {
        try {
            return (new BigDecimal(Double.toString(x)).setScale(scale, roundingMethod)).doubleValue();
        } catch (NumberFormatException ex) {
            if (Double.isInfinite(x)) {
                return x;
            } else {
                return Double.NaN;
            }
        }
    }

    /*------------------------------------------------------------------------------
     *
     * Platform-specific Utility Methods
     * 
     *----------------------------------------------------------------------------*/

    public static File ensureAppFilesFolder(String appName) {
        File aff = getAppFileFolder(appName);
        if (aff.exists()) {
            return aff;
        }
        if (aff.mkdir()) {
            return aff;
        }
        logger.warning("Could not create Application Files Folder: " + aff);
        return null;
    }

    public static File getAppFileFolder(String appName) {
        String path = null;
        if (SystemUtils.IS_OS_MAC) {
            path = System.getProperty("user.home") + "/Library/Application Support/" + appName;
        } else if (SystemUtils.IS_OS_WINDOWS) {
            File base = javax.swing.filechooser.FileSystemView.getFileSystemView().getDefaultDirectory();
            path = base.getAbsolutePath() + File.separator + appName;
        } else if (SystemUtils.IS_OS_LINUX) {
            path = System.getProperty("user.home") + File.separator + "." + appName;
        }
        return (path == null) ? null : new File(path);
    }

    public static void openFileViewer(String where) {
        String command = "";

        if (SystemUtils.IS_OS_MAC) {
            command = "open";
        } else if (SystemUtils.IS_OS_WINDOWS) {
            command = "Explorer.exe";
        } else if (SystemUtils.IS_OS_LINUX) {
            //command = "vi";
            command = "xdg-open";
        }

        try {
            Process p = (new ProcessBuilder(command, where)).start();
            p.waitFor();
            if (p.exitValue() != 0) {
                logger.warning("Unable to open file viewer with command: " + command);
            }
        } catch (IOException | InterruptedException ex) {
            logger.warning("Unable able to open file viewer: " + ex);
        }
    }

    /*------------------------------------------------------------------------------
     *
     * Other Utility Methods
     * 
     *----------------------------------------------------------------------------*/

    /**
     * Return the number of milliseconds that have elapsed between the specified
     * time and now (as determined by System.currentTimeMillis).
     * @param t A time before now
     * @return  The difference between System.currentTimeMillis() and t
     */
    public static long timeSince(long t) {
        return System.currentTimeMillis() - t;
    }

    public static int compareVersions(String versionA, String versionB) {
        String[] partsOfA = versionA.split("\\.");
        String[] partsOfB = versionB.split("\\.");
        int shortest = Math.min(partsOfA.length, partsOfB.length);

        int i = 0;
        while (i < shortest && partsOfA[i].equals(partsOfB[i])) {
            i++;
        }

        if (i < shortest)
            return Integer.valueOf(partsOfA[i]) - Integer.valueOf(partsOfB[i]);

        return partsOfA.length - partsOfB.length;
    }

    public static List<String> getJVMArgs() {
        RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
        return runtimeMxBean.getInputArguments();
    }

    /**
     * Lock a global resource between instances of Java running in different
     * processes. This can be used to ensure that only one instance of an
     * application is running. The lock is obtained by locking a file in
     * the file system that is agreed upon by all parties involved in the 
     * locking process.
     * @param lockName  The file to lock
     * @param folder    The folder containing the file to lock
     * @return          true if this process obtained the lock
     *                  false if the lock is already held by another process
     */
    public static boolean obtainLock(String lockName, File folder) {
        File lockFile = new File(folder, lockName);
        try {
            RandomAccessFile raf = new RandomAccessFile(lockFile, "rw");
            FileLock instanceLock = raf.getChannel().tryLock();
            return instanceLock != null;
        } catch (IOException ex) {
            Tesla.logger.severe(ex.getMessage());
            return false;
        }
    }

    public static void setupLogger(File where, String basename, Logger logger, Level level) {
        rotateLogs(where, basename, 3);

        FileHandler fileHandler;
        try {
            logger.setLevel(level);
            fileHandler = new FileHandler((new File(where, basename + "-00.log")).getAbsolutePath());
            fileHandler.setFormatter(new SimpleFormatter());
            fileHandler.setLevel(level);
            logger.addHandler(fileHandler);

            for (Handler handler : Logger.getLogger("").getHandlers()) {
                if (handler instanceof ConsoleHandler) {
                    handler.setLevel(level);
                }
            }
        } catch (IOException | SecurityException ex) {
            logger.severe("Unable to establish log file: " + ex);
        }
    }

    private static void rotateLogs(File where, String basename, int max) {
        File logfile = new File(where, String.format("%s-%02d.log", basename, max));
        if (logfile.exists()) {
            logfile.delete();
        }
        if (max > 0) {
            File previous = new File(where, String.format("%s-%02d.log", basename, max - 1));
            if (previous.exists()) {
                previous.renameTo(logfile);
            }
            rotateLogs(where, basename, max - 1);
        }
    }
}