Coverage Report - org.galagosearch.tupleflow.Parameters
 
Classes in this File Line Coverage Branch Coverage Complexity
Parameters
22%
29/134
5%
2/40
0
Parameters$CharSequenceBuffer
52%
13/25
50%
5/10
0
Parameters$Parser
74%
23/31
71%
10/14
0
Parameters$Value
62%
50/80
50%
14/28
0
Parameters$Variable
0%
0/4
N/A
0
 
 1  
 // BSD License (http://www.galagosearch.org/license)
 2  
 package org.galagosearch.tupleflow;
 3  
 
 4  
 import java.io.File;
 5  
 import java.io.IOException;
 6  
 import java.io.Serializable;
 7  
 import java.io.StringReader;
 8  
 import java.io.StringWriter;
 9  
 import java.util.ArrayList;
 10  
 import java.util.Collections;
 11  
 import java.util.HashMap;
 12  
 import java.util.List;
 13  
 import java.util.Map;
 14  
 import java.util.Stack;
 15  
 import javax.xml.parsers.DocumentBuilderFactory;
 16  
 import javax.xml.parsers.SAXParser;
 17  
 import javax.xml.parsers.SAXParserFactory;
 18  
 import javax.xml.transform.Transformer;
 19  
 import javax.xml.transform.TransformerFactory;
 20  
 import javax.xml.transform.dom.DOMSource;
 21  
 import javax.xml.transform.stream.StreamResult;
 22  
 import org.w3c.dom.Document;
 23  
 import org.w3c.dom.Element;
 24  
 import org.xml.sax.Attributes;
 25  
 import org.xml.sax.InputSource;
 26  
 import org.xml.sax.SAXException;
 27  
 import org.xml.sax.helpers.DefaultHandler;
 28  
 
 29  
 /**
 30  
  * A Parameters object is a hierarchical collection of strings.  TupleFlow
 31  
  * uses it as a convenient way to pass parameters to objects, read and write
 32  
  * parameters from files, and read parameters from the command line.
 33  
  * 
 34  
  * @author trevor
 35  
  */
 36  0
 public class Parameters implements Serializable {
 37  
     public static class Variable implements Serializable {
 38  
         String name;
 39  
 
 40  0
         public Variable(String name) {
 41  0
             this.name = name;
 42  0
         }
 43  
 
 44  
         public String toString(HashMap<String, String> values) {
 45  0
             return values.get(name);
 46  
         }
 47  
     }
 48  
 
 49  
     public static class Value implements Serializable {
 50  
         Map<String, List<Value>> _map;
 51  
         CharSequence _string;
 52  
 
 53  
         /**
 54  
          * Construct a new Value object with nothing in it.
 55  
          */
 56  44
         public Value() {
 57  44
             _map = null;
 58  44
             _string = "";
 59  44
         }
 60  
 
 61  
         /**
 62  
          * Returns true if there is no data in this value object.
 63  
          */
 64  
         public boolean isEmpty() {
 65  4
             return _map == null && _string.equals("");
 66  
         }
 67  
 
 68  
         /**
 69  
          * Creates the map if it is currently null.
 70  
          */
 71  
         private void ensureMap() {
 72  26
             if (_map == null) {
 73  16
                 _map = new HashMap<String, List<Value>>();
 74  
             }
 75  26
         }
 76  
 
 77  
         /**
 78  
          * Ensures that the map exists.  Adds a values array to
 79  
          * the map if there isn't one already.  Returns the values
 80  
          * array corresponding to this key.
 81  
          */
 82  
         private List<Value> ensureKey(String key) {
 83  26
             ensureMap();
 84  26
             List<Value> values = new ArrayList<Value>();
 85  26
             if (!_map.containsKey(key)) {
 86  22
                 _map.put(key, values);
 87  
             } else {
 88  4
                 values = _map.get(key);
 89  
             }
 90  26
             return values;
 91  
         }
 92  
 
 93  
         /**
 94  
          * Add a new child value object.  This is similar to adding a 
 95  
          * child XML element at this point, but without actually putting any
 96  
          * data in that element yet.
 97  
          *
 98  
          * @param key The XML tag/key name of this child value.
 99  
          * @return A new empty child Value object.
 100  
          */
 101  
         public Value add(String key) {
 102  2
             Value result = new Value();
 103  2
             ensureKey(key).add(result);
 104  2
             return result;
 105  
         }
 106  
 
 107  
         public void add(String key, List<Value> values) {
 108  22
             if (key.contains("/")) {
 109  0
                 String fields[] = key.split("/", 2);
 110  0
                 String subKey = fields[1];
 111  0
                 String rootKey = fields[0];
 112  0
                 Value subValue = null;
 113  
 
 114  0
                 if (!containsKey(rootKey)) {
 115  0
                     subValue = add(rootKey);
 116  
                 } else {
 117  0
                     subValue = list(rootKey).get(0);
 118  
                 }
 119  0
                 subValue.add(subKey, values);
 120  0
             } else {
 121  22
                 ensureKey(key).addAll(values);
 122  
             }
 123  22
         }
 124  
 
 125  
         /**
 126  
          * Add a new XML value.  Key may be a simple tag name
 127  
          * or a slash-delimited XML pathname.
 128  
          *
 129  
          * @param key The XML path to the tag this call should modify/add.
 130  
          * @param value The text value to assign to the node specified by the key parameter.
 131  
          */
 132  
         public void add(String key, CharSequence value) {
 133  22
             Value stringValue = new Value();
 134  22
             stringValue._string = value;
 135  22
             List<Value> valueList = Collections.singletonList(stringValue);
 136  
 
 137  22
             add(key, valueList);
 138  22
         }
 139  
 
 140  
         public void set(CharSequence value) {
 141  4
             _map = null;
 142  4
             _string = value;
 143  4
         }
 144  
 
 145  
         public void set(String key, List<Value> values) {
 146  2
             if (key.contains("/")) {
 147  0
                 String fields[] = key.split("/", 2);
 148  0
                 String subKey = fields[1];
 149  0
                 String rootKey = fields[0];
 150  0
                 Value subValue = null;
 151  
 
 152  0
                 if (!containsKey(rootKey)) {
 153  0
                     subValue = add(rootKey);
 154  
                 } else {
 155  0
                     subValue = list(rootKey).get(0);
 156  
                 }
 157  0
                 subValue.add(subKey, values);
 158  0
             } else {
 159  2
                 List<Value> current = ensureKey(key);
 160  2
                 current.clear();
 161  2
                 current.addAll(values);
 162  
             }
 163  2
         }
 164  
 
 165  
         @Override
 166  
         public String toString() {
 167  22
             return _string.toString();
 168  
         }
 169  
 
 170  
         public Map<String, List<Value>> map() {
 171  0
             return _map;
 172  
         }
 173  
 
 174  
         public String get(String key, String def) {
 175  0
             if (containsKey(key)) {
 176  0
                 return get(key);
 177  
             }
 178  0
             return def;
 179  
         }
 180  
 
 181  
         public String get(String key) {
 182  26
             if (key == null || key.length() == 0) {
 183  0
                 return toString();
 184  
             }
 185  26
             List<Value> list = list(key);
 186  
 
 187  26
             if (list == null) {
 188  4
                 throw new IllegalArgumentException("Key '" + key + "' not found.");
 189  
             }
 190  22
             Value first = list.get(0);
 191  22
             return first.toString();
 192  
         }
 193  
 
 194  
         public List<Value> list(String key) {
 195  
             // this key may actually be a path expression.
 196  
             // if so, we consider just the first part, and call that the key
 197  30
             String[] fields = key.split("/", 2);
 198  30
             key = fields[0];
 199  
 
 200  
             // get the appropriate list from the map
 201  30
             List<Value> list = _map.get(key);
 202  
 
 203  
             // if it's not a path, just return what we found.
 204  30
             if (fields.length == 1) {
 205  30
                 return list;
 206  
             } else {
 207  
                 // it's a path, so we descend through the first
 208  
                 // item of this list, then ask for the list corresponding
 209  
                 // to the rest of this path
 210  0
                 String tail = fields[1];
 211  0
                 return list.get(0).list(tail);
 212  
             }
 213  
         }
 214  
 
 215  
         public List<String> stringList(String key) {
 216  0
             List<Value> list = list(key);
 217  0
             ArrayList<String> strings = new ArrayList<String>(list.size());
 218  
 
 219  0
             for (Value value : list) {
 220  0
                 strings.add(value.toString());
 221  
             }
 222  
 
 223  0
             return strings;
 224  
         }
 225  
 
 226  
         public boolean containsKey(String key) {
 227  
             try {
 228  6
                 get(key);
 229  2
             } catch (Exception e) {
 230  2
                 return false;
 231  4
             }
 232  
 
 233  4
             return true;
 234  
         }
 235  
     }
 236  18
     Value _data = new Value();
 237  18
     HashMap<String, String> _variables = new HashMap<String, String>();
 238  
 
 239  
     /**
 240  
      * This class gathers up a stack of CharSequence objects and makes
 241  
      * them look like a single CharSequence.  The reason we do this is so
 242  
      * that we can insert special, mutable CharSequences in here that are used
 243  
      * as parameters.
 244  
      * 
 245  
      * For example, suppose we have an input string like:
 246  
      *      ${path:/Users/trevor/Desktop}.txt
 247  
      * We can make a CharSequenceBuffer = [ MutableCharSequence("path"), ".txt" ]
 248  
      * 
 249  
      * Now, we can go and change the MutableCharSequence later so that parameters work
 250  
      * appropriately.
 251  
      */
 252  8
     public class CharSequenceBuffer implements CharSequence {
 253  8
         ArrayList sequences = new ArrayList();
 254  
 
 255  
         public void add(Object sequence) {
 256  2
             sequences.add(sequence);
 257  2
         }
 258  
 
 259  
         public boolean isStatic() {
 260  2
             for (Object sequence : sequences) {
 261  2
                 boolean isString = (sequence instanceof String);
 262  2
                 if (!isString) {
 263  0
                     return false;
 264  
                 }
 265  2
             }
 266  
 
 267  2
             return true;
 268  
         }
 269  
 
 270  
         @Override
 271  
         public String toString() {
 272  2
             StringBuilder builder = new StringBuilder();
 273  
 
 274  2
             for (Object sequence : sequences) {
 275  2
                 builder.append(sequence.toString());
 276  
             }
 277  
 
 278  2
             return builder.toString();
 279  
         }
 280  
 
 281  
         public String toString(HashMap<String, String> values) {
 282  0
             StringBuilder builder = new StringBuilder();
 283  
 
 284  0
             for (Object sequence : sequences) {
 285  0
                 if (sequence instanceof Variable) {
 286  0
                     Variable v = (Variable) sequence;
 287  0
                     builder.append(_variables.get(v.name));
 288  0
                 } else {
 289  0
                     builder.append(sequence.toString());
 290  
                 }
 291  
             }
 292  
 
 293  0
             return builder.toString();
 294  
         }
 295  
 
 296  
         public int length() {
 297  0
             return toString().length();
 298  
         }
 299  
 
 300  
         public char charAt(int index) {
 301  0
             return toString().charAt(index);
 302  
         }
 303  
 
 304  
         public CharSequence subSequence(int start, int end) {
 305  0
             return toString().subSequence(start, end);
 306  
         }
 307  
     }
 308  
 
 309  2
     public class Parser extends DefaultHandler {
 310  2
         CharSequenceBuffer writer = new CharSequenceBuffer();
 311  2
         Stack<Value> contexts = new Stack();
 312  
         Value current;
 313  
 
 314  
         @Override
 315  
         public void characters(char[] data, int start, int length) throws SAXException {
 316  2
             writer.add(new String(data, start, length));
 317  2
         }
 318  
 
 319  
         @Override
 320  
         public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
 321  4
             if (qName.equals("parameters") && current == null) {
 322  2
                 contexts.push(_data);
 323  2
                 current = _data;
 324  2
             } else if (current == null) {
 325  0
                 throw new SAXException("Found an outermost tag that was not 'parameters': " + qName);
 326  2
             } else if (qName.equals("variable")) {
 327  0
                 String variableName = attributes.getValue("name");
 328  0
                 String variableDefault = attributes.getValue("default");
 329  0
                 Variable variable = new Variable(variableName);
 330  
 
 331  0
                 _variables.put(variableName, variableDefault);
 332  0
                 writer.add(variable);
 333  0
             } else {
 334  2
                 writer = new CharSequenceBuffer();
 335  2
                 current = current.add(qName);
 336  2
                 contexts.push(current);
 337  
             }
 338  4
         }
 339  
 
 340  
         @Override
 341  
         public void endElement(String uri, String localName, String qName) throws SAXException {
 342  
             // if there are no variables in there, store as a String
 343  4
             if (current.isEmpty()) {
 344  2
                 if (writer.isStatic()) {
 345  2
                     current.set(writer.toString());
 346  
                 } else {
 347  0
                     current.set(writer);            // make a new sequence
 348  
                 }
 349  
             }
 350  4
             writer = new CharSequenceBuffer();
 351  4
             contexts.pop();
 352  
 
 353  4
             if (!contexts.empty()) {
 354  2
                 current = contexts.peek();
 355  
             } else {
 356  2
                 current = null;
 357  
             }
 358  4
         }
 359  
     }
 360  
 
 361  18
     public Parameters() {
 362  18
     }
 363  
 
 364  
     /**
 365  
      * Creates a Parameters object using XML data.
 366  
      * 
 367  
      * @param xmlData
 368  
      * @throws java.io.IOException
 369  
      */
 370  
     
 371  0
     public Parameters(byte[] xmlData) throws IOException {
 372  0
         parse(xmlData);
 373  0
     }
 374  
 
 375  
     /**
 376  
      * Creates a Parameters object using the contents of an XML file.
 377  
      * 
 378  
      * @param f The file to grab XML data from.
 379  
      * @throws java.io.IOException
 380  
      */
 381  0
     public Parameters(File f) throws IOException {
 382  0
         parse(f.getPath());
 383  0
     }
 384  
 
 385  0
     public Parameters(Value v) {
 386  0
         _data = v;
 387  0
     }
 388  
 
 389  
     /**
 390  
      * Creates a Parameters object based on a key/value map.
 391  
      * 
 392  
      * @param map
 393  
      */
 394  0
     public Parameters(Map<String, String> map) {
 395  0
         _data = new Value();
 396  
 
 397  0
         for (String key : map.keySet()) {
 398  0
             _data.add(key, map.get(key));
 399  
         }
 400  0
     }
 401  
 
 402  
     /**
 403  
      * <p>
 404  
      * Fills in a Parameters object based on command line flags.  The
 405  
      * flag format is like this:
 406  
      * </p>
 407  
      * 
 408  
      * <pre>
 409  
      * --a.b.c=d
 410  
      * </pre>
 411  
      * 
 412  
      * <p>
 413  
      * This is equivalent to:
 414  
      * <pre>
 415  
      * &gt;a&lt;&gt;b&lt;&gt;c&ltd&gt;/c&lt;&gt;/b&lt;&gt;/a&lt;
 416  
      * </pre>
 417  
      * </p>
 418  
      * 
 419  
      * <p>A flag has no equals sign, like this one:</p>
 420  
      * <pre>
 421  
      * --a.b.c
 422  
      * </pre>
 423  
      * <p>is equivalent to:</p>
 424  
      * <pre>
 425  
      * --a.b.c=True
 426  
      * </pre>
 427  
      * 
 428  
      * <p>Any argument that doesn't begin with a dash is assumed to be the filename
 429  
      * of an XML file.  The data from that file data will be added to this object.</p>
 430  
      * 
 431  
      * @param args
 432  
      * @throws java.io.IOException
 433  
      */
 434  0
     public Parameters(String[] args) throws IOException {
 435  0
         for (String arg : args) {
 436  0
             if (arg.startsWith("-")) {
 437  
                 // this is a command-line argument, not a parameters file
 438  0
                 int startIndex = 1;
 439  
 
 440  
                 // skip any number of leading dashes
 441  0
                 while (arg.length() > startIndex && arg.charAt(startIndex) == '-') {
 442  0
                     startIndex++;                // split on equals (format is --argument=value, or just --argument)
 443  
                 }
 444  0
                 String[] fields = arg.substring(startIndex).split("=");
 445  
                 // on the command line, we allow either slashes or dots as the key;
 446  
                 // like --corpus/path=collection or --corpus.path=collection,
 447  
                 // but internal code requires slashes.
 448  0
                 String key = fields[0].replace('.', '/');
 449  
                 String value;
 450  
 
 451  
                 // if there's no explicit value, assume they just mean 'true'
 452  0
                 if (fields.length == 1) {
 453  0
                     value = "True";
 454  
                 } else {
 455  0
                     value = fields[1];
 456  
                 }
 457  
 
 458  0
                 _data.add(key, value);
 459  0
             } else {
 460  
                 // it's a file, so parse it
 461  0
                 parse(arg);
 462  
             }
 463  
         }
 464  0
     }
 465  
 
 466  
     public Parser getParseHandler() {
 467  0
         return new Parser();
 468  
     }
 469  
 
 470  
     /**
 471  
      * Gets the value for this key.  You can retrieve a nested value
 472  
      * using a path syntax, e.g. get("a/b/c").
 473  
      * 
 474  
      * @param key
 475  
      */
 476  
     
 477  
     public String get(String key) {
 478  20
         return _data.get(key);
 479  
     }
 480  
     
 481  
     /**
 482  
      * Gets the value for this key.  Returns def if key isn't found.
 483  
      * 
 484  
      * @param key
 485  
      * @param def
 486  
      */
 487  
 
 488  
     public String get(String key, String def) {
 489  
         try {
 490  4
             return get(key);
 491  0
         } catch (Exception e) {
 492  0
             return def;
 493  
         }
 494  
     }
 495  
     
 496  
     /**
 497  
      * Gets the value for key.  If key is not found, returns the
 498  
      * value for "default".  If "default" isn't found, returns def.
 499  
      * 
 500  
      * @param key
 501  
      * @param def
 502  
      * @return
 503  
      */
 504  
 
 505  
     public String getAsDefault(String key, String def) {
 506  0
         return get(get(key), get("default", def));
 507  
     }
 508  
 
 509  
     /**
 510  
      * Gets the value for this key, but returning a default value if the
 511  
      * key doesn't exist in the object.  This method tries to convert the
 512  
      * value to boolean.  Values starting with 'T', 'Y', or a non-zero number
 513  
      * are considered to be true; everything else is false.
 514  
      * 
 515  
      * @param key
 516  
      * @param def
 517  
      * @return
 518  
      */
 519  
     public boolean get(String key, boolean def) {
 520  
         try {
 521  2
             String result = get(key);
 522  2
             char c = result.charAt(0);
 523  
 
 524  
             // True, true, Yes, yes, non-zero
 525  2
             if (c == 'T' || c == 't' || c == 'Y' || c == 'y' || (Character.isDigit(c) && c != '0')) {
 526  2
                 return true;
 527  
             }
 528  0
             return false;
 529  0
         } catch (Exception e) {
 530  0
             return def;
 531  
         }
 532  
     }
 533  
 
 534  
     public boolean getAsDefault(String key, boolean def) {
 535  0
         return get(key, get("default", def));
 536  
     }
 537  
 
 538  
     public long get(String key, long def) {
 539  
         try {
 540  4
             String result = get(key);
 541  2
             return Long.parseLong(result);
 542  2
         } catch (Exception e) {
 543  2
             return def;
 544  
         }
 545  
     }
 546  
 
 547  
     public long getAsDefault(String key, long def) {
 548  0
         return get(key, get("default", def));
 549  
     }
 550  
 
 551  
     public double get(String key, double def) {
 552  
         try {
 553  0
             String result = get(key);
 554  0
             return Double.parseDouble(result);
 555  0
         } catch (Exception e) {
 556  0
             return def;
 557  
         }
 558  
     }
 559  
 
 560  
     public double getAsDefault(String key, double def) {
 561  0
         return get(get(key), get("default", def));
 562  
     }
 563  
 
 564  
     public void copy(String key, Parameters other) {
 565  0
         List<Value> values = other.list(key);
 566  
 
 567  0
         if (values == null) {
 568  0
             return;
 569  
         }
 570  0
         if (_data.containsKey(key)) {
 571  0
             _data.list(key).addAll(values);
 572  
         } else {
 573  0
             _data.add(key, values);
 574  
         }
 575  0
     }
 576  
 
 577  
     public void copy(Parameters other) {
 578  0
         if (other._data == null || other._data._map == null) {
 579  0
             return;
 580  
         }
 581  0
         for (String key : other._data._map.keySet()) {
 582  0
             copy(key, other);
 583  
         }
 584  0
     }
 585  
 
 586  
     @Override
 587  
     public Parameters clone() {
 588  0
         Parameters p = new Parameters();
 589  0
         p.copy(this);
 590  0
         return p;
 591  
     }
 592  
 
 593  
     public void add(String key, List<Value> values) {
 594  0
         _data.add(key, values);
 595  0
     }
 596  
 
 597  
     public void add(String key, String value) {
 598  22
         _data.add(key, value);
 599  22
     }
 600  
 
 601  
     public void set(String key, List<Value> values) {
 602  0
         _data.set(key, values);
 603  0
     }
 604  
 
 605  
     public void set(String key, String value) {
 606  2
         Value stringValue = new Value();
 607  2
         stringValue.set(value);
 608  2
         List<Value> values = Collections.singletonList(stringValue);
 609  2
         _data.set(key, values);
 610  2
     }
 611  
 
 612  
     public List<Value> list(String key) {
 613  4
         return _data.list(key);
 614  
     }
 615  
 
 616  
     public List<String> stringList(String key) {
 617  0
         return _data.stringList(key);
 618  
     }
 619  
 
 620  
     public Value value() {
 621  0
         return _data;
 622  
     }
 623  
 
 624  
     public boolean containsKey(String key) {
 625  6
         return _data.containsKey(key);
 626  
     }
 627  
 
 628  
     public void parse(String filename) throws IOException {
 629  
         try {
 630  0
             SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
 631  0
             parser.parse(new File(filename), new Parser());
 632  0
         } catch (Exception e) {
 633  0
             throw new IOException(e.toString());
 634  0
         }
 635  0
     }
 636  
 
 637  
     public void parse(byte[] xmlData) throws IOException {
 638  
         try {
 639  2
             SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
 640  2
             String xmlText = new String(xmlData);
 641  2
             StringReader reader = new StringReader(xmlText);
 642  2
             parser.parse(new InputSource(reader), new Parser());
 643  0
         } catch (Exception e) {
 644  0
             throw new IOException(e.toString());
 645  2
         }
 646  2
     }
 647  
 
 648  
     public void write(Value value, Document document, Element element) {
 649  0
         if (value._map == null) {
 650  0
             element.appendChild(document.createTextNode(value.toString()));
 651  
         } else {
 652  0
             for (String key : value._map.keySet()) {
 653  0
                 for (Value childValue : value._map.get(key)) {
 654  0
                     Element childElement = document.createElement(key);
 655  0
                     write(childValue, document, childElement);
 656  0
                     element.appendChild(childElement);
 657  0
                 }
 658  
             }
 659  
         }
 660  0
     }
 661  
 
 662  
     public void write(StreamResult result) throws IOException {
 663  
         try {
 664  0
             Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().
 665  
                     newDocument();
 666  0
             Element root = document.createElement("parameters");
 667  0
             write(_data, document, root);
 668  0
             document.appendChild(root);
 669  
 
 670  0
             Transformer identity = TransformerFactory.newInstance().newTransformer();
 671  0
             identity.transform(new DOMSource(document), result);
 672  0
         } catch (Exception e) {
 673  0
             throw new IOException(e.toString());
 674  0
         }
 675  0
     }
 676  
 
 677  
     public void write(String filename) throws IOException {
 678  
         try {
 679  0
             write(new StreamResult(new File(filename)));
 680  0
         } catch (Exception e) {
 681  0
             throw new IOException(e.toString());
 682  0
         }
 683  0
     }
 684  
 
 685  
     @Override
 686  
     public String toString() {
 687  0
         StringWriter writer = new StringWriter();
 688  
         try {
 689  0
             write(new StreamResult(writer));
 690  0
         } catch (Exception e) {
 691  0
             return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Exception: " + e.toString() + " --><parameters/>\n";
 692  0
         }
 693  0
         String result = writer.toString();
 694  0
         return result;
 695  
     }
 696  
 
 697  
     public boolean isEmpty() {
 698  0
         return _data.isEmpty();
 699  
     }
 700  
 }