nz.co.fortytwo.signalk.util.Util.java Source code

Java tutorial

Introduction

Here is the source code for nz.co.fortytwo.signalk.util.Util.java

Source

/*
 *
 * Copyright (C) 2012-2014 R T Huitema. All Rights Reserved.
 * Web: www.42.co.nz
 * Email: robert@42.co.nz
 * Author: R T Huitema
 *
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package nz.co.fortytwo.signalk.util;

import static nz.co.fortytwo.signalk.util.SignalKConstants.*;
import static nz.co.fortytwo.signalk.util.SignalKConstants.self;
import static nz.co.fortytwo.signalk.util.SignalKConstants.self_str;
import static nz.co.fortytwo.signalk.util.SignalKConstants.vessels;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.StringReader;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Properties;
import java.util.regex.Pattern;

import mjson.Json;
import net.sf.marineapi.nmea.sentence.RMCSentence;
import nz.co.fortytwo.signalk.model.SignalKModel;
import nz.co.fortytwo.signalk.model.impl.SignalKModelFactory;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.log4j.Logger;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;

/**
 * Place for all the left over bits that are used across Signalk
 * 
 * @author robert
 * 
 */
public class Util {

    private static Logger logger = Logger.getLogger(Util.class);
    // private static Properties props;
    private static SignalKModel model = null;
    public static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_hh:mm:ss");
    public static File cfg = null;
    private static boolean timeSet = false;
    public static final double R = 6372800; // In meters

    private static Pattern selfMatch = Pattern.compile("\\.self\\.");
    private static String dot_self_dot = dot + self + dot;

    private static Pattern selfEndMatch = Pattern.compile("\\.self$");
    private static String dot_self = dot + self;

    /**
     * Smooth the data a bit
     * 
     * @param prev
     * @param current
     * @return
     */
    public static double movingAverage(double ALPHA, double prev, double current) {
        prev = ALPHA * prev + (1 - ALPHA) * current;
        return prev;
    }

    /**
     * Load the config from the
     * default location The config is cached, subsequent calls get the same
     * object
     * 
     * @param dir
     * @return
     * @throws FileNotFoundException
     * @throws IOException
     */
    public static void getConfig() throws FileNotFoundException, IOException {
        model = SignalKModelFactory.getInstance();
        Util.setDefaults(model);
        SignalKModelFactory.loadConfig(model);
        String mySelf = (String) model.get(ConfigConstants.UUID);
        Util.setSelf(mySelf);
    }

    /**
     * Load the provided model as the config. The config is cached, subsequent calls get the same
     * object
     * Useful in tests
     * 
     * @param dir
     * @return
     * @throws FileNotFoundException
     * @throws IOException
     */
    public static void setConfig(SignalKModel model) throws FileNotFoundException, IOException {
        Util.model = model;
    }

