2 * Copyright (c) 2003-2005 The BISON Project
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License version 2 as
6 * published by the Free Software Foundation.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 package peersim.config;
21 import java.lang.reflect.*;
23 import org.lsmp.djep.groupJep.*;
26 * This class is the container for the configuration data used in
27 * {@link Configuration}; see that class for more information.
29 public class ConfigContainer
32 // =================== static fields =================================
33 // ===================================================================
35 /** Symbolic constant for no debug */
36 private static final int DEBUG_NO = 0;
38 /** Symbolic constant for regular debug */
39 private static final int DEBUG_REG = 1;
41 /** Symbolic constant for extended debug */
42 private static final int DEBUG_CONTEXT = 2;
44 //========================== fields =================================
45 //===================================================================
48 * The properties object that stores all configuration information.
50 private Properties config;
53 * Map associating string protocol names to the numeric protocol
54 * identifiers. The protocol names are understood without prefix.
56 private Map<String, Integer> protocols;
59 * The maximum depth that can be reached when analyzing expressions. This
60 * value can be substituted by setting the configuration parameter
66 private int debugLevel;
69 * If true, no exception is thrown. Instead, an error is printed and the
70 * Configuration tries to return a reasonable return value
72 private boolean check = false;
74 // =================== initialization ================================
75 // ===================================================================
77 public ConfigContainer(Properties config, boolean check)
81 maxdepth = getInt(Configuration.PAR_MAXDEPTH, Configuration.DEFAULT_MAXDEPTH);
83 // initialize protocol id-s
84 protocols = new HashMap<String, Integer>();
85 String[] prots = getNames(Configuration.PAR_PROT);// they're returned in correct order
86 for (int i = 0; i < prots.length; ++i) {
87 protocols.put(prots[i].substring(Configuration.PAR_PROT.length() + 1), Integer.valueOf(i));
89 String debug = config.getProperty(Configuration.PAR_DEBUG);
90 if (Configuration.DEBUG_EXTENDED.equals(debug))
91 debugLevel = DEBUG_CONTEXT;
92 else if (Configuration.DEBUG_FULL.equals(debug)) {
93 Map<String, String> map = new TreeMap<String, String>();
94 Enumeration e = config.propertyNames();
95 while (e.hasMoreElements()) {
96 String name = (String) e.nextElement();
97 String value = config.getProperty(name);
100 Iterator i = map.keySet().iterator();
101 while (i.hasNext()) {
102 String name = (String) i.next();
103 System.err.println("DEBUG " + name
104 + ("".equals(map.get(name)) ? "" : " = " + map.get(name)));
106 } else if (debug != null) {
107 debugLevel = DEBUG_REG;
109 debugLevel = DEBUG_NO;
113 // =================== static public methods =========================
114 // ===================================================================
117 * @return true if and only if name is a specified (existing) property.
119 public boolean contains(String name)
121 boolean ret = config.containsKey(name);
122 debug(name, "" + ret);
126 // -------------------------------------------------------------------
129 * Reads given configuration property. If not found, throws a
130 * {@link MissingParameterException}.
132 * Name of configuration property
136 public boolean getBoolean(String name, boolean def)
139 return getBool(name);
140 } catch (RuntimeException e) {
141 manageDefault(name, def, e);
146 // -------------------------------------------------------------------
150 * Reads given property. If not found, or the value is empty string then
151 * throws a {@link MissingParameterException}. Empty string is not
152 * accepted as false due to the similar function of {@link #contains} which
153 * returns true in that case. True is returned if the lowercase value of
154 * the property is "true", otherwise false is returned.
156 * Name of configuration property
158 public boolean getBoolean(String name)
161 return getBool(name);
162 } catch (RuntimeException e) {
163 manageException(name, e);
168 //-------------------------------------------------------------------
171 * The actual methods that implements getBoolean.
173 private boolean getBool(String name)
175 if (config.getProperty(name) == null) {
176 throw new MissingParameterException(name);
177 // "\nPossibly incorrect property: " + getSimilarProperty(name));
179 if (config.getProperty(name).matches("\\p{Blank}*")) {
180 throw new MissingParameterException(name,
181 "Blank value is not accepted when parsing Boolean.");
183 boolean ret = Boolean.valueOf(config.getProperty(name));
184 debug(name, "" + ret);
188 // -------------------------------------------------------------------
191 * Reads given configuration property. If not found, returns the default
194 * Name of configuration property
198 public int getInt(String name, int def)
201 Number ret = getVal(name, name, 0);
202 debug(name, "" + ret);
203 return ret.intValue();
204 } catch (RuntimeException e) {
205 manageDefault(name, def, e);
210 // -------------------------------------------------------------------
213 * Reads given configuration property. If not found, throws a
214 * {@link MissingParameterException}.
216 * Name of configuration property
218 public int getInt(String name)
221 Number ret = getVal(name, name, 0);
222 debug(name, "" + ret);
223 return ret.intValue();
224 } catch (RuntimeException e) {
225 manageException(name, e);
230 // -------------------------------------------------------------------
233 * Reads given configuration property. If not found, returns the default
236 * Name of configuration property
240 public long getLong(String name, long def)
243 Number ret = getVal(name, name, 0);
244 debug(name, "" + ret);
245 return ret.longValue();
246 } catch (RuntimeException e) {
247 manageDefault(name, def, e);
252 // -------------------------------------------------------------------
255 * Reads given configuration property. If not found, throws a
256 * {@link MissingParameterException}.
258 * Name of configuration property
260 public long getLong(String name)
263 Number ret = getVal(name, name, 0);
264 debug(name, "" + ret);
265 return ret.longValue();
266 } catch (RuntimeException e) {
267 manageException(name, e);
272 // -------------------------------------------------------------------
275 * Reads given configuration property. If not found, returns the default
278 * Name of configuration property
282 public double getDouble(String name, double def)
285 Number ret = getVal(name, name, 0);
286 debug(name, "" + ret);
287 return ret.doubleValue();
288 } catch (RuntimeException e) {
289 manageDefault(name, def, e);
294 // -------------------------------------------------------------------
297 * Reads given configuration property. If not found, throws a
298 * MissingParameterException.
300 * Name of configuration property
302 public double getDouble(String name)
305 Number ret = getVal(name, name, 0);
306 debug(name, "" + ret);
307 return ret.doubleValue();
308 } catch (RuntimeException e) {
309 manageException(name, e);
314 // -------------------------------------------------------------------
317 * Read numeric property values, parsing expression if necessary.
320 * the property name that started this expression evaluation
322 * the current property name to be evaluated
324 * the depth reached so far
325 * @return the evaluation of the expression associated to property
327 private Number getVal(String initial, String property, int depth)
329 if (depth > maxdepth) {
330 throw new IllegalParameterException(initial,
331 "Probable recursive definition - exceeded maximum depth " +
335 String s = config.getProperty(property);
336 if (s == null || s.equals("")) {
337 throw new MissingParameterException(property,
338 " when evaluating property " + initial);
339 // + "\nPossibly incorrect property: " + getSimilarProperty(property));
342 GroupJep jep = new GroupJep(new Operators());
343 jep.setAllowUndeclared(true);
345 jep.parseExpression(s);
346 String[] symbols = getSymbols(jep);
347 for (int i = 0; i < symbols.length; i++) {
348 Object d = getVal(initial, symbols[i], depth + 1);
349 jep.addVariable(symbols[i], d);
351 Object ret = jep.getValueAsObject();
353 System.err.println(jep.getErrorInfo());
357 // -------------------------------------------------------------------
360 * Returns an array of string, containing the symbols contained in the
361 * expression parsed by the specified JEP parser.
363 * the java expression parser containing the list of variables
364 * @return an array of strings.
366 private String[] getSymbols(org.nfunk.jep.JEP jep)
368 Hashtable h = jep.getSymbolTable();
369 String[] ret = new String[h.size()];
370 Enumeration e = h.keys();
372 while (e.hasMoreElements()) {
373 ret[i++] = (String) e.nextElement();
378 // -------------------------------------------------------------------
381 * Reads given configuration property. If not found, returns the default
384 * Name of configuration property
388 public String getString(String name, String def)
392 } catch (RuntimeException e) {
393 manageDefault(name, def, e);
398 // -------------------------------------------------------------------
401 * Reads given configuration property. If not found, throws a
402 * MissingParameterException. Removes trailing whitespace characters.
404 * Name of configuration property
406 public String getString(String name)
410 } catch (RuntimeException e) {
411 manageException(name, e);
417 * The actual method implementing getString().
419 private String getStr(String name)
421 String result = config.getProperty(name);
422 if (result == null) {
423 throw new MissingParameterException(name);
424 // "\nPossibly incorrect property: " + getSimilarProperty(name));
426 debug(name, "" + result);
428 return result.trim();
431 // -------------------------------------------------------------------
434 * Reads the given property from the configuration interpreting it as a
435 * protocol name. Returns the numeric protocol identifier of this protocol
436 * name. See the discussion of protocol name at {@link Configuration} for
437 * details on how this numeric id is calculated
440 * Name of configuration property
441 * @return the numeric protocol identifier associated to the value of the
444 public int getPid(String name)
447 String protname = getStr(name);
448 return lookupPid(protname);
449 } catch (RuntimeException e) {
450 manageException(name, e);
455 // -------------------------------------------------------------------
458 * Calls {@link #getPid(String)}, and returns the default if no property
459 * is defined with the given name.
462 * Name of configuration property
464 * the default protocol identifier
465 * @return the numeric protocol identifier associated to the value of the
466 * property, or the default if not defined
468 public int getPid(String name, int pid)
471 String protname = getStr(name);
472 return lookupPid(protname);
473 } catch (RuntimeException e) {
474 manageDefault(name, pid, e);
479 // -------------------------------------------------------------------
482 * Returns the numeric protocol identifier of the given protocol name.
486 * @return the numeric protocol identifier associated to the protocol name
488 public int lookupPid(String protname)
490 Integer ret = protocols.get(protname);
492 throw new MissingParameterException(Configuration.PAR_PROT + "." + protname);
493 // "\nPossibly incorrect property: "
494 // + getSimilarProperty(PAR_PROT + "." + protname));
496 return ret.intValue();
499 // -------------------------------------------------------------------
502 * Returns the name of a protocol that has the given identifier.
504 * Note that this is not a constant time operation in the number of
505 * protocols, although typically there are very few protocols defined.
508 * numeric protocol identifier.
509 * @return name of the protocol that has the given id. null if no protocols
512 public String lookupPid(int pid)
515 if (!protocols.containsValue(pid))
517 for (Map.Entry<String, Integer> i : protocols.entrySet()) {
518 if (i.getValue().intValue() == pid)
522 // never reached but java needs it...
526 // -------------------------------------------------------------------
529 * Reads given configuration property. If not found, throws a
530 * {@link MissingParameterException}. When creating the Class object, a
531 * few attempts are done to resolve the classname. See
532 * {@link Configuration} for details.
534 * Name of configuration property
536 public Class getClass(String name)
539 return getClazz(name);
540 } catch (RuntimeException e) {
541 manageException(name, e);
546 private Class getClazz(String name)
548 String classname = config.getProperty(name);
549 if (classname == null) {
550 throw new MissingParameterException(name);
551 // "\nPossibly incorrect property: " + getSimilarProperty(name));
553 debug(name, classname);
558 // Maybe classname is just a fully-qualified name
559 c = Class.forName(classname);
560 } catch (ClassNotFoundException e) {
563 // Maybe classname is a non-qualified name?
564 String fullname = ClassFinder.getQualifiedName(classname);
565 if (fullname != null) {
567 c = Class.forName(fullname);
568 } catch (ClassNotFoundException e) {
573 // Maybe there are multiple classes with the same
574 // non-qualified name.
575 String fullname = ClassFinder.getQualifiedName(classname);
576 if (fullname != null) {
577 String[] names = fullname.split(",");
578 if (names.length > 1) {
579 for (int i = 0; i < names.length; i++) {
580 for (int j = i + 1; j < names.length; j++) {
581 if (names[i].equals(names[j])) {
582 throw new IllegalParameterException(name,
583 "The class " + names[i]
584 + " appears more than once in the classpath; please check"
585 + " your classpath to avoid duplications.");
589 throw new IllegalParameterException(name,
590 "The non-qualified class name " + classname
591 + "corresponds to multiple fully-qualified classes:" + fullname);
596 // Last attempt: maybe the fully classified name is wrong,
597 // but the classname is correct.
598 String shortname = ClassFinder.getShortName(classname);
599 String fullname = ClassFinder.getQualifiedName(shortname);
600 if (fullname != null) {
601 throw new IllegalParameterException(name, "Class "
602 + classname + " does not exist. Possible candidate(s): " + fullname);
606 throw new IllegalParameterException(name, "Class "
607 + classname + " not found");
612 // -------------------------------------------------------------------
615 * Reads given configuration property. If not found, returns the default
618 * Name of configuration property
621 * @see #getClass(String)
623 public Class getClass(String name, Class def)
627 return Configuration.getClass(name);
628 } catch (RuntimeException e) {
629 manageDefault(name, def, e);
634 // -------------------------------------------------------------------
637 * Reads given configuration property for a class name. It returns an
638 * instance of the class. The class must implement a constructor that takes
639 * a String as an argument. The value of this string will be <tt>name</tt>.
640 * The constructor of the class can see the configuration so it can make
641 * use of this name to read its own parameters from it.
643 * Name of configuration property
644 * @throws MissingParameterException
645 * if the given property is not defined
646 * @throws IllegalParameterException
647 * if there is any problem creating the instance
649 public Object getInstance(String name)
652 return getInst(name);
653 } catch (RuntimeException e) {
654 manageException(name, e);
660 * The actual method implementing getInstance().
662 private Object getInst(String name)
664 Class c = getClass(name);
667 final String classname = c.getSimpleName();
670 Class pars[] = {String.class};
671 Constructor cons = c.getConstructor(pars);
672 Object objpars[] = {name};
673 return cons.newInstance(objpars);
674 } catch (NoSuchMethodException e) {
675 throw new IllegalParameterException(name, "Class "
676 + classname + " has no " + classname + "(String) constructor");
677 } catch (InvocationTargetException e) {
678 if (e.getTargetException() instanceof RuntimeException) {
679 throw (RuntimeException) e.getTargetException();
681 e.getTargetException().printStackTrace();
682 throw new RuntimeException("" + e.getTargetException());
684 } catch (Exception e) {
685 throw new IllegalParameterException(name, e + "");
689 // -------------------------------------------------------------------
692 * Reads given configuration property for a class name. It returns an
693 * instance of the class. The class must implement a constructor that takes
694 * a String as an argument. The value of this string will be <tt>name</tt>.
695 * The constructor of the class can see the configuration so it can make
696 * use of this name to read its own parameters from it.
698 * Name of configuration property
700 * The default object that is returned if there is no property
701 * defined with the given name
702 * @throws IllegalParameterException
703 * if the given name is defined but there is a problem creating
706 public Object getInstance(String name, Object def)
711 return getInst(name);
712 } catch (RuntimeException e) {
713 manageException(name, e);
718 // -------------------------------------------------------------------
721 * It returns an array of class instances. The instances are constructed by
722 * calling {@link #getInstance(String)} on the names returned by
723 * {@link #getNames(String)}.
725 * The component type (i.e. prefix of the list of configuration
726 * properties) which will be passed to {@link #getNames(String)}.
728 public Object[] getInstanceArray(String name)
731 String names[] = getNames(name);
732 Object[] result = new Object[names.length];
734 for (int i = 0; i < names.length; ++i) {
735 result[i] = getInstance(names[i]);
741 // -------------------------------------------------------------------
744 * Returns an array of names prefixed by the specified name. The array is
745 * sorted as follows. If there is no config entry
746 * <code>{@value peersim.config.Configuration#PAR_INCLUDE}+"."+name</code> or
747 * <code>{@value peersim.config.Configuration#PAR_ORDER}+"."+name</code> then the order is
748 * alphabetical. Otherwise this entry defines the order. For more
749 * information see {@link Configuration}.
751 * the component type (i.e., the prefix)
752 * @return the full property names in the order specified by the
755 public String[] getNames(String name)
757 ArrayList<String> ll = new ArrayList<String>();
758 final String pref = name + ".";
760 Enumeration e = config.propertyNames();
761 while (e.hasMoreElements()) {
762 String key = (String) e.nextElement();
763 if (key.startsWith(pref) && key.indexOf(".", pref.length()) < 0)
766 String[] ret = ll.toArray(new String[ll.size()]);
767 return order(ret, name);
770 // -------------------------------------------------------------------
773 * The input of this method is a set of property <code>names</code> (e.g.
774 * initializers, controls and protocols) and a string specifying the type
775 * (prefix) of these. The output is in <code>names</code>, which will
776 * contain a permutation of the original array. Parameter
777 * PAR_INCLUDE+"."+type, or if not present, PAR_ORDER+"."+type is read from
778 * the configuration. If none of them are defined then the order is
779 * identical to that of <code>names</code>. Otherwise the configuration
780 * entry must contain entries from <code>names</code>. It is assumed
781 * that the entries in <code>names</code> contain only word characters
782 * (alphanumeric and underscore '_'. The order configuration entry thus
783 * contains a list of entries from <code>names</code> separated by any
784 * non-word characters.
786 * It is not required that all entries are listed. If PAR_INCLUDE is used,
787 * then only those entries are returned that are listed. If PAR_ORDER is
788 * used, then all names are returned, but the array will start with those
789 * that are listed. The rest of the names follow in alphabetical order.
793 * the set of property names to be searched
795 * the string identifying the particular set of properties to be
798 private String[] order(String[] names, String type)
800 String order = getString(Configuration.PAR_INCLUDE + "." + type, null);
801 boolean include = order != null;
803 order = getString(Configuration.PAR_ORDER + "." + type, null);
806 if (order != null && !order.equals("")) {
807 // split around non-word characters
808 String[] sret = order.split("\\W+");
809 for (; i < sret.length; i++) {
811 for (; j < names.length; ++j)
812 if (names[j].equals(type + "." + sret[i]))
814 if (j == names.length) {
815 throw new IllegalParameterException(
816 (include ? Configuration.PAR_INCLUDE : Configuration.PAR_ORDER)
817 + "." + type, type + "." + sret[i] + " is not defined.");
818 } else // swap the element to current position
820 String tmps = names[j];
827 Arrays.sort(names, i, names.length);
828 int retsize = (include ? i : names.length);
829 String[] ret = new String[retsize];
830 for (int j = 0; j < retsize; ++j)
835 // -------------------------------------------------------------------
838 * Print debug information for configuration. The amount of information
839 * depends on the debug level DEBUG. 0 = nothing 1 = just the config name 2 =
840 * config name plus method calling
844 private void debug(String name, String result)
846 if (debugLevel == DEBUG_NO)
848 StringBuffer buffer = new StringBuffer();
849 buffer.append("DEBUG ");
851 buffer.append(" = ");
852 buffer.append(result);
855 if (debugLevel == DEBUG_CONTEXT) {
857 buffer.append("\n at ");
858 // Obtain the stack trace
859 StackTraceElement[] stack = null;
861 throw new Exception();
862 } catch (Exception e) {
863 stack = e.getStackTrace();
866 // Search the element that invoked Configuration
867 // It's the first whose class is different from Configuration
869 for (pos = 0; pos < stack.length; pos++) {
870 if (!stack[pos].getClassName().equals(Configuration.class.getName()))
874 buffer.append(stack[pos].getClassName());
876 buffer.append(stack[pos].getLineNumber());
877 buffer.append(", method ");
878 buffer.append(stack[pos - 1].getMethodName());
882 System.err.println(buffer);
885 // -------------------------------------------------------------------
888 * @return an array of adjacent letter pairs contained in the input string
889 * http://www.catalysoft.com/articles/StrikeAMatch.html
891 private String[] letterPairs(String str)
893 int numPairs = str.length() - 1;
894 String[] pairs = new String[numPairs];
895 for (int i = 0; i < numPairs; i++) {
896 pairs[i] = str.substring(i, i + 2);
901 // -------------------------------------------------------------------
904 * @return an ArrayList of 2-character Strings.
905 * http://www.catalysoft.com/articles/StrikeAMatch.html
907 private ArrayList<String> wordLetterPairs(String str)
909 ArrayList<String> allPairs = new ArrayList<String>();
910 // Tokenize the string and put the tokens/words into an array
911 String[] words = str.split("\\s");
913 for (int w = 0; w < words.length; w++) {
914 // Find the pairs of characters
915 String[] pairsInWord = letterPairs(words[w]);
916 for (int p = 0; p < pairsInWord.length; p++) {
917 allPairs.add(pairsInWord[p]);
923 // -------------------------------------------------------------------
926 * @return lexical similarity value in the range [0,1]
927 * http://www.catalysoft.com/articles/StrikeAMatch.html
929 private double compareStrings(String str1, String str2)
931 ArrayList pairs1 = wordLetterPairs(str1.toUpperCase());
932 ArrayList pairs2 = wordLetterPairs(str2.toUpperCase());
933 int intersection = 0;
934 int union_ = pairs1.size() + pairs2.size();
935 for (int i = 0; i < pairs1.size(); i++) {
936 Object pair1 = pairs1.get(i);
937 for (int j = 0; j < pairs2.size(); j++) {
938 Object pair2 = pairs2.get(j);
939 if (pair1.equals(pair2)) {
946 return (2.0 * intersection) / union_;
949 // -------------------------------------------------------------------
952 * Among the defined properties, returns the one more similar to String
955 private String getSimilarProperty(String property)
957 String bestProperty = null;
958 double bestValue = 0.0;
959 Enumeration e = config.keys();
960 while (e.hasMoreElements()) {
961 String key = (String) e.nextElement();
962 double compare = compareStrings(key, property);
963 if (compare > bestValue) {
971 //-------------------------------------------------------------------
973 private void manageDefault(String name, Object def,
976 debug(name, "" + def + " (DEFAULT)");
978 System.out.println("Warning: Property " + name + " = " +
981 if (e instanceof MissingParameterException) {
984 manageException(name, e);
988 //-------------------------------------------------------------------
990 private void manageException(String name, RuntimeException e)
993 if (e instanceof MissingParameterException) {
994 // Print just the short message in this case
995 System.out.println("Error: " +
996 ((MissingParameterException) e).getShortMessage());
997 } else if (e instanceof IllegalParameterException) {
998 // Print just the short message in this case
999 System.out.println("Error: " +
1000 ((IllegalParameterException) e).getShortMessage());
1002 System.out.println("Error: " + e.getMessage());
1009 //-------------------------------------------------------------------