package ruleset.plugin; import java.io.*; import java.util.*; import pds.util.*; import pds.ruleset.*; /** * Multi-function lookup utility. This application is designed to function * as a plug-in the the PPI Rulesets. This plug-in supports a variety of lookup * related functions including the lookup of values in ASCII comma seperated * value (CSV) tables. *

* The general form of usage is:
*

* lookup service [parameters...] *
* where service is the name of the service to run and * parameters is one or more parameters required by the service. *

*

* Supported lookup services are:
*

*Table: Lookup values in from a file which contains ASCII, comma seperated * values. *
* * @author Todd King, Steven Joy, Joe Mafi * @author IGPP/UCLA/PDS * @version 1.0, 05/21/03 * @since 1.0 */ public class Lookup { /** Nested class - internal use only. * This is a nested class which is used to store items found in the * lookup source which match the passed constraints. */ static class Item implements Comparable { /** Start time of the item */ PPITime mStart; /** Stop time of the item */ PPITime mStop; /** Value of the item. */ String mValue; /** Overlap with interval */ long mOverlap; /** The method of comparing Items. The value assigned to this * method matches the "order" option. * */ static int mCompareOrder = 0; /** * Creates an instanace. */ public Item() { } /** * Sets the overlap of the passed start and stop times with this * interval. Should be called after the mStart and mStop times * are set. * * @param start the start time of the interval. * @param stop the stop time of the interval. * */ public void setOverlap(PPITime start, PPITime stop) { long startMilli; long stopMilli; if(mStart.compareTo(start) < 0) { startMilli = start.mDate.getTimeInMillis(); } else { startMilli = mStart.mDate.getTimeInMillis(); } if(mStop.compareTo(stop) > 0) { stopMilli = stop.mDate.getTimeInMillis(); } else { stopMilli = mStop.mDate.getTimeInMillis(); } mOverlap = stopMilli - startMilli; } /** * Compares two Items. */ public int compareTo(Object o) { double diff; int c = 0; int d = 1; Item to = (Item) o; switch(mCompareOrder) { case 0: // AS_IS return 1; case 1: // CHRON_ASCENDING c = mStart.compareTo(to.mStart); d = mValue.compareTo(to.mValue); break; case 2: // CHRON_DESCENDING c = -mStart.compareTo(to.mStart); d = mValue.compareTo(to.mValue); break; case 3: // OVERLAP_ASCENDING diff = mOverlap - to.mOverlap; if(diff == 0) d = 0; if(diff < 0) d = -1; if(diff > 0) d = 1; break; case 4: // OVERLAP_DESCENDING diff = to.mOverlap - mOverlap; if(diff == 0) d = 0; if(diff < 0) d = -1; if(diff > 0) d = 1; break; case 5: // SPAN_ASCENDING diff = mStart.span(mStop) - to.mStart.span(to.mStop); if(diff == 0) d = 0; if(diff < 0) d = -1; if(diff > 0) d = 1; break; case 6: // SPAN_DESCENDING diff = to.mStart.span(to.mStop) - mStart.span(mStop); if(diff == 0) d = 0; if(diff < 0) d = -1; if(diff > 0) d = 1; break; case 7: // Remove duplicates - UNIQUE d = mValue.compareTo(to.mValue); if(d != 0) d = 1; // Keep same order - throw out duplicates break; } if(c == 0) return d; else return c; } } /** * Creates an instance. */ public Lookup() { } /** * Entry point for application. */ public static void main(String[] args) { boolean good = false; if(args.length < 1) { PPIRuleset.showRule(PPIAction.MESSAGE, "lookup plugin. Proper usage: time service {...}"); PPIRuleset.showRule(PPIAction.MESSAGE, "Available services are:"); PPIRuleset.showRule(PPIAction.MESSAGE, "\tTable: Lookup values in an ASCII table."); return; } if(args[0].compareToIgnoreCase("Table") == 0) { good = true; Table(args); } if(!good) PPIRuleset.showRule(PPIAction.MESSAGE, "time called with unknown action request. " + args[0]); } /** * Lookup values in an ASCII CSV table. * The lookup plug-in returns a PDS label parameter compatible with the PDS/PPI ruleset * language. Returned values are based upon a lookup table search. * Results are returned as a ruleset assignment and are in the form: * *
* $<parameter> = value *
* * Returned values are formatted according to PDS implementation of ODL lexical standards. This * means that strings are enclosed in double quotes, and multiple value sets are enclosed in brackets * "{}". *

* Command line options *

* Required command line options: *

*

*
source
[character string] Name (includ ing path) of the lookup table. Lookup tables * should be ASCII CSV files with the format: start_time,stop_time,value. Here * start_time and start_time are in PDS format (truncated at the minutes), and * value is the return value assigned to that interval.
* *
start
[PDS time*] Start time of the file to be labeled
* *
stop
[PDS time*] Stop time of the file to be labeled
* *
fudge
[integer] Size of the tolerance (in minutes) used * in matching the start and stop to intervals in the lookup table. * The way the fudge factor is applied depends upon the search method (see section describing search * method below)
* *
parameter
[character string] Parameter to return (e.g. TARG_LIST).
* *
method
[character string] Name of the method to use in search of * lookup table. Options: *
* * Phase - Selection criteria: start >= start_time - * fudge && stop <= stop_time + fudge_factor.
* Target - Selection criteria: start <= stop_time - * fudge && stop >= start_time + fudge_factor. *
*
* *
return
[character string] Indicates whether to return value * will consist of a list or a single item. Options:
* LIST - multiple values may be returned in a comma-separated list
* SINGLE - only a single value will be returned
*
* Default for return depends upon the choice of search method.
* If method=0, then the default return is SINGLE. * If method=1, then the default return is LIST. *
* *
*

* Optional command line options: *

*

* *
order
[character string] Way in which a LIST will be sorted. If * return_type=SINGLE any value given for list_order is ignored. Options:
* * CHRON_ASCENDING - ascending by start_time
* CHRON_DESCENDING - descending by start_time
* ALPHA_ASCENDING - ascending by ASCII sort of return value
* ALPHA_DESCENDING - descending by ASCII sort of return value
* AS_IS - preserve order of items in the lookup table
* *
* *
single
[character string] Method by which to reduce the return value from multiple * items to a single item. Options:
* MAX_LOOKUP - value corresponding to the interval from lookup table * of longest duration
* MIN_LOOKUP - value corresponding to the interval from lookup table of * shortest duration
* MAX_DATA - value from lookup table covering the greatest amount * (time span) of data interval
* MIN_DATA - value from lookup table covering the least amount (time * span) of data interval
* *
max
[integer] Maximum number of items to include in a list. After a list is sorted * according to the list_order parameter, items in excess of max_list_items will * be dropped. The first item is considered item 1. max_list_items = 1 is * equivalent to return_type = SINGLE, using list_order rather than * single_method to determine what value is returned.
*
* * Defaults *
* fudge=0
* return:
* if method=Phase, return=SINGLE
* if method=Target, return=LIST
* order = AS_IS
* single=MIN_LOOKUP
* max=No maximum (all items will be returned)
*
* * PDS Time Note: Both year-month-day or year-day of year format PDS times are supported. *

* Examples
* excerpt from targets lookup table "GLL_TARGET.TAB" ... *

 * 1995-07-01T00:00,             EOM , JUPITER
 * 1997-12-16T11:30, 1997-12-16T12:45, EUROPA
 * 1997-12-15T09:30, 1997-12-15T10:30, GANYMEDE
 * LAUNCH          , 1995-12-06T00:00, SOLAR_WIND
 * 
*

* Example 1:
* excerpt from rule set ... *

 * $lookup = "GLL_TARGET.TAB"
 * $start = "1997-12-15T00:00"
 * $stop = "1998-02-09T00:00"
 * $fudge = 720
 * $parameter = "TARG_LIST"
 * $search = 1
 * $return = "LIST"
 *
 * <RUN java lookup table source=$lookup start=$start
 *    stop=$stop fudge=$fudge parameter=$parameter
 *    method=$search return=$return>
 * 
*

* would return:
*

 * $TARG_LIST = {"JUPITER","EUROPA","GANYMEDE"}
 * 
* * Example 2:
* excerpt from rule set ... *
 * [same as Example 1, except]
 * $order = "CHRON_ASCENDING"
 *
 * <RUN java lookup table source=$lookup start=$start
 *    stop=$stop fudge=$fudge parameter=$parameter
 *    method=$search return=$return order=$order>
 * 
*

* would return:
*

 * $TARG_LIST = {"JUPITER","GANYMEDE","EUROPA"}
 * 
* * Example 3:
* excerpt from rule set ... *
 * [same as Example 1, except]
 * $order = "CHRON_DESCENDING"
 * $max = 1
 *
 * <RUN java lookup table source=$lookup start=$start
 *    stop=$stop fudge=$fudge parameter=$parameter
 *    method=$search return=$return order=$order
 *    max=$max>
 * 
*

* would return:
*

 * $TARG_LIST = "EUROPA"
 * 
* * Example 4:
* excerpt from rule set ... *
 * [same as Example 1, except]
 * $search = 1
 * $return = "SINGLE"
 * $single = "MIN_LOOKUP"
 *
 * <RUN java lookup table source=$lookup start=$start
 *    stop=$stop fudge=$fudge parameter=$parameter
 *    method=$search return=$return single=$single>
 * 
*

* would return:
*

 * $TARG_LIST = "GANYMEDE"
 * 
* * Example 5 (differs from example 4 only in search method):
* excerpt from rule set ... *
 * [same as Example 1, except]
 * $search = 0
 * $return = "SINGLE"
 * $single = "MIN_LOOKUP"
 *
 * <RUN java lookup table source=$lookup start=$start
 *    stop=$stop fudge=$fudge parameter=$parameter
 *    method=$search return=$return single=$single>
 * 
*

* would return:
*

 * $TARG_LIST = "JUPITER"
*
* * @since 1.0 */ public static void Table(String[] args) { // Process passed arguments String source = PPIOption.find(args, "SOURCE", null, 1); String start = PPIOption.find(args, "START", null, 1); String stop = PPIOption.find(args, "STOP", null, 1); String fudge = PPIOption.find(args, "FUDGE", "0", 1); String parameter = PPIOption.find(args, "PARAMETER", null, 1); String method = PPIOption.find(args, "METHOD", "Phase", 1); String returnAs = PPIOption.find(args, "RETURN", null, 1); String order = PPIOption.find(args, "ORDER", null, 1); String maxList = PPIOption.find(args, "MAX", "0", 1); String single = PPIOption.find(args, "SINGLE", "MIN_SPAN", 1); String format = PPIOption.find(args, "FORMAT", "STRING", 1); // Enumerated METHOD tokens final int METHOD_PHASE = 0; final int METHOD_TARGET = 1; PPIOption methodOpt = new PPIOption(); methodOpt.add("PHASE", METHOD_PHASE); methodOpt.add("TARGET", METHOD_TARGET); // Enumerated RETURN tokens final int RETURN_LIST = 0; final int RETURN_SINGLE = 1; PPIOption returnOpt = new PPIOption(); returnOpt.add("LIST", RETURN_LIST); returnOpt.add("SINGLE", RETURN_SINGLE); // Enumerated ORDER tokens final int ORDER_AS_IS = 0; final int ORDER_CHRON_ASCENDING = 1; final int ORDER_CHRON_DESCENDING = 2; final int ORDER_OVERLAP_ASCENDING = 3; final int ORDER_OVERLAP_DESCENDING= 4; final int ORDER_SPAN_ASCENDING = 5; final int ORDER_SPAN_DESCENDING = 6; final int ORDER_UNIQUE = 7; PPIOption orderOpt = new PPIOption(); orderOpt.add("AS_IS", ORDER_AS_IS); orderOpt.add("CHRON_ASCENDING", ORDER_CHRON_ASCENDING); orderOpt.add("CHRON_DESCENDING",ORDER_CHRON_DESCENDING); orderOpt.add("OVERLAP_ASCENDING", ORDER_OVERLAP_ASCENDING); orderOpt.add("OVERLAP_DESCENDING", ORDER_OVERLAP_DESCENDING); orderOpt.add("SPAN_ASCENDING", ORDER_SPAN_ASCENDING); orderOpt.add("SPAN_DESCENDING",ORDER_SPAN_DESCENDING); // Enumerated SINGLE tokens final int SINGLE_MAX_OVERLAP = 1; final int SINGLE_MIN_OVERLAP = 2; final int SINGLE_MAX_SPAN = 3; final int SINGLE_MIN_SPAN = 4; PPIOption singleOpt = new PPIOption(); singleOpt.add("MAX_OVERLAP", SINGLE_MAX_OVERLAP); singleOpt.add("MIN_OVERLAP", SINGLE_MIN_OVERLAP); singleOpt.add("MAX_SPAN", SINGLE_MAX_SPAN); singleOpt.add("MIN_SPAN", SINGLE_MIN_SPAN); // Enumerated METHOD tokens final int FORMAT_STRING = 0; final int FORMAT_IDENTIFIER = 1; PPIOption formatOpt = new PPIOption(); formatOpt.add("STRING", FORMAT_STRING); formatOpt.add("IDENTIFIER", FORMAT_IDENTIFIER); ArrayList findList = new ArrayList(); PPITable file = new PPITable(); PPITime time = new PPITime(); boolean leave = false; String buffer = ""; String[] field; PPITime recordStart = new PPITime(); PPITime recordStop = new PPITime(); PPITime timeStart = new PPITime(); PPITime timeStop = new PPITime(); Item item; String value; String delim; String quote; ArrayList valueList = new ArrayList(); int cnt; // Internal values of options. int max; double minutes; int methodID; int orderID; int singleID; int returnID; int formatID; String header = new String("'lookup table' not called with the proper arguments."); // Check that required arguments were passed. if(source == null) { PPIRuleset.show(header); PPIRuleset.showRule(PPIAction.MESSAGE, "\tSOURCE not specified"); leave = true; } if(start == null) { if(!leave) PPIRuleset.showRule(PPIAction.MESSAGE, header); PPIRuleset.showRule(PPIAction.MESSAGE, "\tSTART not specified"); leave = true; } if(stop == null) { if(!leave) PPIRuleset.showRule(PPIAction.MESSAGE, header); PPIRuleset.showRule(PPIAction.MESSAGE, "\tSTOP not specified"); leave = true; } if(parameter == null) { if(!leave) PPIRuleset.showRule(PPIAction.MESSAGE, header); PPIRuleset.showRule(PPIAction.MESSAGE, "\tPARAMETER not specified"); leave = true; } if(leave) return; // Tokenize options methodID = methodOpt.token(method); if(methodID == -1) { PPIRuleset.showRule(PPIAction.MESSAGE, "\tInvalid option for METHOD: " + method); return; } singleID = singleOpt.token(single); if(singleID == -1) { PPIRuleset.showRule(PPIAction.MESSAGE, "\tInvalid option for SINGLE: " + single); return; } formatID = formatOpt.token(format); if(formatID == -1) { PPIRuleset.showRule(PPIAction.MESSAGE, "\tInvalid option for FORMAT: " + single); return; } minutes = Double.parseDouble(fudge); max = Integer.parseInt(maxList); timeStart.convert(PPITime.PDS, start); timeStop.convert(PPITime.PDS, stop); // Set return form if(returnAs == null) returnAs = "LIST"; returnID = returnOpt.token(returnAs); if(returnID == -1) { PPIRuleset.showRule(PPIAction.MESSAGE, "\tInvalid option for RETURN: " + returnAs); return; } // If single select - choose sorting so that // desired value is first in list. if(order == null) { orderID = ORDER_AS_IS; switch(returnID) { case RETURN_SINGLE: switch(singleID) { case SINGLE_MAX_OVERLAP: orderID = ORDER_OVERLAP_DESCENDING; break; case SINGLE_MIN_OVERLAP: orderID = ORDER_OVERLAP_ASCENDING; break; case SINGLE_MAX_SPAN: orderID = ORDER_SPAN_ASCENDING; break; case SINGLE_MIN_SPAN: orderID = ORDER_SPAN_DESCENDING; break; } break; } } else { orderID = orderOpt.token(order); if(orderID == -1) { PPIRuleset.showRule(PPIAction.MESSAGE, "\tInvalid option for ORDER: " + order); return; } } // Adjust time based on method switch(methodID) { case METHOD_PHASE: if(returnAs == null) returnAs = "SINGLE"; break; case METHOD_TARGET: if(returnAs == null) returnAs = "LIST"; break; } // Open the table if(!file.open(source, false)) { PPIRuleset.showRule(PPIAction.MESSAGE, "Unable to open file: " + source); return; } // Scan table for records while(file.readRecord()) { try { recordStart.convert(PPITime.PDS, file.getValue(0)); recordStop.convert(PPITime.PDS, file.getValue(1)); } catch(Exception e) { PPIRuleset.showRule(PPIAction.MESSAGE, "Error parsing record " + file.recordAt()); continue; } switch(methodID) { case METHOD_PHASE: recordStart.advance(-minutes); recordStop.advance(minutes); if(timeStart.compareTo(recordStart) >= 0 && timeStop.compareTo(recordStop) <= 0) { item = new Item(); item.mStart = new PPITime(recordStart); item.mStop = new PPITime(recordStop); item.setOverlap(timeStart, timeStop); item.mValue = file.getValue(2); findList.add(item); } break; case METHOD_TARGET: recordStart.advance(minutes); recordStop.advance(-minutes); if(timeStart.compareTo(recordStop) <= 0 && timeStop.compareTo(recordStart) >= 0) { item = new Item(); item.mStart = new PPITime(recordStart); item.mStop = new PPITime(recordStop); item.setOverlap(timeStart, timeStop); item.mValue = file.getValue(2); findList.add(item); } break; } } if(findList.size() > 0) { item = (Item) findList.get(0); item.mCompareOrder = orderID; } // Sort the list SortedSet set = new TreeSet(findList); // Build value list switch(returnID) { case RETURN_SINGLE: try { item = (Item) set.first(); valueList.add(item.mValue); } catch (Exception e) { } break; case RETURN_LIST: if(set.size() > 0) { Iterator it = set.iterator(); ArrayList setArray = new ArrayList(); while(it.hasNext()) { item = (Item) it.next(); item.mCompareOrder = ORDER_UNIQUE; setArray.add(item); } // Sort the list and remove duplicates SortedSet uniqueSet = new TreeSet(setArray); it = uniqueSet.iterator(); cnt = 0; while(it.hasNext()) { if(cnt == max && max > 0) break; cnt++; item = (Item) it.next(); valueList.add(item.mValue); } } break; } // Build-up response value = ""; delim = ""; quote = ""; if(valueList.size() == 0) { value = ""; } else { if(valueList.size() > 1 || returnID == RETURN_LIST) { value += "{ "; quote = "\""; } // Start a set for(int i = 0; i < valueList.size(); i++) { buffer = (String) valueList.get(i); switch(formatID) { case FORMAT_STRING: buffer = buffer.replace('_', ' '); value += delim + quote + buffer + quote; break; case FORMAT_IDENTIFIER: buffer = buffer.replace(' ', '_'); buffer = buffer.toUpperCase(); value += delim + buffer; break; } delim = ", "; } if(valueList.size() > 1 || returnID == RETURN_LIST) value += " }"; // End of set } PPIRuleset.showRule(PPIAction.ASSIGN, parameter, value); file.close(); } }