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* Default for return depends upon the choice of search method.
* SINGLE - only a single value will be returned
*
* If method=0, then the default return is SINGLE. * If method=1, then the default return is LIST. *
* Optional command line options: *
*
* ** * Defaults *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 intervalmax [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. *
* fudge=0* * PDS Time Note: Both year-month-day or year-day of year format PDS times are supported. *
* 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)
*
* 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:
* [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:
* [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:
* [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):
* [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();
}
}