0001: /*
0002: * <copyright>
0003: *
0004: * Copyright 2002-2007 BBNT Solutions, LLC
0005: * under sponsorship of the Defense Advanced Research Projects
0006: * Agency (DARPA).
0007: *
0008: * You can redistribute this software and/or modify it under the
0009: * terms of the Cougaar Open Source License as published on the
0010: * Cougaar Open Source Website (www.cougaar.org).
0011: *
0012: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
0013: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
0014: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
0015: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
0016: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
0017: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
0018: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
0019: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
0020: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
0021: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
0022: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0023: *
0024: * </copyright>
0025: */
0026:
0027: package org.cougaar.util;
0028:
0029: import java.io.Serializable;
0030: import java.lang.reflect.Field;
0031: import java.lang.reflect.Method;
0032: import java.lang.reflect.Modifier;
0033: import java.util.AbstractMap;
0034: import java.util.AbstractSet;
0035: import java.util.ArrayList;
0036: import java.util.Arrays;
0037: import java.util.Collection;
0038: import java.util.Collections;
0039: import java.util.Enumeration;
0040: import java.util.HashSet;
0041: import java.util.Iterator;
0042: import java.util.LinkedHashMap;
0043: import java.util.LinkedHashSet;
0044: import java.util.List;
0045: import java.util.Map;
0046: import java.util.Properties;
0047: import java.util.Set;
0048: import java.util.regex.Matcher;
0049: import java.util.regex.Pattern;
0050:
0051: import org.cougaar.bootstrap.SystemProperties;
0052:
0053: /**
0054: * A "name=value" parser.
0055: * <p>
0056: * Note that Arguments instances are unmodifiable, like Strings.
0057: * <p>
0058: * Example use:
0059: *
0060: * <pre>
0061: * Arguments args = new Arguments("x=y, q=one, q=two, z=99");
0062: * String x = args.getString("x");
0063: * assert "y".equals(x);
0064: * int z = args.getInt("z", 1234);
0065: * assert z == 99;
0066: * List<String> q = args.getStrings("q");
0067: * assert q != null && q.size() == 2;
0068: * </pre>
0069: *
0070: * <p>
0071: * The Cougaar component model includes built-in support to invoke an optional
0072: * "setArguments" method. Here's an example:
0073: *
0074: * <pre>
0075: * package org;
0076: * public class MyPlugin ... {
0077: * private Arguments args;
0078: * // The "setArguments" method is special -- it's an optional method
0079: * // that's found by the component model via reflection. The passed-in
0080: * // arguments instance is created via:
0081: * // new Arguments(listOfStrings, classname);
0082: * public void setArguments(Arguments args) { this.args = args; }
0083: * public void load() {
0084: * super.load();
0085: * // Get the value of our "foo" argument
0086: * //
0087: * // First looks for a plugin XML argument named "foo",
0088: * // next looks for a "-Dorg.MyPlugin.foo=" system property,
0089: * // otherwise the value will be the 1234 default.
0090: * int foo = args.getInt("foo", 1234);
0091: * System.out.println("foo is "+foo);
0092: * }
0093: * }
0094: * </pre>
0095: *
0096: * <p>
0097: * The {@link #callSetters} method supports "setter" reflection, for example:
0098: *
0099: * <pre>
0100: * package org;
0101: * public class MyPlugin ... {
0102: * private int foo = 1234;
0103: * public void setArguments(Arguments args) { args.callSetters(this); }
0104: * // This "set<i>NAME</i>(<i>TYPE</i>)" method is found by reflection.
0105: * // The args class will only invoke the setters for which it has values.
0106: * public void setFoo(int i) { this.foo = i; }
0107: * public void load() {
0108: * super.load();
0109: * System.out.println("foo is "+foo);
0110: * }
0111: * }
0112: * </pre>
0113: */
0114: public final class Arguments extends AbstractMap<String, List<String>>
0115: implements Serializable {
0116:
0117: /** A singleton instance for an empty arguments */
0118: public static final Arguments EMPTY_INSTANCE = new Arguments(null);
0119:
0120: /** @see toString(String,String) */
0121: private static final Pattern PATTERN = Pattern
0122: .compile("\\$(key|value|vals|veach|vlist)");
0123:
0124: private static final int OPTIMIZE_SIZE = 5;
0125:
0126: private final Map<String, List<String>> m;
0127:
0128: public Arguments(Object o) {
0129: this (o, null);
0130: }
0131:
0132: public Arguments(Object o, Object propertyPrefix) {
0133: this (o, propertyPrefix, null);
0134: }
0135:
0136: public Arguments(Object o, Object propertyPrefix, Object deflt) {
0137: this (o, propertyPrefix, deflt, null);
0138: }
0139:
0140: /**
0141: * @param o the optional input object, e.g. a List of name=value Strings, or
0142: * another Arguments instance.
0143: * @param propertyPrefix the optional SystemProperties property prefix, e.g.
0144: * "org.MyPlugin.", for "-Dorg.MyPlugin.name=value" lookups. If a
0145: * class is specified, that class's name+. and its parent's
0146: * names+. will be used.
0147: * @param deflt the optional default values, e.g. a List of name=value
0148: * Strings, or another Arguments instance.
0149: * @param keys the optional filter on which keys are allowed, e.g. only
0150: * allow (A, B, C)
0151: */
0152: public Arguments(Object o, Object propertyPrefix, Object deflt,
0153: Object keys) {
0154: try {
0155: Map<String, List<String>> m2 = parseMap(o);
0156: List<String> prefixes = parsePrefixes(propertyPrefix);
0157: Map<String, List<String>> def = parseMap(deflt);
0158: Set<String> ks = parseSet(keys);
0159: this .m = parse(m2, prefixes, def, ks);
0160: } catch (Exception e) {
0161: throw new IllegalArgumentException(
0162: "Unable to create new Arguments(" + "\n o = " + o
0163: + argString(propertyPrefix, deflt, keys)
0164: + ")", e);
0165: }
0166: }
0167:
0168: private String argString(Object propertyPrefix, Object deflt,
0169: Object keys) {
0170: if (propertyPrefix == null && deflt == null && keys == null) {
0171: return "";
0172: }
0173: return ",\n propertyPrefix = "
0174: + propertyPrefix
0175: + (deflt == null && keys == null ? ""
0176: : (",\n deflt = " + deflt + (keys == null ? ""
0177: : ",\n keys = " + keys)));
0178: }
0179:
0180: /**
0181: * All the other "get*" methods call this method.
0182: * <p>
0183: * Equivalent to:
0184: *
0185: * <pre>
0186: * List<String> l = get(key);
0187: * return (l == null ? deflt : l);
0188: * </pre>
0189: *
0190: * @return the non-empty values or the deflt
0191: */
0192: public List<String> getStrings(String key, List<String> deflt) {
0193: if (m instanceof OptimizedMap) {
0194: return ((OptimizedMap) m).getStrings(key, deflt);
0195: }
0196: List<String> l = m.get(key);
0197: return (l == null ? deflt : l);
0198: }
0199:
0200: //
0201: // Helper methods:
0202: //
0203:
0204: /** @return the value, or null if not set */
0205: public String getString(String key) {
0206: return getString(key, null);
0207: }
0208:
0209: /**
0210: * Get the first value, or the specified default if there is no value.
0211: * <p>
0212: * Equivalent to:
0213: *
0214: * <pre>
0215: * List<String> l = get(key);
0216: * return (l == null ? deflt : l.get(0));
0217: * </pre>
0218: *
0219: * @return the first value, or the deflt if not set
0220: */
0221: public String getString(String key, String deflt) {
0222: if (m instanceof OptimizedMap) {
0223: return ((OptimizedMap) m).getString(key, deflt);
0224: }
0225: List<String> l = m.get(key);
0226: return (l == null ? deflt : l.get(0));
0227: }
0228:
0229: /** @return same as {@link #get(String)} */
0230: public List<String> getStrings(String key) {
0231: return getStrings(key, null);
0232: }
0233:
0234: /** @return the value, or false if not set */
0235: public boolean getBoolean(String key) {
0236: return getBoolean(key, false);
0237: }
0238:
0239: /** @return the first value, or the deflt if not set */
0240: public boolean getBoolean(String key, boolean deflt) {
0241: String value = getString(key);
0242: return (value == null ? deflt : "true".equals(value));
0243: }
0244:
0245: /** @return the values, or null if not set */
0246: public List<Boolean> getBooleans(String key) {
0247: return getBooleans(key, null);
0248: }
0249:
0250: /** @return the values, or the deflt if not set */
0251: public List<Boolean> getBooleans(String key, List<Boolean> deflt) {
0252: List<String> l = getStrings(key, null);
0253: if (l == null) {
0254: return deflt;
0255: }
0256: int n = l.size();
0257: if (n == 1) {
0258: Boolean value = Boolean.valueOf(l.get(0));
0259: return Collections.singletonList(value);
0260: }
0261: List<Boolean> ret = new ArrayList<Boolean>(n);
0262: for (String s : l) {
0263: ret.add(Boolean.valueOf(s));
0264: }
0265: return Collections.unmodifiableList(ret);
0266: }
0267:
0268: /** @return the value, or -1 if not set */
0269: public int getInt(String key) {
0270: return getInt(key, -1);
0271: }
0272:
0273: /** @return the first value, or the deflt if not set */
0274: public int getInt(String key, int deflt) {
0275: String value = getString(key);
0276: return (value == null ? deflt : Integer.parseInt(value));
0277: }
0278:
0279: /** @return the values, or null if not set */
0280: public List<Integer> getInts(String key) {
0281: return getInts(key, null);
0282: }
0283:
0284: /** @return the values, or the deflt if not set */
0285: public List<Integer> getInts(String key, List<Integer> deflt) {
0286: List<String> l = getStrings(key, null);
0287: if (l == null) {
0288: return deflt;
0289: }
0290: int n = l.size();
0291: if (n == 1) {
0292: Integer value = Integer.valueOf(l.get(0));
0293: return Collections.singletonList(value);
0294: }
0295: List<Integer> ret = new ArrayList<Integer>(n);
0296: for (String s : l) {
0297: ret.add(Integer.valueOf(s));
0298: }
0299: return Collections.unmodifiableList(ret);
0300: }
0301:
0302: /** @return the value, or -1 if not set */
0303: public long getLong(String key) {
0304: return getLong(key, -1);
0305: }
0306:
0307: /** @return the first value, or the deflt if not set */
0308: public long getLong(String key, long deflt) {
0309: String value = getString(key);
0310: return (value == null ? deflt : Long.parseLong(value));
0311: }
0312:
0313: /** @return the value, or null if not set */
0314: public List<Long> getLongs(String key) {
0315: return getLongs(key, null);
0316: }
0317:
0318: /** @return the values, or the deflt if not set */
0319: public List<Long> getLongs(String key, List<Long> deflt) {
0320: List<String> l = getStrings(key, null);
0321: if (l == null) {
0322: return deflt;
0323: }
0324: int n = l.size();
0325: if (n == 1) {
0326: Long value = Long.valueOf(l.get(0));
0327: return Collections.singletonList(value);
0328: }
0329: List<Long> ret = new ArrayList<Long>(n);
0330: for (String s : l) {
0331: ret.add(Long.valueOf(s));
0332: }
0333: return Collections.unmodifiableList(ret);
0334: }
0335:
0336: /** @return the value, or Double.NaN if not set */
0337: public double getDouble(String key) {
0338: return getDouble(key, Double.NaN);
0339: }
0340:
0341: /** @return the first value, or the deflt if not set */
0342: public double getDouble(String key, double deflt) {
0343: String value = getString(key);
0344: return (value == null ? deflt : Double.parseDouble(value));
0345: }
0346:
0347: /** @return the value, or null if not set */
0348: public List<Double> getDoubles(String key) {
0349: return getDoubles(key, null);
0350: }
0351:
0352: /** @return the values, or the deflt if not set */
0353: public List<Double> getDoubles(String key, List<Double> deflt) {
0354: List<String> l = getStrings(key, null);
0355: if (l == null) {
0356: return deflt;
0357: }
0358: int n = l.size();
0359: if (n == 1) {
0360: Double value = Double.valueOf(l.get(0));
0361: return Collections.singletonList(value);
0362: }
0363: List<Double> ret = new ArrayList<Double>(n);
0364: for (String s : l) {
0365: ret.add(Double.valueOf(s));
0366: }
0367: return Collections.unmodifiableList(ret);
0368: }
0369:
0370: /**
0371: * Split this arguments instance of String-to-List[N] pairs into a List of
0372: * "flattened" Arguments with String-to-List[1] pairs.
0373: * <p>
0374: * This is useful to group together same-named arguments.
0375: *
0376: * <pre>
0377: * For example, the constructor input:
0378: * "foo=f1, bar=b1, qux=q1,
0379: * foo=f2, bar=b2, qux=q2,
0380: * foo=f3, bar=b3, qux=q3"
0381: * will be parsed as:
0382: * {foo=[f1,f2,f3], bar=[b1,b2,b3], qux=[q1,q2,q3]}
0383: * and can be split into:
0384: * [{foo=[f1], bar=[b1], qux=[q1]},
0385: * {foo=[f2], bar=[b2], qux=[q2]},
0386: * {foo=[f3], bar=[b3], qux=[q3]}}
0387: * This simplifies iteration:
0388: * for (Arguments a : args.split()) {
0389: * System.out.println("foo is "+a.getString("foo"));
0390: * }
0391: * which will print:
0392: * foo is f1
0393: * foo is f2
0394: * foo is f3
0395: * </pre>
0396: *
0397: * @return a List of Arguments.
0398: */
0399: public List<Arguments> split() {
0400: int n = 1;
0401: if (!(m instanceof OptimizedMap)) {
0402: for (List<String> l : m.values()) {
0403: n = Math.max(n, l.size());
0404: }
0405: }
0406: if (n == 1) {
0407: return Collections.singletonList(this );
0408: }
0409: List<Map<String, String>> ma = new ArrayList<Map<String, String>>(
0410: n);
0411: for (int i = 0; i < n; i++) {
0412: ma.add(new LinkedHashMap<String, String>());
0413: }
0414:
0415: for (Map.Entry<String, List<String>> me : m.entrySet()) {
0416: String key = me.getKey();
0417: List<String> l = me.getValue();
0418: for (int i = 0; i < l.size(); i++) {
0419: ma.get(i).put(key, l.get(i));
0420: }
0421: }
0422:
0423: List<Arguments> ret = new ArrayList<Arguments>(n);
0424: for (Map<String, String> mi : ma) {
0425: ret.add(new Arguments(mi));
0426: }
0427: return Collections.unmodifiableList(ret);
0428: }
0429:
0430: //
0431: // Modifiers
0432: //
0433:
0434: /**
0435: * Return an Arguments instance where the values for key1 and key2 are
0436: * swapped.
0437: * <p>
0438: * In other words:
0439: *
0440: * <pre>
0441: * // given:
0442: * List<String> v1 = args.getStrings(key1);
0443: * List<String> v2 = args.getStrings(key2);
0444: * // do swap:
0445: * Arguments ret = args.swap(key1, key2);
0446: * // validate:
0447: * assert(ret.size() == args.size());
0448: * List<String> x1 = ret.getStrings(key1);
0449: * List<String> x2 = ret.getStrings(key2);
0450: * assert(v1 == null ? x2 == null : v1.equals(x2));
0451: * assert(v2 == null ? x1 == null : v2.equals(x1));
0452: * </pre>
0453: *
0454: * @return returns a new Arguments instance
0455: */
0456: public Arguments swap(String key1, String key2) {
0457: // could optimize this...
0458: List<String> v1 = getStrings(key1);
0459: List<String> v2 = getStrings(key2);
0460: Arguments ret = setStrings(key1, v2);
0461: ret = ret.setStrings(key2, v1);
0462: return ret;
0463: }
0464:
0465: /** @see #setStrings */
0466: public Arguments setString(String key, String value) {
0467: List<String> l = (value == null ? null : Collections
0468: .singletonList(value));
0469: return setStrings(key, l);
0470: }
0471:
0472: /**
0473: * @param key the non-null key
0474: * @param values the values, which can be null or empty to remove the
0475: * specified key's entry
0476: * @return returns a possibly new Arguments instance (like
0477: * {@link String#trim} and similar copy-on-modify classes) where
0478: * "getStrings(key)" will be equal to the specified "values"
0479: */
0480: public Arguments setStrings(String key, List<String> values) {
0481: // could optimize this...
0482: List<String> old = getStrings(key);
0483: if (values == null || values.isEmpty()) {
0484: if (old == null) {
0485: return this ;
0486: }
0487: if (size() == 1) {
0488: return EMPTY_INSTANCE;
0489: }
0490: Set<String> filter = new HashSet<String>(keySet());
0491: filter.remove(key);
0492: return new Arguments(this , null, null, filter);
0493: } else {
0494: Map<String, List<String>> add = Collections.singletonMap(
0495: key, values);
0496: return new Arguments(add, null, this );
0497: }
0498: }
0499:
0500: //
0501: // Required base class methods:
0502: //
0503:
0504: public Set<Map.Entry<String, List<String>>> entrySet() {
0505: return m.entrySet();
0506: }
0507:
0508: // avoid linear scan through our "entrySet()":
0509: public int size() {
0510: return m.size();
0511: }
0512:
0513: public boolean containsKey(Object key) {
0514: return (get(key) != null);
0515: }
0516:
0517: public List<String> get(String key) {
0518: return m.get(key);
0519: }
0520:
0521: //
0522: // Reflection methods:
0523: //
0524:
0525: /**
0526: * Call the given object's setter methods or fields for every name=value
0527: * pair in the {@link #entrySet}, return the Set of unknown String keys.
0528: * <p>
0529: * For example, the name=value pair "x=y" will look for:
0530: *
0531: * <pre>
0532: * public void setX(<i>type</i>) {..}
0533: * </pre>
0534: *
0535: * and field:
0536: *
0537: * <pre>
0538: * public <i>type</i> x;
0539: * </pre>
0540: *
0541: * If neither are found then "x" will be included in the returned Set.
0542: *
0543: * @param o Object that has the setter methods & fields
0544: * @return Subset of the {@link #keySet} that could not be set
0545: */
0546: public Set<String> callSetters(Object o) {
0547: if (isEmpty()) {
0548: return Collections.emptySet();
0549: }
0550: Class cl = o.getClass();
0551: if (!Modifier.isPublic(cl.getModifiers())) {
0552: return keySet();
0553: }
0554: Set<String> ret = null;
0555: Method[] methods = cl.getMethods();
0556: for (Map.Entry<String, List<String>> me : entrySet()) {
0557: String key = me.getKey();
0558: List<String> l = me.getValue();
0559:
0560: boolean found = false;
0561:
0562: // look for setter method(s)
0563: String setter_name = "set"
0564: + Character.toUpperCase(key.charAt(0))
0565: + key.substring(1);
0566: for (int i = 0; i < methods.length; i++) {
0567: Method mi = methods[i];
0568: if (!Modifier.isPublic(mi.getModifiers())) {
0569: continue;
0570: }
0571: if (!setter_name.equals(mi.getName())) {
0572: continue;
0573: }
0574: Class[] p = mi.getParameterTypes();
0575: if (p.length != 1) {
0576: continue;
0577: }
0578: try {
0579: Object arg = cast(l, p[0]);
0580: mi.invoke(o, new Object[] { arg });
0581: found = true;
0582: } catch (Exception e) {
0583: throw new RuntimeException("Unable to set " + key
0584: + "=" + l + " on " + mi, e);
0585: }
0586: }
0587:
0588: // look for field
0589: Field field = null;
0590: try {
0591: field = cl.getField(key);
0592: if (!Modifier.isPublic(field.getModifiers())) {
0593: field = null;
0594: }
0595: } catch (Exception e) {
0596: }
0597: if (field != null) {
0598: try {
0599: Object arg = cast(l, field.getType());
0600: field.set(o, arg);
0601: found = true;
0602: } catch (Exception e) {
0603: throw new RuntimeException("Unable to set " + key
0604: + "=" + l + " on " + field, e);
0605: }
0606: }
0607:
0608: if (!found) {
0609: if (ret == null) {
0610: ret = new LinkedHashSet<String>();
0611: }
0612: ret.add(key);
0613: }
0614: }
0615: if (ret == null) {
0616: return Collections.emptySet();
0617: }
0618: return ret;
0619: }
0620:
0621: private static Object cast(List<String> l, Class type) {
0622: if (String.class.isAssignableFrom(type)) {
0623: return l.get(0);
0624: }
0625: if (type.isPrimitive()) {
0626: String value = l.get(0);
0627: if (type == Boolean.TYPE) {
0628: return Boolean.valueOf(value);
0629: } else if (type == Character.TYPE) {
0630: return Character.valueOf(value.charAt(0));
0631: } else if (type == Byte.TYPE) {
0632: return Byte.valueOf(value);
0633: } else if (type == Short.TYPE) {
0634: return Short.valueOf(value);
0635: } else if (type == Integer.TYPE) {
0636: return Integer.valueOf(value);
0637: } else if (type == Long.TYPE) {
0638: return Long.valueOf(value);
0639: } else if (type == Float.TYPE) {
0640: return Float.valueOf(value);
0641: } else if (type == Double.TYPE) {
0642: return Double.valueOf(value);
0643: }
0644: }
0645: if (Collection.class.isAssignableFrom(type)) {
0646: return l;
0647: }
0648: // RFE support primitive wrappers, e.g. Integer? Boolean?
0649: // RFE support arrays, e.g. double[]? String[]?
0650: throw new UnsupportedOperationException("Unknown type " + type
0651: + " for " + l);
0652: }
0653:
0654: //
0655: // toString methods:
0656: //
0657:
0658: /**
0659: * @see #toString(String) Same as "{"+toString(null)+"}"
0660: */
0661: public String toString() {
0662: // return "{" + toString(null, null) + "}";
0663: return super .toString();
0664: }
0665:
0666: /** @see #toString(String,String) Same as "toString(format, null)" */
0667: public String toString(String format) {
0668: return toString(format, null);
0669: }
0670:
0671: /**
0672: * Create a string representation of this map using the given format.
0673: *
0674: * <pre>
0675: * For example, if our map contains:
0676: * A=B
0677: * X=V0,V1,V2
0678: * then:
0679: * toString("the_$key is the_$value", " * ");
0680: * would return:
0681: * the_A is the_B * the_X is the_V0
0682: * and:
0683: * toString("($key eq $vals)", " +\n");
0684: * would return:
0685: * (A eq B) +
0686: * (X eq [V0, V1, V2])
0687: * and:
0688: * toString("$key=$veach", "&");
0689: * would return a URL-like string:
0690: * A=B&X=V0&X=V1&X=V2
0691: * and:
0692: * "{" + toString("$key=$vlist", ", ") + "}";
0693: * would return the standard {@link Map#toString} format:
0694: * {A=[B], X=[V0, V1, V2]}
0695: * </pre>
0696: *
0697: * The supported variables are:
0698: * <ul>
0699: * <li>"$key" is the string key name (e.g. "X")</li>
0700: * <li>"$value" is the first value (e.g. "V0"), as defined in
0701: * {@link #getString(String)}</li>
0702: * <li>"$vals" is the first value if there is only one value (e.g. "B"),
0703: * otherwise the list of values prefixed with "[" and "]" if there are
0704: * multiple values (e.g. "[V0, V1, V2]").</li>
0705: * <li>"$veach" is current value in the list (e.g. "V1")</li>
0706: * <li>"$vlist" is the "[]" wrapped list (e.g. "[B]" or "[V0, V1, V2]")</li>
0707: * </ul>
0708: *
0709: * @param format optional format, defaults to "$key=$vlist"
0710: * @param separator optional separator, defaults to ", "
0711: *
0712: * @return a string with each entry in the given format, where every "$key"
0713: * is replaced with the map key and every "$value" is replaced with
0714: * the map value.
0715: */
0716: public String toString(String format, String separator) {
0717: String form = format;
0718: if (form == null) {
0719: form = "$key=$vlist";
0720: }
0721:
0722: String sep = separator;
0723: if (sep == null) {
0724: sep = ", ";
0725: }
0726: if (sep.length() == 0) {
0727: sep = null;
0728: }
0729:
0730: Matcher x = PATTERN.matcher(form);
0731:
0732: boolean firstTime = true;
0733: StringBuffer buf = new StringBuffer();
0734: for (Map.Entry<String, List<String>> me : entrySet()) {
0735: String k = me.getKey();
0736: List<String> l = me.getValue();
0737:
0738: boolean hasEach = false;
0739: int eachIndex = 0;
0740: while (true) {
0741: if (firstTime) {
0742: firstTime = false;
0743: } else {
0744: if (sep != null) {
0745: buf.append(sep);
0746: }
0747: }
0748:
0749: x.reset();
0750: while (x.find()) {
0751: String tag = x.group(1);
0752: String value;
0753: if ("veach".equals(tag)) {
0754: hasEach = true;
0755: value = l.get(eachIndex);
0756: } else {
0757: value = entryString(k, l, tag);
0758: }
0759: x.appendReplacement(buf, value);
0760: }
0761: x.appendTail(buf);
0762:
0763: if (!hasEach || ++eachIndex >= l.size())
0764: break;
0765: }
0766: }
0767: return buf.toString();
0768: }
0769:
0770: private String entryString(String k, List<String> l, String tag) {
0771: if ("key".equals(tag)) {
0772: return k;
0773: } else if ("value".equals(tag)) {
0774: return l.get(0);
0775: } else if ("vals".equals(tag)) {
0776: if (l.size() == 1) {
0777: return l.get(0);
0778: } else {
0779: return l.toString();
0780: }
0781: } else if ("vlist".equals(tag)) {
0782: return l.toString();
0783: } else {
0784: return "InternalError!";
0785: }
0786: }
0787:
0788: //
0789: // Internal parsing:
0790: //
0791:
0792: /**
0793: * @return null or a modifiable, non-empty, ordered map of unmodifiable,
0794: * non-empty, lists
0795: */
0796: private static final Map<String, List<String>> parseMap(
0797: Object object) {
0798: Object o = object;
0799: if (o == null) {
0800: return null;
0801: }
0802: if (o instanceof Arguments) {
0803: return ((Arguments) o).m;
0804: }
0805: if (o instanceof Map) {
0806: Map m2 = (Map) o;
0807: if (m2.isEmpty()) {
0808: return null;
0809: }
0810: Map<String, List<String>> ret = new LinkedHashMap<String, List<String>>();
0811: for (Object x : m2.entrySet()) {
0812: Map.Entry me = (Map.Entry) x;
0813: String key = parseString(me.getKey(), "Map key");
0814: List<String> value = parseList(me.getValue());
0815: if (value == null) {
0816: continue;
0817: }
0818: ret.put(key, value);
0819: }
0820: return ret;
0821: }
0822: if (o instanceof String) {
0823: o = ((String) o).split("\\s*,\\s*");
0824: }
0825: if (o instanceof Object[]) {
0826: o = Arrays.asList((Object[]) o);
0827: }
0828: if (!(o instanceof Collection)) {
0829: throw new IllegalArgumentException(
0830: "Expecting null, Arguments, Map, Object[], or Collection, not "
0831: + (o == null ? "null" : o.getClass()
0832: .getName()));
0833: }
0834: Collection c = (Collection) o;
0835: int n = c.size();
0836: if (n == 0) {
0837: return null;
0838: }
0839: Map<String, List<String>> ret = null;
0840: boolean hasMulti = false;
0841: Iterator iter = c.iterator();
0842: for (int i = 0; i < n; i++) {
0843: Object oi = iter.next();
0844: if (!(oi instanceof String)) {
0845: throw new IllegalArgumentException(
0846: "Expecting a Collection of Strings, not "
0847: + (oi == null ? "null" : (oi.getClass()
0848: .getName()
0849: + " " + oi)));
0850: }
0851: String s = (String) oi;
0852: int sep = s.indexOf('=');
0853: if (sep < 0) {
0854: throw new IllegalArgumentException(
0855: "Missing a \"=\" separator for \"" + s + "\"");
0856: }
0857: String key = s.substring(0, sep).trim();
0858: String value = s.substring(sep + 1).trim();
0859: if (key.length() <= 0) {
0860: throw new IllegalArgumentException(
0861: "Key length is zero for \"" + s + "\"");
0862: }
0863: if (ret == null) {
0864: ret = new LinkedHashMap<String, List<String>>();
0865: }
0866: List<String> prev = ret.get(key);
0867: if (prev == null) {
0868: ret.put(key, Collections.singletonList(value));
0869: continue;
0870: }
0871: hasMulti = true;
0872: if (prev.size() == 1) {
0873: String s0 = prev.get(0);
0874: prev = new ArrayList<String>(2);
0875: prev.add(s0);
0876: ret.put(key, prev);
0877: }
0878: prev.add(value);
0879: }
0880: if (hasMulti) {
0881: // make values unmodifiable
0882: for (Map.Entry<String, List<String>> me : ret.entrySet()) {
0883: List<String> prev = me.getValue();
0884: if (prev.size() == 1) {
0885: continue;
0886: }
0887: prev = Collections.unmodifiableList(prev);
0888: me.setValue(prev);
0889: }
0890: }
0891: // don't need to make ret unmodifiable
0892: return ret;
0893: }
0894:
0895: private static final Set<String> parseSet(Object object) {
0896: Object o = object;
0897: if (o == null) {
0898: return null;
0899: }
0900: if (o instanceof String) {
0901: o = ((String) o).split("\\s*,\\s*");
0902: }
0903: if (o instanceof Object[]) {
0904: o = Arrays.asList((Object[]) o);
0905: }
0906: if (!(o instanceof Collection)) {
0907: throw new IllegalArgumentException(
0908: "Expecting null, String, Object[], or Collection, not "
0909: + (o == null ? "null" : o.getClass()
0910: .getName()));
0911: }
0912: Collection c = (Collection) o;
0913: int n = c.size();
0914: if (n == 0) {
0915: return null;
0916: }
0917: Set<String> ret = new HashSet<String>(c.size());
0918: for (Object oi : c) {
0919: ret.add(parseString(oi, "Set filter value"));
0920: }
0921: return ret;
0922: }
0923:
0924: private static final List<String> parseList(Object object) {
0925: Object o = object;
0926: if (o instanceof String) {
0927: return Collections.singletonList((String) o);
0928: }
0929: if (o instanceof Object[]) {
0930: o = Arrays.asList((Object[]) o);
0931: }
0932: if (!(o instanceof Collection)) {
0933: throw new IllegalArgumentException(
0934: "Expecting a String, Object[], or Collection value, not "
0935: + (o == null ? "null" : (o.getClass()
0936: .getName())
0937: + " " + o));
0938: }
0939: Collection c = (Collection) o;
0940: int n = c.size();
0941: if (n == 0) {
0942: return null;
0943: }
0944: Iterator iter = c.iterator();
0945: if (n == 1) {
0946: String s = parseString(iter.next(), "Collection value");
0947: return Collections.singletonList(s);
0948: }
0949: List<String> ret = new ArrayList<String>(n);
0950: for (int i = 0; i < n; i++) {
0951: String s = parseString(iter.next(), "Collection value");
0952: ret.add(s);
0953: }
0954: return Collections.unmodifiableList(ret);
0955: }
0956:
0957: private static final String parseString(Object o, String desc) {
0958: if (!(o instanceof String)) {
0959: throw new IllegalArgumentException("Expecting a "
0960: + desc
0961: + " String, not "
0962: + (o == null ? "null" : (o.getClass().getName())
0963: + " " + o));
0964: }
0965: return (String) o;
0966: }
0967:
0968: private static final List<String> parsePrefixes(Object o) {
0969: if (o == null) {
0970: return Collections.emptyList();
0971: }
0972: if (o instanceof String) {
0973: return Collections.singletonList((String) o);
0974: }
0975: if (!(o instanceof Class)) {
0976: throw new IllegalArgumentException(
0977: "Expecting null, a String, or a Class, not "
0978: + (o == null ? "null" : (o.getClass()
0979: .getName())
0980: + " " + o));
0981: }
0982: List<String> ret = new ArrayList<String>();
0983: for (Class cl = (Class) o; cl != null; cl = cl.getSuperclass()) {
0984: ret.add(cl.getName() + ".");
0985: }
0986: return ret;
0987: }
0988:
0989: /**
0990: * @param m a map created by "parseMap()"
0991: * @param prefixes a list created by "parsePrefixes()"
0992: * @param deflt a map created by "parseMap()"
0993: * @param keys a set created by "parseSet()"
0994: * @return a non-null, unmodifiable, ordered map
0995: */
0996: private static final Map<String, List<String>> parse(
0997: Map<String, List<String>> m, List<String> prefixes,
0998: Map<String, List<String>> deflt, Set<String> keys) {
0999: Map<String, List<String>> ret = new LinkedHashMap<String, List<String>>();
1000: if (m != null && !m.isEmpty()) {
1001: if (keys == null) {
1002: ret.putAll(m);
1003: } else {
1004: for (String key : keys) {
1005: List<String> l = m.get(key);
1006: if (l == null) {
1007: continue;
1008: }
1009: ret.put(key, l);
1010: }
1011: }
1012: }
1013: if ((prefixes != null && !prefixes.isEmpty())
1014: && (keys == null || (ret.size() < keys.size()))) {
1015: for (String s : prefixes) {
1016: Properties props = SystemProperties
1017: .getSystemPropertiesWithPrefix(s);
1018: if (props == null || props.isEmpty()) {
1019: continue;
1020: }
1021: for (Enumeration en = props.propertyNames(); en
1022: .hasMoreElements();) {
1023: String name = (String) en.nextElement();
1024: if (!name.startsWith(s)) {
1025: continue;
1026: }
1027: String key = name.substring(s.length());
1028: if (key.length() <= 0) {
1029: continue;
1030: }
1031: if (ret.containsKey(key)) {
1032: continue;
1033: }
1034: if (keys != null && !keys.contains(key)) {
1035: continue;
1036: }
1037: String value = props.getProperty(name);
1038: // RFE split by commas, but not if quoted?
1039: List<String> l = Collections.singletonList(value);
1040: ret.put(key, l);
1041: }
1042: }
1043: }
1044: if ((deflt != null && !deflt.isEmpty())
1045: && (keys == null || (ret.size() < keys.size()))) {
1046: for (Map.Entry<String, List<String>> me : deflt.entrySet()) {
1047: String key = me.getKey();
1048: if (ret.containsKey(key)) {
1049: continue;
1050: }
1051: if (keys != null && !keys.contains(key)) {
1052: continue;
1053: }
1054: ret.put(key, me.getValue());
1055: }
1056: }
1057: int n = ret.size();
1058: if (n == 0) {
1059: return Collections.emptyMap();
1060: }
1061: if (n == 1) {
1062: Map.Entry<String, List<String>> me = ret.entrySet()
1063: .iterator().next();
1064: return Collections.singletonMap(me.getKey(), me.getValue());
1065: }
1066: if (n <= OPTIMIZE_SIZE) {
1067: boolean hasMulti = false;
1068: for (List<String> l : ret.values()) {
1069: if (l.size() > 1) {
1070: hasMulti = true;
1071: break;
1072: }
1073: }
1074: if (!hasMulti) {
1075: return new OptimizedMapImpl(ret);
1076: }
1077: }
1078: return Collections.unmodifiableMap(ret);
1079: }
1080:
1081: interface OptimizedMap extends Map<String, List<String>> {
1082: List<String> getStrings(String key, List<String> deflt);
1083:
1084: String getString(String key, String deflt);
1085: }
1086:
1087: /**
1088: * Nearly all of our expected uses will have only a handleful of entries,
1089: * where every value is a single-element List<String>, so we optimize
1090: * this case.
1091: * <p>
1092: * We keep two String arrays; one for the keys, and one for the values.
1093: * <p>
1094: * Contrast this with the general case, where we keep an unmodifiableMap
1095: * wrapper around a LinkedHashMap of String-to-List entries, where the value
1096: * Lists would be a mix of<br>
1097: * (1) singletonList wrappers around strings, and<br>
1098: * (2) unmodifiableList wrappers around ArrayLists of strings.<br>
1099: */
1100: private static final class OptimizedMapImpl extends
1101: AbstractMap<String, List<String>> implements OptimizedMap,
1102: Serializable {
1103: private final String[] keys;
1104: private final String[] values;
1105:
1106: public OptimizedMapImpl(Map<String, List<String>> m) {
1107: keys = new String[m.size()];
1108: values = new String[m.size()];
1109: int i = 0;
1110: for (Map.Entry<String, List<String>> me : m.entrySet()) {
1111: keys[i] = me.getKey();
1112: values[i] = me.getValue().get(0);
1113: i++;
1114: }
1115: }
1116:
1117: public List<String> getStrings(String key, List<String> deflt) {
1118: String s = getString(key, null);
1119: return (s == null ? deflt : Collections.singletonList(s));
1120: }
1121:
1122: public String getString(String key, String deflt) {
1123: for (int i = 0; i < keys.length; i++) {
1124: if (key.equals(keys[i])) {
1125: return values[i];
1126: }
1127: }
1128: return deflt;
1129: }
1130:
1131: // required by our AbstractMap base class:
1132: public Set<Map.Entry<String, List<String>>> entrySet() {
1133: return new AbstractSet<Map.Entry<String, List<String>>>() {
1134: public int size() {
1135: return keys.length;
1136: }
1137:
1138: public Iterator<Map.Entry<String, List<String>>> iterator() {
1139: return new Iterator<Map.Entry<String, List<String>>>() {
1140: private int i = 0;
1141:
1142: public boolean hasNext() {
1143: return i < keys.length;
1144: }
1145:
1146: public Map.Entry<String, List<String>> next() {
1147: if (i >= keys.length) {
1148: throw new ArrayIndexOutOfBoundsException(
1149: i);
1150: }
1151: final int j = i++;
1152: return new Map.Entry<String, List<String>>() {
1153: public String getKey() {
1154: return keys[j];
1155: }
1156:
1157: public List<String> getValue() {
1158: return Collections
1159: .singletonList(values[j]);
1160: }
1161:
1162: public List<String> setValue(
1163: List<String> value) {
1164: throw new UnsupportedOperationException();
1165: }
1166: };
1167: }
1168:
1169: public void remove() {
1170: throw new UnsupportedOperationException();
1171: }
1172: };
1173: }
1174: };
1175: }
1176:
1177: public boolean containsKey(Object key) {
1178: for (int i = 0; i < keys.length; i++) {
1179: if (key.equals(keys[i])) {
1180: return true;
1181: }
1182: }
1183: return false;
1184: }
1185:
1186: public List<String> get(String key) {
1187: for (int i = 0; i < keys.length; i++) {
1188: if (key.equals(keys[i])) {
1189: return Collections.singletonList(values[i]);
1190: }
1191: }
1192: return null;
1193: }
1194: }
1195: }
|