001: //
002: // Copyright (C) 2005 United States Government as represented by the
003: // Administrator of the National Aeronautics and Space Administration
004: // (NASA). All Rights Reserved.
005: //
006: // This software is distributed under the NASA Open Source Agreement
007: // (NOSA), version 1.3. The NOSA has been approved by the Open Source
008: // Initiative. See the file NOSA-1.3-JPF at the top of the distribution
009: // directory tree for the complete NOSA document.
010: //
011: // THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY
012: // KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT
013: // LIMITED TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO
014: // SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR
015: // A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT
016: // THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT
017: // DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE.
018: //
019: package gov.nasa.jpf;
020:
021: import java.io.File;
022: import java.io.FileInputStream;
023: import java.io.IOException;
024: import java.io.InputStream;
025: import java.io.PrintWriter;
026: import java.io.StringWriter;
027: import java.lang.reflect.Constructor;
028: import java.lang.reflect.InvocationTargetException;
029: import java.net.URL;
030: import java.util.ArrayList;
031: import java.util.Enumeration;
032: import java.util.Iterator;
033: import java.util.Properties;
034: import java.util.TreeSet;
035: import java.util.logging.Level;
036: import java.util.logging.Logger;
037:
038: /**
039: * class that encapsulates property-based JPF configuration. This is mainly an
040: * associative array with various typed accessors, and a structured
041: * initialization process. This implementation has the design constraint that it
042: * does not promote symbolic information to concrete types, which means that
043: * frequently accessed data should be promoted and cached in client classes.
044: * This in turn means we assume the data is not going to change at runtime.
045: * Major motivation for this mechanism is to avoid 'Option' classes that have
046: * concrete type fields, and hence are structural bottlenecks, i.e. every
047: * parameterized user extension (Heuristics, Scheduler etc.) require to update
048: * this single class. Note that Config is also not thread safe with respect to
049: * retrieving exceptions that occurrred during instantiation
050: *
051: * Another important caveat for both implementation and usage of Config is that
052: * it is supposed to be our master configuration mechanism, i.e. it is also used
053: * to configure other core services like logging. This means that Config
054: * initialization should not depend on these services. Initialization has to
055: * return at all times, recording potential problems for later handling. This is
056: * why we have to keep the Config data model and initialization fairly simple
057: * and robust.
058: */
059: public class Config extends Properties {
060:
061: static final String TARGET_KEY = "target";
062:
063: static final String TARGET_ARGS_KEY = "target_args";
064:
065: /**
066: * this class wraps the various exceptions we might encounter esp. during
067: * reflection instantiation
068: */
069: public class Exception extends java.lang.Exception {
070: public Exception(String msg) {
071: super (msg);
072: }
073:
074: public Exception(String msg, Throwable cause) {
075: super (msg, cause);
076: }
077:
078: public Exception(String key, Class cls, String failure) {
079: super ("error instantiating class " + cls.getName()
080: + " for entry \"" + key + "\":" + failure);
081: }
082:
083: public Exception(String key, Class cls, String failure,
084: Throwable cause) {
085: this (key, cls, failure);
086: initCause(cause);
087: }
088:
089: public String toString() {
090: StringBuffer sb = new StringBuffer();
091: sb.append("JPF configuration error: ");
092: sb.append(getMessage());
093:
094: return sb.toString();
095: }
096: }
097:
098: String fileName;
099:
100: Object source;
101:
102: boolean gotProperties;
103:
104: /**
105: * all arguments that are not <key>= <value>pairs
106: */
107: String[] freeArgs;
108:
109: /**
110: * this is our internal defaults ctor
111: */
112: private Config(String alternatePath, Class codeBase) {
113: gotProperties = loadFile("default.properties", alternatePath,
114: codeBase);
115: }
116:
117: public Config(String[] args, String fileName, String alternatePath,
118: Class codeBase) {
119: super (new Config(alternatePath,
120: (codeBase == null) ? codeBase = getCallerClass(1)
121: : codeBase));
122:
123: this .fileName = fileName;
124: gotProperties = loadFile(fileName, alternatePath, codeBase);
125:
126: if (args != null) {
127: processArgs(args);
128: }
129: normalizeValues();
130: }
131:
132: public boolean gotDefaultProperties() {
133: if (defaults != null && defaults instanceof Config) {
134: return ((Config) defaults).gotProperties();
135: }
136: return false;
137: }
138:
139: public boolean gotProperties() {
140: return gotProperties;
141: }
142:
143: public String getFileName() {
144: return fileName;
145: }
146:
147: public String getSourceName() {
148: if (source == null) {
149: return null;
150: } else if (source instanceof File) {
151: return ((File) source).getAbsolutePath();
152: } else if (source instanceof URL) {
153: return source.toString();
154: } else {
155: return source.toString();
156: }
157: }
158:
159: public String getDefaultsSourceName() {
160: if ((defaults != null) && (defaults instanceof Config)) {
161: return ((Config) defaults).getSourceName();
162: } else {
163: return null;
164: }
165: }
166:
167: public Object getSource() {
168: return source;
169: }
170:
171: public String[] getArgs() {
172: return freeArgs;
173: }
174:
175: public void throwException(String msg) throws Exception {
176: throw new Exception(msg);
177: }
178:
179: /**
180: * find callers class
181: *
182: * @param up -
183: * levels upwards from our caller (NOT counting ourselves)
184: * @return caller class, null if illegal 'up' value
185: */
186: public static Class getCallerClass(int up) {
187: int idx = up + 1; // don't count ourselves
188:
189: StackTraceElement[] st = (new Throwable()).getStackTrace();
190: if ((up < 0) || (idx >= st.length)) {
191: return null;
192: } else {
193: try {
194: return Class.forName(st[idx].getClassName());
195: } catch (Throwable t) {
196: return null;
197: }
198: }
199: }
200:
201: boolean loadFile(String fileName, String alternatePath,
202: Class codeBase) {
203: InputStream is = null;
204:
205: try {
206: // first, try to load from a file
207: File f = new File(fileName);
208: if (!f.exists()) {
209: // Ok, try alternatePath, if fileName wasn't absolute
210: if (!f.isAbsolute() && (alternatePath != null)) {
211: f = new File(alternatePath, fileName);
212: }
213: }
214:
215: if (f.exists()) {
216: source = f;
217: is = new FileInputStream(f);
218: } else {
219: // if there is no file, try to load as a resource (jar)
220: Class clazz = (codeBase != null) ? codeBase
221: : Config.class;
222: is = clazz.getResourceAsStream(fileName);
223: if (is != null) {
224: source = clazz.getResource(fileName); // a URL
225: }
226: }
227:
228: if (is != null) {
229: load(is);
230: return true;
231: }
232: } catch (IOException iex) {
233: return false;
234: }
235:
236: return false;
237: }
238:
239: /**
240: * extract all "+ <key>= <val>" parameters, store/overwrite them in our
241: * dictionary, collect all other parameters in a String array
242: *
243: * @param args -
244: * array of String parameters to process
245: */
246: void processArgs(String[] args) {
247: int i;
248: String arg;
249: ArrayList list = new ArrayList();
250:
251: for (i = 0; i < args.length; i++) {
252: String a = args[i];
253: if (a != null) {
254: if (a.charAt(0) == '+') {
255: int idx = a.indexOf("=");
256: if (idx > 0) {
257: setProperty(a.substring(1, idx), a
258: .substring(idx + 1));
259: } else {
260: setProperty(a.substring(1), "");
261: }
262: } else {
263: list.add(a);
264: }
265: }
266: }
267:
268: int n = list.size();
269: freeArgs = new String[n];
270: for (i = 0; i < n; i++) {
271: freeArgs[i] = (String) list.get(i);
272: }
273: }
274:
275: /**
276: * return the index of the first free argument that does not start with an
277: * hyphen
278: */
279: public int getNonOptionArgIndex() {
280: if ((freeArgs == null) || (freeArgs.length == 0))
281: return -1;
282:
283: for (int i = 0; i < freeArgs.length; i++) {
284: String a = freeArgs[i];
285: if (a != null) {
286: char c = a.charAt(0);
287: if (c != '-') {
288: return i;
289: }
290: }
291: }
292:
293: return -1;
294: }
295:
296: /**
297: * return the first non-option freeArg, or 'null' if there is none (usually
298: * denotes the application to start)
299: */
300: public String getTargetArg() {
301: int i = getNonOptionArgIndex();
302: if (i < 0) {
303: return getString(TARGET_KEY);
304: } else {
305: return freeArgs[i];
306: }
307: }
308:
309: /**
310: * return all args that follow the first non-option freeArgs (usually denotes
311: * the parametsr to pass to the application to start)
312: */
313: public String[] getTargetArgParameters() {
314: int i = getNonOptionArgIndex();
315: if (i >= freeArgs.length - 1) {
316: String[] a = getStringArray(TARGET_ARGS_KEY);
317: if (a != null) {
318: return a;
319: } else {
320: return new String[0];
321: }
322: } else {
323: int n = freeArgs.length - (i + 1);
324: String[] a = new String[n];
325: System.arraycopy(freeArgs, i + 1, a, 0, n);
326: return a;
327: }
328: }
329:
330: public String getArg(int i) {
331: if (freeArgs == null)
332: return null;
333: if (freeArgs.length - 1 < i)
334: return null;
335: if (i < 0)
336: return null;
337:
338: return freeArgs[i];
339: }
340:
341: /**
342: * turn standard type values (boolean etc.) into common formats
343: * ("true"/"false" for booleans)
344: */
345: void normalizeValues() {
346: for (Enumeration keys = keys(); keys.hasMoreElements();) {
347: String k = (String) keys.nextElement();
348: String v = getProperty(k);
349:
350: // trim heading and trailing blanks (at least Java 1.4.2 does not take care of trailing blanks)
351: String v0 = v;
352: v = v.trim();
353: if (v != v0) {
354: put(k, v);
355: }
356:
357: if ("true".equalsIgnoreCase(v) || "t".equalsIgnoreCase(v)
358: || "yes".equalsIgnoreCase(v)
359: || "y".equalsIgnoreCase(v)) {
360: put(k, "true");
361: } else if ("false".equalsIgnoreCase(v)
362: || "f".equalsIgnoreCase(v)
363: || "no".equalsIgnoreCase(v)
364: || "n".equalsIgnoreCase(v)) {
365: put(k, "false");
366: }
367: }
368: }
369:
370: public boolean getBoolean(String key) {
371: String v = getProperty(key);
372: return (v != null) && ("true".equals(v));
373: }
374:
375: public boolean getBoolean(String key, boolean def) {
376: String v = getProperty(key);
377: if (v != null) {
378: return ("true".equals(v));
379: } else {
380: return def;
381: }
382: }
383:
384: public int[] getIntArray(String key) throws Exception {
385: String v = getProperty(key);
386:
387: if (v != null) {
388: String[] sa = v.split("[:;, ]+");
389: int[] a = new int[sa.length];
390: int i = 0;
391: try {
392: for (; i < sa.length; i++) {
393: a[i] = Integer.parseInt(sa[i]);
394: }
395: return a;
396: } catch (NumberFormatException nfx) {
397: throw new Exception("illegal int[] element in '" + key
398: + "' = \"" + sa[i] + '"');
399: }
400: } else {
401: return null;
402: }
403: }
404:
405: public int getInt(String key) {
406: return getInt(key, 0);
407: }
408:
409: public int getInt(String key, int defValue) {
410: String v = getProperty(key);
411: if (v != null) {
412: try {
413: return Integer.parseInt(v);
414: } catch (NumberFormatException nfx) {
415: return defValue;
416: }
417: }
418:
419: return defValue;
420: }
421:
422: public long getLong(String key) {
423: return getLong(key, 0L);
424: }
425:
426: public long getLong(String key, long defValue) {
427: String v = getProperty(key);
428: if (v != null) {
429: try {
430: return Long.parseLong(v);
431: } catch (NumberFormatException nfx) {
432: return defValue;
433: }
434: }
435:
436: return defValue;
437: }
438:
439: public long[] getLongArray(String key) throws Exception {
440: String v = getProperty(key);
441:
442: if (v != null) {
443: String[] sa = v.split("[:;, ]+");
444: long[] a = new long[sa.length];
445: int i = 0;
446: try {
447: for (; i < sa.length; i++) {
448: a[i] = Long.parseLong(sa[i]);
449: }
450: return a;
451: } catch (NumberFormatException nfx) {
452: throw new Exception("illegal long[] element in " + key
453: + " = " + sa[i]);
454: }
455: } else {
456: return null;
457: }
458: }
459:
460: public double getDouble(String key) {
461: return getDouble(key, 0.0);
462: }
463:
464: public double getDouble(String key, double defValue) {
465: String v = getProperty(key);
466: if (v != null) {
467: try {
468: return Double.parseDouble(v);
469: } catch (NumberFormatException nfx) {
470: return defValue;
471: }
472: }
473:
474: return defValue;
475: }
476:
477: public double[] getDoubleArray(String key) throws Exception {
478: String v = getProperty(key);
479:
480: if (v != null) {
481: String[] sa = v.split("[:;, ]+");
482: double[] a = new double[sa.length];
483: int i = 0;
484: try {
485: for (; i < sa.length; i++) {
486: a[i] = Double.parseDouble(sa[i]);
487: }
488: return a;
489: } catch (NumberFormatException nfx) {
490: throw new Exception("illegal double[] element in "
491: + key + " = " + sa[i]);
492: }
493: } else {
494: return null;
495: }
496: }
497:
498: public String getString(String key) {
499: return getProperty(key);
500: }
501:
502: public String getString(String key, String defValue) {
503: String s = getProperty(key);
504: if (s != null) {
505: return s;
506: } else {
507: return defValue;
508: }
509: }
510:
511: /**
512: * same as getString(), except of that we look for '${ <key>}' patterns, and
513: * replace them with values if we find corresponding keys. Expansion is not
514: * done recursively (but could be)
515: */
516: public String getExpandedString(String key) {
517: int i, j = 0;
518: String s = getString(key);
519: if (s == null || s.length() == 0) {
520: return s;
521: }
522:
523: while ((i = s.indexOf("${", j)) >= 0) {
524: if ((j = s.indexOf('}', i)) > 0) {
525: String k = s.substring(i + 2, j);
526: String v = getString(k, "");
527: if (v != null) {
528: s = s.substring(0, i) + v
529: + s.substring(j + 1, s.length());
530: j = i + v.length();
531: } else {
532: s = s.substring(0, i)
533: + s.substring(j + 1, s.length());
534: j = i;
535: }
536: }
537: }
538:
539: return s;
540: }
541:
542: /**
543: * return memory size in bytes, or 'defValue' if not in dictionary. Encoding
544: * can have a 'M' or 'k' postfix, values have to be positive integers (decimal
545: * notation)
546: */
547: public long getMemorySize(String key, long defValue) {
548: String v = getProperty(key);
549: long sz = defValue;
550:
551: if (v != null) {
552: int n = v.length() - 1;
553: try {
554: char c = v.charAt(n);
555:
556: if ((c == 'M') || (c == 'm')) {
557: sz = Long.parseLong(v.substring(0, n)) << 20;
558: } else if ((c == 'K') || (c == 'k')) {
559: sz = Long.parseLong(v.substring(0, n)) << 10;
560: } else {
561: sz = Long.parseLong(v);
562: }
563:
564: } catch (NumberFormatException nfx) {
565: return defValue;
566: }
567: }
568:
569: return sz;
570: }
571:
572: public String[] getStringArray(String key) {
573: String v = getProperty(key);
574: if (v != null) {
575: return v.split("[:;, ]+");
576: }
577:
578: return null;
579: }
580:
581: public Class getClass(String key) throws Exception {
582: String v = getProperty(key);
583: if ((v != null) && (v.length() > 0)) {
584: try {
585: return Class.forName(v);
586: } catch (ClassNotFoundException cfx) {
587: throw new Exception("class not found " + v);
588: } catch (ExceptionInInitializerError ix) {
589: throw new Exception("class initialization of " + v
590: + " failed: " + ix, ix);
591: }
592: }
593:
594: return null;
595: }
596:
597: public Class getEssentialClass(String key) throws Exception {
598: Class cls = getClass(key);
599: if (cls == null) {
600: throw new Exception("no classname entry for: \"" + key
601: + "\"");
602: }
603:
604: return cls;
605: }
606:
607: public Class[] getClasses(String key) throws Exception {
608: String[] v = getStringArray(key);
609: if (v != null) {
610: int n = v.length;
611: Class[] a = new Class[n];
612: for (int i = 0; i < n; i++) {
613: try {
614: a[i] = Class.forName(v[i]);
615: } catch (ClassNotFoundException cnfx) {
616: throw new Exception("class not found " + v[i]);
617: } catch (ExceptionInInitializerError ix) {
618: throw new Exception("class initialization of "
619: + v[i] + " failed: " + ix, ix);
620: }
621: }
622:
623: return a;
624: }
625:
626: return null;
627: }
628:
629: public Object[] getInstances(String key, Class type)
630: throws Exception {
631: Class[] c = getClasses(key);
632: if (c != null) {
633: Class[] argTypes = { Config.class };
634: Object[] args = { this };
635: Object[] a = new Object[c.length];
636:
637: for (int i = 0; i < c.length; i++) {
638: a[i] = getInstance(key, c[i], type, argTypes, args);
639: }
640:
641: return a;
642: }
643:
644: return null;
645: }
646:
647: public Object getInstance(String key, Class type) throws Exception {
648: Class[] argTypes = { Config.class };
649: Object[] args = { this };
650:
651: return getInstance(key, type, argTypes, args);
652: }
653:
654: public Object getInstance(String key, Class type, Class[] argTypes,
655: Object[] args) throws Exception {
656: Class cls = getClass(key);
657: if (cls != null) {
658: return getInstance(key, cls, type, argTypes, args);
659: } else {
660: return null;
661: }
662: }
663:
664: public Object getEssentialInstance(String key, Class type)
665: throws Exception {
666: Class[] argTypes = { Config.class };
667: Object[] args = { this };
668: return getEssentialInstance(key, type, argTypes, args);
669: }
670:
671: public Object getEssentialInstance(String key, Class type,
672: Class[] argTypes, Object[] args) throws Exception {
673: Class cls = getEssentialClass(key);
674: return getInstance(key, cls, type, argTypes, args);
675: }
676:
677: /**
678: * this is our private instantiation workhorse try to instantiate an object of
679: * class 'cls' by using the following ordered set of ctors 1. <cls>(
680: * <argTypes>) 2. <cls>(Config) 3. <cls>() if all of that fails, or there was
681: * a 'type' provided the instantiated object does not comply with, return null
682: */
683: Object getInstance(String key, Class cls, Class type,
684: Class[] argTypes, Object[] args) throws Exception {
685: Object o = null;
686: Constructor ctor = null;
687:
688: if (cls == null) {
689: return null;
690: }
691:
692: do {
693: try {
694: ctor = cls.getConstructor(argTypes);
695: o = ctor.newInstance(args);
696: } catch (NoSuchMethodException nmx) {
697: if (argTypes.length >= 1) {
698: if (argTypes[0] != Config.class) {
699: // fallback 1: try a single Config param
700: argTypes = new Class[1];
701: argTypes[0] = Config.class;
702: args = new Object[1];
703: args[0] = this ;
704: } else {
705: // fallback 2: try the default ctor
706: argTypes = new Class[0];
707: args = new Object[0];
708: }
709: } else {
710: // Ok, there is no suitable ctor, bail out
711: throw new Exception(key, cls,
712: "no suitable ctor found");
713: }
714: } catch (IllegalAccessException iacc) {
715: throw new Exception(key, cls,
716: "\n> ctor not accessible: "
717: + getMethodSignature(ctor));
718: } catch (IllegalArgumentException iarg) {
719: throw new Exception(key, cls,
720: "\n> illegal constructor arguments: "
721: + getMethodSignature(ctor));
722: } catch (InvocationTargetException ix) {
723: Throwable tx = ix.getTargetException();
724: if (tx instanceof Config.Exception) {
725: throw new Exception(tx.getMessage()
726: + "\n> used within \"" + key
727: + "\" instantiation of " + cls);
728: } else {
729: throw new Exception(key, cls, "\n> exception in "
730: + getMethodSignature(ctor) + ":\n>> " + tx,
731: tx);
732: }
733: } catch (InstantiationException ivt) {
734: throw new Exception(key, cls,
735: "\n> abstract class cannot be instantiated");
736: } catch (ExceptionInInitializerError eie) {
737: throw new Exception(key, cls,
738: "\n> static initialization failed:\n>> "
739: + eie.getException(), eie
740: .getException());
741: }
742: } while (o == null);
743:
744: // check type
745: if ((type != null) && !type.isInstance(o)) {
746: throw new Exception(key, cls, "\n> instance not of type: "
747: + type.getName());
748: }
749:
750: return o;
751: }
752:
753: String getMethodSignature(Constructor ctor) {
754: StringBuffer sb = new StringBuffer();
755: sb.append(ctor.getName());
756: sb.append('(');
757: Class[] argTypes = ctor.getParameterTypes();
758: for (int i = 0; i < argTypes.length; i++) {
759: if (i > 0) {
760: sb.append(',');
761: }
762: sb.append(argTypes[i].getName());
763: }
764: sb.append(')');
765: return sb.toString();
766: }
767:
768: /**
769: * check if any of the freeArgs matches a regular expression
770: *
771: * @param regex -
772: * regular expression to check for
773: * @return true if found, false if not found or no freeArgs
774: */
775: public boolean hasArg(String regex) {
776: if (freeArgs == null) {
777: return false;
778: }
779:
780: for (int i = 0; i < freeArgs.length; i++) {
781: if (freeArgs[i].matches(regex)) {
782: return true;
783: }
784: }
785:
786: return false;
787: }
788:
789: public boolean hasValue(String key) {
790: String v = getProperty(key);
791: return ((v != null) && (v.length() > 0));
792: }
793:
794: public boolean hasValueIgnoreCase(String key, String value) {
795: String v = getProperty(key);
796: if (v != null) {
797: return v.equalsIgnoreCase(value);
798: }
799:
800: return false;
801: }
802:
803: public int getChoiceIndexIgnoreCase(String key, String[] choices) {
804: String v = getProperty(key);
805:
806: if ((v != null) && (choices != null)) {
807: for (int i = 0; i < choices.length; i++) {
808: if (v.equalsIgnoreCase(choices[i])) {
809: return i;
810: }
811: }
812: }
813:
814: return -1;
815: }
816:
817: public void print(PrintWriter pw) {
818: pw.println("----------- dictionary contents");
819:
820: // just how much do you have to do to get a sorted printout :(
821: TreeSet kset = new TreeSet();
822: for (Enumeration e = propertyNames(); e.hasMoreElements();) {
823: kset.add(e.nextElement());
824: }
825: for (Iterator it = kset.iterator(); it.hasNext();) {
826: String key = (String) it.next();
827: String val = getExpandedString(key);
828: pw.print(key);
829: pw.print(" = ");
830: pw.println(val);
831: }
832:
833: if ((freeArgs != null) && (freeArgs.length > 0)) {
834: pw.println("----------- free arguments");
835: for (int i = 0; i < freeArgs.length; i++) {
836: pw.println(freeArgs[i]);
837: }
838: }
839:
840: pw.flush();
841: }
842:
843: public void printStatus(Logger log) {
844: log
845: .config("configuration initialized from: "
846: + getSourceName());
847:
848: Config def = (Config) defaults;
849: if (def.source == null) {
850: log.warning("no defaults.properties found");
851: } else {
852: log.config("default configuration initialized from: "
853: + def.getSourceName());
854: }
855: }
856: }
|