gov.usgs.anss.query.EdgeQueryOptions.java Source code

Java tutorial

Introduction

Here is the source code for gov.usgs.anss.query.EdgeQueryOptions.java

Source

/*
 * Copyright 2010, Institute of Geological & Nuclear Sciences Ltd or
 * third-party contributors as indicated by the @author tags.
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program 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 General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package gov.usgs.anss.query;

import gov.usgs.anss.query.cwb.formatter.CWBQueryFormatter;
import nz.org.geonet.simplequakeml.QuakeML_RT_1_2;
import nz.org.geonet.simplequakeml.domain.Event;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Duration;
import org.joda.time.ReadableDuration;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.DateTimeFormatterBuilder;

import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * An attempt to encapsulate (read isolate) EdgeQueryClient command line args.
 * TODO: move line space handling, double quoting and splitting of arguments to a separate method.
 * 
 * @author richardg
 */
public class EdgeQueryOptions {

    private static final Logger logger = Logger.getLogger(EdgeQueryOptions.class.getName());

    static {
        logger.fine("$Id$");
    }

    public enum OutputType {

        ms, mx, msz, sac, dcc, dcc512, HOLD, text, NULL;
    }

    private static String beginFormat = "YYYY/MM/dd HH:mm:ss";
    private static String beginFormatDoy = "YYYY,DDD-HH:mm:ss";
    private static String millisFormat = ".SSS";
    private static DateTimeFormatter parseMillisFormat = DateTimeFormat.forPattern(millisFormat);
    private static DateTimeFormatter parseBeginFormat = new DateTimeFormatterBuilder().appendPattern(beginFormat)
            .appendOptional(parseMillisFormat.getParser()).toFormatter().withZone(DateTimeZone.forID("UTC"));
    private static DateTimeFormatter parseBeginFormatDoy = new DateTimeFormatterBuilder()
            .appendPattern(beginFormatDoy).appendOptional(parseMillisFormat.getParser()).toFormatter()
            .withZone(DateTimeZone.forID("UTC"));
    public String host = QueryProperties.getGeoNetCwbIP();