    /**
     * Config defaults
     * 
     * @param props
     */
    public static void setDefaults(SignalKModel model) {
        // populate sensible defaults here
        model.put(ConfigConstants.UUID, "self");
        model.put(ConfigConstants.WEBSOCKET_PORT, 3000);
        model.put(ConfigConstants.REST_PORT, 8080);
        model.put(ConfigConstants.CFG_DIR, "./conf/");
        model.put(ConfigConstants.CFG_FILE, "signalk.cfg");
        model.put(ConfigConstants.STORAGE_ROOT, "./storage/");
        model.put(ConfigConstants.STATIC_DIR, "./signalk-static");
        model.put(ConfigConstants.MAP_DIR, "./mapcache");
        model.put(ConfigConstants.DEMO, false);
        model.put(ConfigConstants.STREAM_URL, "./src/test/resources/motu.log&scanStream=true&scanStreamDelay=500");
        model.put(ConfigConstants.USBDRIVE, "/media/usb0");
        model.put(ConfigConstants.SERIAL_PORTS,
                "[\"/dev/ttyUSB0\",\"/dev/ttyUSB1\",\"/dev/ttyUSB2\",\"/dev/ttyACM0\",\"/dev/ttyACM1\",\"/dev/ttyACM2\"]");
        if (SystemUtils.IS_OS_WINDOWS) {
            model.put(ConfigConstants.SERIAL_PORTS, "[\"COM1\",\"COM2\",\"COM3\",\"COM4\"]");
        }
        model.put(ConfigConstants.SERIAL_PORT_BAUD, 38400);
        model.put(ConfigConstants.ENABLE_SERIAL, true);
        model.put(ConfigConstants.TCP_PORT, 55555);
        model.put(ConfigConstants.UDP_PORT, 55554);
        model.put(ConfigConstants.TCP_NMEA_PORT, 55557);
        model.put(ConfigConstants.UDP_NMEA_PORT, 55556);
        model.put(ConfigConstants.STOMP_PORT, 61613);
        model.put(ConfigConstants.MQTT_PORT, 1883);
        model.put(ConfigConstants.CLOCK_source, "system");
        model.put(ConfigConstants.HAWTIO_PORT, 8000);
        model.put(ConfigConstants.HAWTIO_AUTHENTICATE, false);
        model.put(ConfigConstants.HAWTIO_CONTEXT, "/hawtio");
        model.put(ConfigConstants.HAWTIO_WAR, "./hawtio/hawtio-default-offline-1.4.48.war");
        model.put(ConfigConstants.HAWTIO_START, false);
        model.put(ConfigConstants.VERSION, "0.1");
        model.put(ConfigConstants.ALLOW_INSTALL, true);
        model.put(ConfigConstants.ALLOW_UPGRADE, true);
        model.put(ConfigConstants.GENERATE_NMEA0183, true);
        model.put(ConfigConstants.START_MQTT, true);
        model.put(ConfigConstants.START_STOMP, true);
        model.put(ConfigConstants.CLIENT_TCP, null);
        model.put(ConfigConstants.CLIENT_MQTT, null);
        model.put(ConfigConstants.CLIENT_STOMP, null);

    }

    public static Json getWelcomeMsg() {
        Json msg = Json.object();
        msg.set(SignalKConstants.version, getVersion());
        msg.set(SignalKConstants.timestamp, getIsoTimeString());
        msg.set(self_str, ConfigConstants.UUID);
        return msg;
    }

    public static String getVersion() {
        return getConfigProperty(ConfigConstants.VERSION);
    }

    /**
     * Round to specified decimals
     * 
     * @param val
     * @param places
     * @return
     */
    public static double round(double val, int places) {
        double scale = Math.pow(10, places);
        long iVal = Math.round(val * scale);
        return iVal / scale;
    }

    /**
     * Attempt to set the system time using the GPS time
     * 
     * @param sen
     */
    @SuppressWarnings("deprecation")
    public static void checkTime(RMCSentence sen) {
        if (timeSet)
            return;
        try {
            net.sf.marineapi.nmea.util.Date dayNow = sen.getDate();
            // if we need to set the time, we will be WAAYYY out
            // we only try once, so we dont get lots of native processes
            // spawning if we fail
            timeSet = true;
            Date date = new Date();
            if ((date.getYear() + 1900) == dayNow.getYear()) {
                if (logger.isDebugEnabled())
                    logger.debug("Current date is " + date);
                return;
            }
            // so we need to set the date and time
            net.sf.marineapi.nmea.util.Time timeNow = sen.getTime();
            String yy = String.valueOf(dayNow.getYear());
            String MM = pad(2, String.valueOf(dayNow.getMonth()));
            String dd = pad(2, String.valueOf(dayNow.getDay()));
            String hh = pad(2, String.valueOf(timeNow.getHour()));
            String mm = pad(2, String.valueOf(timeNow.getMinutes()));
            String ss = pad(2, String.valueOf(timeNow.getSeconds()));
            if (logger.isDebugEnabled())
                logger.debug("Setting current date to " + dayNow + " " + timeNow);
            String cmd = "sudo date --utc " + MM + dd + hh + mm + yy + "." + ss;
            Runtime.getRuntime().exec(cmd.split(" "));// MMddhhmm[[yy]yy]
            if (logger.isDebugEnabled())
                logger.debug("Executed date setting command:" + cmd);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }

    }

