/** * List of fields for a set of database entries. Fields are either * Strings or doubles. Format for the input file is: * ("S" | "D") name description * where name is a single token and description is the rest of the line. * @author Gary Yngve * @version 2/6/06 */ import java.util.*; public class FieldsDefinition { // a field consists of a type (String or double), // a fieldname, and a description private static class Field { boolean isString; // false implies double String name; String desc; Field(boolean isString, String name, String desc) { this.isString = isString; this.name = name; this.desc = desc; } } private int[] allToStringsMap; private int[] allToDoublesMap; private List fieldList = new ArrayList(); private int numStrings; /** * Constructs a FieldsDefinition from text in a Scanner * @param sc the Scanner containing the definition of the fields */ public FieldsDefinition(Scanner sc) { numStrings = 0; while(sc.hasNextLine()) { String s = sc.nextLine(); Scanner line = new Scanner(s); if (!line.hasNext()) throw new RuntimeException("line " + fieldList.size() + ": 1st token not found"); String type = line.next(); boolean isString; if (type.equals("S")) { isString = true; numStrings++; } else if (type.equals("D")) { isString = false; } else throw new RuntimeException("line " + fieldList.size() + ": unrecognized type"); if (!line.hasNext()) throw new RuntimeException("line " + fieldList.size() + ": 2nd token not found"); String name = line.next(); String desc = ""; /* hack, because I'm not sure I trust intermixing next with nextLine because of lookahead issues */ while (line.hasNext()) desc += line.next() + " "; fieldList.add(new Field(isString,name,desc.trim())); } int n = fieldList.size(); allToStringsMap = new int[n]; allToDoublesMap = new int[n]; int is = 0, id = 0; for(int i = 0; i < n; i++) { Field f = (Field)fieldList.get(i); if (f.isString) { allToStringsMap[i] = is++; allToDoublesMap[i] = -1; } else { allToStringsMap[i] = -1; allToDoublesMap[i] = id++; } } } /** * Returns the number of fields that are Strings. * @return the number of fields that are Strings */ public int numStringFields() { return numStrings; } /** * Returns the number of fields that are doubles. * @return the number of fields that are doubles */ public int numDoubleFields() { return fieldList.size()-numStrings; } /** * Returns the index in the String array for the corresponding field. * @param field tht number of the field * @return the index in the String array, or -1 if OOB or a double */ public int getStringIndex(int field) { if (field < 0 || field >= allToStringsMap.length) return -1; return allToStringsMap[field]; } /** * Returns the index in the double array for the corresponding field. * @param field tht number of the field * @return the index in the double array, or -1 if OOB or a String */ public int getDoubleIndex(int field) { if (field < 0 || field >= allToDoublesMap.length) return -1; return allToDoublesMap[field]; } /** * Returns the index of the field matching the name. * @param the name of the field being looked for * @return the index of the found field, or -1 if not found */ public int fieldNum(String name) { int j = 0; for(Iterator i = fieldList.iterator(); i.hasNext(); ) { Field f = (Field)i.next(); if (f.name.equals(name)) return j; j++; } return -1; } /** * Returns the name of field idx. * @param index of the field * @return name of the field at index */ public String fieldName(int idx) { return ((Field)fieldList.get(idx)).name; } /** * Returns the description of field idx. * @param index of the field * @return description of the field at index */ public String fieldDesc(int idx) { return ((Field)fieldList.get(idx)).desc; } /** * Reads the line of data into the set of fields stored as double and * String arrays. Some fields may be empty. * Empty string fields are indicated by null, and empty double fields by -1 * (assumes all double fields are to be nonnegative) * @param line the line of data to be parsed (delimited by ^) * @param d array of double fields * @param s array of String fields */ public void parseLine(String line, double[] d, String[] s) { int a = 0, b = 0; int di = 0, si = 0; for(int f = 0; f < fieldList.size(); f++) { if (((Field)fieldList.get(f)).isString) { if (line.charAt(a) != '~') throw new RuntimeException(line + "\n" + "~ expected at start of field, field " + f); b = a+1; while(b < line.length() && line.charAt(b) != '~') b++; if (b >= line.length()) throw new RuntimeException(line + "\n" + "eol when searching for ending ~, field " + f); if (b-(a+1) == 1) s[si] = null; else s[si] = line.substring(a+1,b); si++; a = b + 2; // skip the ~ and ^ b++; // skip the ~ } else { b = a; while(b < line.length() && line.charAt(b) != '^') b++; if (b == a) d[di] = -1; else { try { d[di] = Double.valueOf(line.substring(a,b)); } catch (Exception e) { throw new RuntimeException(line + "\nfield " + f + ": " + e.toString()); } } a = b + 1; // skip the ^ di++; } } } public boolean isString(int i) { Field f = (Field)fieldList.get(i); return f.isString; } public String fieldString(int i) { Field f = (Field)fieldList.get(i); String s = ""; s += (f.isString?"String":"double") + " " + f.name + " " + f.desc; return s; } public String toString() { String s = ""; for(int i = 0; i < fieldList.size(); i++) { s += fieldString(i) + "\n"; } return s; } }