    public void setHost(String host) {
        this.host = host;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public int port = QueryProperties.getGeoNetCwbPort();
    public String[] args;
    public List<String> extraArgs;
    private Double duration = 300.0;
    private String seedname = null;
    private DateTime begin = null;
    private OutputType type = OutputType.sac;
    public boolean dbg = false;
    public boolean lsoption = false;
    public boolean lschannels = false;
    public int julian = 0;
    public String filenamein = null;
    public int blocksize = 512; // only used for msz type
    public String filemask = "%N";
    public boolean quiet = false;
    public boolean gapsonly = false;
    // Make a pass for the command line args for either mode!
    public String exclude = null;
    public boolean nosort = false;
    public boolean holdingMode = false;
    public String holdingIP = QueryProperties.getGeoNetCwbIP();
    public int holdingPort = QueryProperties.getGeoNetCwbPort();
    public String holdingType = "CWB";
    public boolean showIllegals = false;
    public boolean perfMonitor = false;
    public boolean chkDups = false;
    public boolean sacpz = false;
    public String pzunit = "nm";
    private Event event = null;
    private ReadableDuration offset = null;

    public boolean picks = true;

    private String synthetic = null;
    public boolean extendedPhases = false;

    private CustomEvent customEvent = null;

    /**
     * Parses known args into object fields. Does some argument validation and
     * potentially System.exit(0).
     * TODO: move any/all validation to a validateArgs method.
     * TODO: return a String array of unused/unparsed args to be used for
     * outputter customisation.
     * @param args the arguments to parse
     * @return unused args (unmodified order)
     */
    public List parse(String[] args) {

        //      List<String> argList = Arrays.asList(args);
        ArrayList<String> argList = new ArrayList(Arrays.asList(args));
        int pos = argList.indexOf("-f");
        if (pos != -1) {
            argList.remove(pos);
            filenamein = argList.remove(pos);
            quiet = argList.remove("-q");
            dbg = argList.remove("-dbg");

            return argList;
        }

        ArrayList<String> extraArgsList = new ArrayList(args.length);

        for (int i = 0; i < args.length; i++) {
            if (args[i].equals("-f")) { // Documented functionality.
                filenamein = args[i + 1];
                i++;
            } else if (args[i].equals("-t")) { // Documented functionality.
                setType(args[i + 1]);
                i++;
            } else if (args[i].equals("-msb")) { // Documented functionality.
                blocksize = Integer.parseInt(args[i + 1]);
                i++;
            } else if (args[i].equals("-o")) { // Documented functionality.
                filemask = args[i + 1];
                i++;
            } else if (args[i].equals("-e")) {
                exclude = "exclude.txt";
            } else if (args[i].equals("-el")) {
                exclude = args[i + 1];
                i++;
            } else if (args[i].equals("-ls")) { // Documented functionality.
                lsoption = true;
            } else if (args[i].equals("-lsc")) { // Documented functionality.
                lschannels = true;
            } else if (args[i].equals("-b")) { // Documented functionality.
                setBegin(args[i + 1]);
                i++;
            } else if (args[i].equals("-s")) { // Documented functionality.
                setSeedname(args[i + 1]);
                i++;
            } else if (args[i].equals("-d")) { // Documented functionality.
                setDuration(args[i + 1]);
                i++;
            } else if (args[i].equals("-q")) { // Documented functionality.
                quiet = true;
            } else if (args[i].equals("-nosort")) { // Documented functionality.
                nosort = true;
            } else if (args[i].equals("-nogaps"))
                ; // legal for sac and zero MS
            else if (args[i].equals("-noempty")) {
                extraArgsList.add(args[i]); // legal for mx
            } else if (args[i].equals("-notemp")) {
                extraArgsList.add(args[i]); // legal for mx
            } else if (args[i].equals("-nocleanup")) {
                extraArgsList.add(args[i]); // legal for mx
            } else if (args[i].equals("-nodups")) {
                chkDups = true;
            } else if (args[i].equals("-sactrim"))
                ; // legal for sac and zero MS
            else if (args[i].equals("-gaps")) {
                gapsonly = true; // legal for zero MS
            } else if (args[i].equals("-msgaps"))
                ; // legal for zero ms
            else if (args[i].equals("-udphold")) {
                gapsonly = true; // legal for zero MS
            } else if (args[i].equals("-chk"))
                ; // valid only for -t dcc
            else if (args[i].equals("-dccdbg"))
                ; // valid only for -t dcc & -t dcc512
            else if (args[i].equals("-perf")) {
                perfMonitor = true;
            } else if (args[i].equals("-nometa"))
                ;
            else if (args[i].equals("-fill")) {
                i++;
            } else if (args[i].equals("-sacpz")) {
                sacpz = true;
                pzunit = args[i + 1];
                i++;
            } else if (args[i].equals("-si")) {
                showIllegals = true;
            } else if (args[i].indexOf("-hold") == 0) {
                holdingMode = true;
                gapsonly = true;
                setType(OutputType.HOLD);
                String[] a = args[i].split(":");
                if (a.length == 4) {
                    holdingIP = a[1];
                    holdingPort = Integer.parseInt(a[2]);
                    holdingType = a[3];
                }
                logger.config("Holdings server=" + holdingIP + "/" + holdingPort + " type=" + holdingType);
            } else if (args[i].equals("-event")) {
                setEvent(args[i + 1]);
                i++;
            } else if (args[i].equals("-offset")) {
                setOffset(Double.parseDouble(args[i + 1]));
                i++;
            } else if (args[i].equals("-nopicks")) {
                picks = false;
            } else if (args[i].startsWith("-synthetic")) {
                String[] a = args[i].split(":");
                if (a.length == 2) {
                    setSynthetic(a[1]);
                } else {
                    setSynthetic("iasp91");
                }
            } else if (args[i].equals("-extended-phases")) {
                extendedPhases = true;
            } else if (args[i].startsWith("-event:")) {
                if (getCustomEvent() == null) {
                    setCustomEvent(new CustomEvent());
                }

                if (args[i].equals("-event:time")) {
                    getCustomEvent().setEventTime(args[++i]);
                } else if (args[i].equals("-event:lat")) {
                    getCustomEvent().setEventLat(args[++i]);
                } else if (args[i].equals("-event:lon")) {
                    getCustomEvent().setEventLon(args[++i]);
                } else if (args[i].equals("-event:depth")) {
                    getCustomEvent().setEventDepth(args[++i]);
                } else if (args[i].equals("-event:mag")) {
                    getCustomEvent().setEventMag(args[++i]);
                } else if (args[i].equals("-event:magtype")) {
                    getCustomEvent().setEventMagType(args[++i]);
                } else if (args[i].equals("-event:type")) {
                    getCustomEvent().setEventType(args[++i]);
                }
            } else {
                logger.info("Unknown CWB Query argument=" + args[i]);
                extraArgsList.add(args[i]);
            }

        }
        return extraArgsList;
    }

    /**
     * @return the synthetic
     */
    public String getSynthetic() {
        return synthetic;
    }

    /**
     * @param synthetic the synthetic to set
     */
    public void setSynthetic(String synthetic) {
        this.synthetic = synthetic;
    }

    /**
     * @return the customEvent
     */
    public CustomEvent getCustomEvent() {
        return customEvent;
    }

    /**
     * @param customEvent the customEvent to set
     */
    public void setCustomEvent(CustomEvent customEvent) {
        this.customEvent = customEvent;
    }

    /**
     * Return true if the arguments specified a batch file.
     * @return
     */
    public boolean isFileMode() {
        return filenamein != null;
    }

    /**
     * Return true if a list query -ls or -lsc was defined.
     * @return
     */
    public boolean isListQuery() {
        return (lsoption || lschannels);
    }

    /**
     * Validate parsed args. This should (initially at least) mimic the dodgy
     * args validation of the current client.
     * TODO: clean up later for context driven help???
     * @return boolean representing whether the args are valid
     */
    public boolean isValid() {
        if (isFileMode()) {
            return (extraArgs.isEmpty());
        }

        if (isListQuery()) {
            // No args checking done here.
            return true;
        }

        if (blocksize != 512 && blocksize != 4096) {
            logger.severe("-msb must be 512 or 4096 and is only meaningful for msz type");
            return false;
        }

        if (getEvent() != null && getCustomEvent() != null) {
            logger.severe("quakeML event cannot be used in conjunction with custom event parameters.");
            return false;
        }

        if (getBegin() == null) {
            logger.severe("You must enter a beginning time");
            return false;
        }

        if (getSeedname() == null) {
            logger.severe("-s SCNL is not optional, you must specify a seedname.");
            return false;
        }

        if (sacpz) {
            if (!pzunit.equalsIgnoreCase("nm") && !pzunit.equalsIgnoreCase("um")) {
                logger.warning("   ****** -sacpz units must be either um or nm switch values is " + pzunit);
                return false;
            }
        }

        if (getNsclComparator() == NSCL.NetworkComparator || getNsclComparator() == NSCL.StationComparator) {
            if (getType() == OutputType.sac) {
                logger.severe("\n    ***** Sac files must have names including the channel! *****");
                return false;
            }
            if (getType() == OutputType.msz) {
                logger.severe("\n    ***** msz files must have names including the channel! *****");
                return false;
            }
        }

        // TODO more checking to come.
        return true;
    }

    public Outputer getOutputter() {
        switch (getType()) {
        case ms:
            return new MSOutputer(this);
        case mx:
            return new MultiplexedMSOutputer(this);
        case sac:
            return new SacOutputer(this);
        case msz:
            return new MSZOutputer(this);
        case dcc:
            return new DCCOutputer(this);
        case dcc512:
            return new DCC512Outputer(this);
        case HOLD:
            return new HoldingOutputer(this);
        case text:
            return new TextOutputer(this);
        }
        return null;
    }

    public Comparator getNsclComparator() {
        // default to LocationComparator
        Comparator comparator = NSCL.LocationComparator;
        if (filemask.indexOf("%n") >= 0) {
            comparator = NSCL.NetworkComparator;
        }
        if (filemask.indexOf("%s") >= 0) {
            comparator = NSCL.StationComparator;
        }
        if (filemask.indexOf("%c") >= 0) {
            comparator = NSCL.ChannelComparator;
        }
        if (filemask.indexOf("%N") >= 0) {
            comparator = NSCL.LocationComparator;
        }
        if (filemask.indexOf("%N") >= 0) {
            comparator = NSCL.LocationComparator;
        }

        return comparator;
    }

    /**
     * Generate some (context?) appropriate help text.
     * @return the help string.
     */
    public String getHelp() {
        return QueryProperties.getUsage();
    }

    /**
     * Generate some (context?) appropriate usage text.
     * @return the usage string.
     */
    public String getUsage() {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    /**
     * A default constructor.
     */
    public EdgeQueryOptions() {
    }

    /**
     * Creates an EdgeQueryOptions object from a set of command line args.
     * @param args
     */
    public EdgeQueryOptions(String[] args) {
        this.args = args;
        this.extraArgs = parse(this.args);
    }

    /**
     * Creates an EdgeQueryOptions object from a single command line string.
     * TODO: Attempt to understand and sanitise this method.
     * @param line
     */
    public EdgeQueryOptions(String line) {
        boolean on = false;

        // Spaces and quoting...?
        char[] linechar = line.toCharArray();
        for (int i = 0; i < line.length(); i++) {
            if (linechar[i] == '"') {
                on = !on;
            } else if (linechar[i] == ' ') {
                if (on) {
                    linechar[i] = '@';
                }
            }
        }

        line = new String(linechar);
        line = line.replaceAll("\"", " ");
        line = line.replaceAll("  ", " ");
        line = line.replaceAll("  ", " ");
        line = line.replaceAll("  ", " ");

        this.args = line.split(" ");
        for (int i = 0; i < args.length; i++) {
            args[i] = args[i].replaceAll("@", " ");
        }
        this.extraArgs = parse(this.args);
    }

    /**
     * Returns a FileReader if filename has been specified, or a StringReader if
     * it just contains command line args.
     * TODO: replace this with a command line iterator...?
     * @return
     * @throws FileNotFoundException
     */
    public Reader getAsReader() throws FileNotFoundException {

        // if not -f mode, read in more command line parameters for the run
        if (this.filenamein != null) {
            return new FileReader(this.filenamein);
        }

        String line = "";
        for (int i = 0; i < this.args.length; i++) {
            line += this.args[i].replaceAll(" ", "@") + " ";
        }
        return new StringReader(line);

    }

    /**
     * Puts the command line args in single quotes, to be sent to the server.
     * TODO: This should be constructed from the fields.
     * @return the command line args, single quoted.
     */
    public String getSingleQuotedCommand() {
        // put command line in single quotes.
        return CWBQueryFormatter.miniSEED(getBeginWithOffset(), getDuration(), getSeedname());
    }

    /**
     * Parses the begin time.  This tries to match
     * the documentation for CWBClient but does not
     * match the Util.stringToDate2 method which attempted
     * to allow for milliseconds.
     *
     * @param beginTime
     * @return java.util.Date parsed from the being time.
     * @throws java.lang.IllegalArgumentException
     */
    public void setBegin(String beginTime) throws IllegalArgumentException {
        begin = null;

        try {
            begin = parseBeginFormat.parseDateTime(beginTime);
        } catch (Exception e) {
        }

        if (begin == null) {
            try {
                begin = parseBeginFormatDoy.parseDateTime(beginTime);
            } catch (Exception e) {
            }
        }

        // TODO Would be ideal if this error contained any range errors from
        // parseDateTime but this is hard with the two attempts at parsing.
        if (begin == null) {
            throw new IllegalArgumentException("Error parsing begin time.  Allowable formats " + "are: "
                    + beginFormat + "[" + millisFormat + "] or " + beginFormatDoy + "[" + millisFormat + "]");
        }
    }

    /**
     * @return the duration
     */
    public Double getDuration() {
        return duration;
    }

    /**
     * Sets the duration from a String. By default this is interpreted as seconds
     * but the user can append 'd' or 'D' to the number to represent days.
     * @param durationString the duration string to set
     */
    private void setDuration(String durationString) {
        if (durationString != null) {
            if (durationString.endsWith("d") || durationString.endsWith("D")) {
                setDuration(Double.parseDouble(durationString.substring(0, durationString.length() - 1)) * 86400.);
            } else {
                setDuration(Double.parseDouble(durationString));
            }
        }
    }

    /**
     * @param duration the duration to set
     */
    public void setDuration(Double duration) {
        this.duration = duration;
    }

    /**
     * @return the seedname
     */
    public String getSeedname() {
        if (seedname == null) {
            if (getEvent() != null) {
                List<NSCL> nscls = QuakeMLQuery.getPhases(getEvent());
                if (!nscls.isEmpty()) {
                    StringBuffer sb = new StringBuffer(nscls.size() * 13);
                    for (NSCL nscl : nscls) {
                        sb.append(nscl.toString()).append('|');
                    }
                    sb.setLength(sb.length() - 1);
                    return sb.toString();
                }
            }
        }
        return seedname;
    }

    /**
     * @param seedname the seedname to set
     */
    public void setSeedname(String seedname) {
        // Append wildcards to end of seedname
        if (seedname != null && seedname.length() < 12) {
            this.seedname = (seedname + ".............").substring(0, 12);
        } else {
            this.seedname = seedname;
        }
    }

    /**
     * @return the type
     */
    public OutputType getType() {
        return type;
    }

    /**
     * @param type the type to set
     */
    public void setType(OutputType type) {
        this.type = type;
    }

    /**
     * @param type the type to set
     */
    public void setType(String type) {
        this.type = OutputType.valueOf(type);
    }

    /**
     * @return the offset
     */
    public ReadableDuration getOffset() {
        return offset;
    }

    /**
     * @param seconds the offset to set
     */
    public void setOffset(double seconds) {
        this.offset = new Duration((long) (seconds * 1000));
    }

    /**
     * @return the begin
     */
    public DateTime getBegin() {

        DateTime quakeMLBegin = null;

        if (begin != null) {
            return begin;
        } else if (getCustomEvent() != null) {
            quakeMLBegin = getCustomEvent().getEventTime();
        } else if (getEvent() != null) {
            quakeMLBegin = event.getTime();
        }

        return quakeMLBegin;
    }

    /**
     * @return the begin
     */
    public Date getBeginAsDate() {
        return getBegin().toDate();
    }

    /**
     * @return the begin
     */
    public String getBeginAsString() {
        return parseBeginFormat.withZone(DateTimeZone.UTC).print(getBegin());
    }

    /**
     * @return the begin
     */
    public DateTime getBeginWithOffset() {
        return getBegin().plus(getOffset());
    }

    /**
     * @return the begin
     */
    public Date getBeginWithOffsetAsDate() {
        return getBeginWithOffset().toDate();
    }

    /**
     * @return the begin
     */
    public String getBeginWithOffsetAsString() {
        return parseBeginFormat.withZone(DateTimeZone.UTC).print(getBeginWithOffset());
    }

    /**
     * @param begin the begin to set
     */
    public void setBegin(DateTime begin) {
        this.begin = begin;
    }

    /**
     * @return the event
     */
    public Event getEvent() {
        return event;
    }

    /**
     * @param event the event to set
     */
    public void setEvent(Event event) {
        this.event = event;
    }

    /**
     * TODO: throw an exception if the event can't be found...?
     * @param event the GeoNet public ID, or fully qualified http or file URI of
     * an event to use.
     */
    public void setEvent(String event) {
        URI uri = null;
        try {
            uri = new URI(event);
        } catch (URISyntaxException ex) {
            Logger.getLogger(EdgeQueryOptions.class.getName()).log(Level.INFO,
                    "Event parameter was not opaque URI, assuming it's GeoNet ID.", ex);
        }

        if (uri != null) {
            if (uri.isAbsolute()) {
                if (uri.getScheme().startsWith("http")) {
                    String username = uri.getUserInfo();
                    String password = null;
                    if (username != null) {
                        int split = username.indexOf(":");
                        if (split != -1) {
                            password = username.substring(split);
                            username = username.substring(0, split);
                        }
                    }
                    try {
                        this.event = new QuakeML_RT_1_2().read(event);
                    } catch (Exception e) {
                        Logger.getLogger(EdgeQueryOptions.class.getName()).log(Level.SEVERE, null, e);
                    }
                } else if (uri.getScheme().equals("file")) {
                    try {
                        this.event = new QuakeML_RT_1_2().read(new FileInputStream(new File(uri)));
                    } catch (FileNotFoundException ex) {
                        Logger.getLogger(EdgeQueryOptions.class.getName()).log(Level.SEVERE, null, ex);
                    } catch (Exception ex) {
                        Logger.getLogger(EdgeQueryOptions.class.getName()).log(Level.SEVERE, null, ex);
                    }
                } else {
                    String quakeMlUri = null;
                    try {
                        quakeMlUri = QueryProperties.getQuakeMlUri(uri.getScheme());
                    } catch (MissingResourceException ex) {
                        logger.warning("Couldn't find quakeML URI pattern for " + uri.getScheme());
                        // TODO: list available patterns?
                        logger.info("Known QuakeML authorities are: "
                                + QueryProperties.getQuakeMlAuthorities().toString());
                    }

                    if (quakeMlUri != null) {
                        // Assume it's a flagged authority's public ID.
                        Matcher quakeMlUriMatcher = Pattern.compile("%ref%").matcher(quakeMlUri);
                        try {
                            this.event = new QuakeML_RT_1_2()
                                    .read(quakeMlUriMatcher.replaceAll(uri.getSchemeSpecificPart()));
                        } catch (Exception e) {
                            Logger.getLogger(EdgeQueryOptions.class.getName()).log(Level.SEVERE, null, e);
                        }
                    }
                }
            }
        }

        if (this.event == null) {
            logger.severe("failed to retrieve details for event string " + event);
        }
    }

}