    /**
     * pad the value to i places, eg 2 >> 02
     * 
     * @param i
     * @param valueOf
     * @return
     */
    private static String pad(int i, String value) {
        while (value.length() < i) {
            value = "0" + value;
        }
        return value;
    }

    public static double kntToMs(double speed) {
        return speed * KNOTS_TO_MS;
    }

    public static double msToKnts(double speed) {
        return speed * MS_TO_KNOTS;
    }

    public static String getConfigProperty(String prop) {
        try {
            return (String) model.get(prop);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    public static Json getConfigJsonArray(String prop) {
        try {
            String arrayStr = (String) model.get(prop);
            if (StringUtils.isNotBlank(arrayStr) && arrayStr.length() > 2) {
                Json array = Json.read(arrayStr);
                return array;
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    public static Integer getConfigPropertyInt(String prop) {
        try {
            if (model.get(prop) instanceof String) {
                return (Integer.valueOf((String) model.get(prop)));
            }
            if (model.get(prop) instanceof Number) {
                return ((Number) model.get(prop)).intValue();
            }

            return (Integer) model.get(prop);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    public static Double getConfigPropertyDouble(String prop) {
        try {
            if (model.get(prop) instanceof String) {
                return (Double.valueOf((String) model.get(prop)));
            }
            return (Double) model.get(prop);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    public static Boolean getConfigPropertyBoolean(String prop) {
        try {
            if (model.get(prop) instanceof Boolean) {
                return ((Boolean) model.get(prop));
            }
            return new Boolean((String) model.get(prop));
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    public static Pattern regexPath(String newPath) {
        // regex it
        String regex = newPath.replaceAll(".", "[$0]").replace("[*]", ".*").replace("[?]", ".");
        return Pattern.compile(regex);
    }

    public static String fixSelfKey(String key) {
        key = selfMatch.matcher(key).replaceAll(dot_self_dot);
        key = selfEndMatch.matcher(key).replaceAll(dot_self);
        return key;
    }

    public static String sanitizePath(String newPath) {
        newPath = newPath.replace('/', '.');
        if (newPath.startsWith(SignalKConstants.dot))
            newPath = newPath.substring(1);
        if (!newPath.endsWith("*") || !newPath.endsWith("?"))
            newPath = newPath + "*";

        return newPath;
    }

    public static void populateTree(SignalKModel signalkModel, SignalKModel temp, String p) {
        NavigableSet<String> node = signalkModel.getTree(p);
        if (logger.isDebugEnabled())
            logger.debug("Found node:" + p + " = " + node);
        if (node != null && node.size() > 0) {
            addNodeToTemp(signalkModel, temp, node);
        } else {
            temp.put(p, signalkModel.get(p));
        }

    }

    public static SignalKModel populateModel(SignalKModel model, String mapDump) throws IOException {
        Properties props = new Properties();
        props.load(new StringReader(mapDump.substring(1, mapDump.length() - 1).replace(", ", "\n")));
        for (Map.Entry<Object, Object> e : props.entrySet()) {
            if (e.getValue().equals("true") || e.getValue().equals("false")) {
                model.put((String) e.getKey(), Boolean.getBoolean((String) e.getValue()));
            } else if (NumberUtils.isNumber((String) e.getValue())) {
                model.put((String) e.getKey(), NumberUtils.createDouble((String) e.getValue()));
            } else {
                model.put((String) e.getKey(), e.getValue());
            }
        }
        return model;
    }

    public static SignalKModel populateModel(SignalKModel signalk, File file) throws IOException {
        return populateModel(signalk, FileUtils.readFileToString(file));
    }

    /**
     * Recursive findNode()
     * 
     * @param node
     * @param fullPath
     * @return
     */
    public static Json findNode(Json node, String fullPath) {
        String[] paths = fullPath.split("\\.");
        // Json endNode = null;
        for (String path : paths) {
            logger.debug("findNode:" + path);
            node = node.at(path);
            if (node == null)
                return null;
        }
        return node;
    }

    public static void addNodeToTemp(SignalKModel temp, NavigableSet<String> node) {
        SignalKModel model = SignalKModelFactory.getInstance();
        addNodeToTemp(model, temp, node);
    }

    public static void addNodeToTemp(SignalKModel model, SignalKModel temp, NavigableSet<String> node) {
        for (String key : node) {
            temp.put(key, model.get(key));
        }
    }

    public static String getIsoTimeString() {
        // TODO Auto-generated method stub
        return DateTime.now(DateTimeZone.UTC).toDateTimeISO().toString();
        // return ISO8601DateFormat.getDateInstance().format(new Date());
    }

    public static String getIsoTimeString(DateTime now) {
        return now.toDateTimeISO().toString();
    }

    public static String getIsoTimeString(long timestamp) {
        return new DateTime(timestamp, DateTimeZone.UTC).toDateTimeISO().toString();
    }

    public static double haversineMeters(double lat, double lon, double anchorLat, double anchorLon) {
        double dLat = Math.toRadians(anchorLat - lat);
        double dLon = Math.toRadians(anchorLon - lon);
        lat = Math.toRadians(lat);
        anchorLat = Math.toRadians(anchorLat);

        double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
                + Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(lat) * Math.cos(anchorLat);
        double c = 2 * Math.asin(Math.sqrt(a));
        return R * c;
    }

    public static String getContext(String path) {
        // return vessels.*
        // TODO; robustness for "signalk/api/v1/", and "vessels.*" and
        // "list/vessels"
        if (StringUtils.isBlank(path))
            return "";
        if (path.equals(SignalKConstants.CONFIG)) {
            return path;
        }
        if (path.startsWith(SignalKConstants.CONFIG + dot)) {
            int p1 = path.indexOf(SignalKConstants.CONFIG) + SignalKConstants.CONFIG.length() + 1;

            int pos = path.indexOf(".", p1);
            if (pos < 0)
                return path;
            return path.substring(0, pos);
        }
        if (path.equals(vessels)) {
            return path;
        }
        if (path.startsWith(vessels + dot) || path.startsWith(SignalKConstants.LIST + dot + vessels + dot)) {
            int p1 = path.indexOf(vessels) + vessels.length() + 1;

            int pos = path.indexOf(".", p1);
            if (pos < 0)
                return path;
            return path.substring(0, pos);
        }
        return "";
    }

    public static void setSelf(String self) {
        //self = self;
        dot_self_dot = dot + self + dot;
        dot_self = dot + self;
        SignalKConstants.self = self;
        SignalKConstants.vessels_dot_self_dot = vessels + dot + self + dot;
        SignalKConstants.vessels_dot_self = vessels + dot + self;
    }

    public static boolean sameNetwork(String localAddress, String remoteAddress) throws Exception {
        InetAddress addr = InetAddress.getByName(localAddress);
        NetworkInterface networkInterface = NetworkInterface.getByInetAddress(addr);
        short netmask = -1;
        for (InterfaceAddress address : networkInterface.getInterfaceAddresses()) {
            if (address.getAddress().equals(addr)) {
                netmask = address.getNetworkPrefixLength();
            }
        }
        byte[] a1 = InetAddress.getByName(localAddress).getAddress();
        byte[] a2 = InetAddress.getByName(remoteAddress).getAddress();
        byte[] m = InetAddress.getByName(normalizeFromCIDR(netmask)).getAddress();

        for (int i = 0; i < a1.length; i++)
            if ((a1[i] & m[i]) != (a2[i] & m[i]))
                return false;

        return true;

    }

    /*
     * RFC 1518, 1519 - Classless Inter-Domain Routing (CIDR)
     * This converts from "prefix + prefix-length" format to
     * "address + mask" format, e.g. from xxx.xxx.xxx.xxx/yy
     * to xxx.xxx.xxx.xxx/yyy.yyy.yyy.yyy.
     */
    public static String normalizeFromCIDR(short bits) {
        final int mask = (bits == 32) ? 0 : 0xFFFFFFFF - ((1 << bits) - 1);

        return Integer.toString(mask >> 24 & 0xFF, 10) + "." + Integer.toString(mask >> 16 & 0xFF, 10) + "."
                + Integer.toString(mask >> 8 & 0xFF, 10) + "." + Integer.toString(mask >> 0 & 0xFF, 10);
    }
}