001: /*
002: * Copyright (c) 1998 - 2005 Versant Corporation
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * Versant Corporation - initial API and implementation
010: */
011: package com.versant.core.util;
012:
013: import com.versant.core.util.classhelper.ClassHelper;
014:
015: import java.util.*;
016: import java.lang.reflect.Method;
017: import java.lang.reflect.InvocationTargetException;
018: import java.lang.reflect.Field;
019: import java.lang.reflect.Modifier;
020: import java.io.PrintStream;
021: import java.io.IOException;
022:
023: import com.versant.core.common.BindingSupportImpl;
024: import com.versant.core.common.Debug;
025:
026: /**
027: * Static utility methods for working with java beans.
028: */
029: public class BeanUtils {
030:
031: private BeanUtils() {
032: }
033:
034: private static final HashMap PRIMITIVE_NAME_MAP = new HashMap(17);
035:
036: static {
037: PRIMITIVE_NAME_MAP.put(Long.TYPE.getName(), Long.TYPE);
038: PRIMITIVE_NAME_MAP.put(Integer.TYPE.getName(), Integer.TYPE);
039: PRIMITIVE_NAME_MAP.put(Short.TYPE.getName(), Short.TYPE);
040: PRIMITIVE_NAME_MAP.put(Byte.TYPE.getName(), Byte.TYPE);
041: PRIMITIVE_NAME_MAP.put(Boolean.TYPE.getName(), Boolean.TYPE);
042: PRIMITIVE_NAME_MAP
043: .put(Character.TYPE.getName(), Character.TYPE);
044: PRIMITIVE_NAME_MAP.put(Float.TYPE.getName(), Float.TYPE);
045: PRIMITIVE_NAME_MAP.put(Double.TYPE.getName(), Double.TYPE);
046:
047: PRIMITIVE_NAME_MAP
048: .put(Long.TYPE.getName() + "[]", Long[].class);
049: PRIMITIVE_NAME_MAP.put(Integer.TYPE.getName() + "[]",
050: Integer[].class);
051: PRIMITIVE_NAME_MAP.put(Short.TYPE.getName() + "[]",
052: Short[].class);
053: PRIMITIVE_NAME_MAP
054: .put(Byte.TYPE.getName() + "[]", Byte[].class);
055: PRIMITIVE_NAME_MAP.put(Boolean.TYPE.getName() + "[]",
056: Boolean[].class);
057: PRIMITIVE_NAME_MAP.put(Character.TYPE.getName() + "[]",
058: Character[].class);
059: PRIMITIVE_NAME_MAP.put(Float.TYPE.getName() + "[]",
060: Float[].class);
061: PRIMITIVE_NAME_MAP.put(Double.TYPE.getName() + "[]",
062: Double[].class);
063:
064: }
065:
066: private static final Class[] STRING_ARGS = new Class[] { String.class };
067: private static final Class[] INT_ARGS = new Class[] { Integer.TYPE };
068: private static final Class[] BOOLEAN_ARGS = new Class[] { Boolean.TYPE };
069:
070: private static final String DEFAULT_PROP_FILE = "versant.properties";
071:
072: /**
073: * Find and set all properties or public fields on bean from key/value
074: * pairs in props. Only int, String and boolean properties and fields may
075: * be set. This is a NOP if props or bean is null.
076: *
077: * @exception IllegalArgumentException if any are invalid
078: */
079: public static void setProperties(Object bean, Map props)
080: throws IllegalArgumentException {
081: if (props == null || bean == null)
082: return;
083: Class cls = bean.getClass();
084: Object[] args = new Object[1];
085: for (Iterator i = props.entrySet().iterator(); i.hasNext();) {
086: Map.Entry e = (Map.Entry) i.next();
087: String prop = (String) e.getKey();
088: String value = (String) e.getValue();
089: Field f = findField(prop, cls, value, args);
090: if (f != null) {
091: try {
092: f.set(bean, args);
093: } catch (Throwable x) {
094: if (x instanceof InvocationTargetException) {
095: x = ((InvocationTargetException) x)
096: .getTargetException();
097: }
098: throw BindingSupportImpl.getInstance()
099: .illegalArgument(
100: x.getClass() + ": "
101: + x.getMessage());
102: }
103: }
104: Method m = findSetMethod(prop, cls, value, args);
105: try {
106: m.invoke(bean, args);
107: } catch (IllegalArgumentException x) {
108: throw x;
109: } catch (Throwable x) {
110: if (x instanceof InvocationTargetException) {
111: x = ((InvocationTargetException) x)
112: .getTargetException();
113: }
114: throw BindingSupportImpl.getInstance().illegalArgument(
115: x.getClass() + ": " + x.getMessage());
116: }
117: }
118: }
119:
120: /**
121: * Set a property on bean. Only int, String and boolean properties may be
122: * set.
123: * @exception IllegalArgumentException if invalid
124: */
125: public static void setProperty(Object bean, String prop,
126: String value) throws IllegalArgumentException {
127: Class cls = bean.getClass();
128: Object[] args = new Object[1];
129: Method m = findSetMethod(prop, cls, value, args);
130: try {
131: m.invoke(bean, args);
132: } catch (IllegalArgumentException x) {
133: throw x;
134: } catch (Throwable x) {
135: if (x instanceof InvocationTargetException) {
136: x = ((InvocationTargetException) x)
137: .getTargetException();
138: }
139: throw BindingSupportImpl.getInstance().illegalArgument(
140: x.getClass() + ": " + x.getMessage());
141: }
142: }
143:
144: private static Field findField(String prop, Class cls,
145: String value, Object[] args) {
146: try {
147: Field f = cls.getField(prop);
148: Class t = f.getType();
149: if (t == Integer.TYPE || t == Integer.class) {
150: args[0] = toInteger(value, prop);
151: } else if (t == Boolean.TYPE || t == Boolean.class) {
152: args[0] = toBoolean(value, prop);
153: } else if (t == String.class) {
154: args[0] = value;
155: } else {
156: throw BindingSupportImpl.getInstance().internal(
157: "Unsupported field type: " + f);
158: }
159: return f;
160: } catch (NoSuchFieldException e) {
161: return null;
162: }
163: }
164:
165: private static Method findSetMethod(String prop, Class cls,
166: String value, Object[] args) {
167: String name = "set" + Character.toUpperCase(prop.charAt(0))
168: + prop.substring(1);
169: Method m;
170: try {
171: try {
172: m = cls.getMethod(name, STRING_ARGS);
173: args[0] = value;
174: } catch (NoSuchMethodException x) {
175: try {
176: m = cls.getMethod(name, INT_ARGS);
177: args[0] = toInteger(value, prop);
178: } catch (NoSuchMethodException xx) {
179: m = cls.getMethod(name, BOOLEAN_ARGS);
180: args[0] = toBoolean(value, prop);
181: }
182: }
183: } catch (NoSuchMethodException x) {
184: String pos = getClosestProperty(prop, cls);
185: String all = getAllPropsFormatted(cls);
186: throw BindingSupportImpl
187: .getInstance()
188: .illegalArgument(
189: "An invalid property '"
190: + prop
191: + "' was set on "
192: + cls.getName()
193: + ". "
194: + (pos != null ? ("The property closely matches '"
195: + pos + "'. ")
196: : "")
197: + (all != null ? ("All possible properties are: " + all)
198: : ""));
199: }
200: return m;
201: }
202:
203: private static Boolean toBoolean(String value, String prop) {
204: if (value.equals("true")) {
205: return Boolean.TRUE;
206: } else if (value.equals("false")) {
207: return Boolean.FALSE;
208: } else {
209: throw BindingSupportImpl.getInstance().illegalArgument(
210: "Expected true or false value for '" + prop
211: + "' got '" + value + "'");
212: }
213: }
214:
215: private static Integer toInteger(String value, String prop) {
216: try {
217: return new Integer(value);
218: } catch (NumberFormatException xx) {
219: throw BindingSupportImpl.getInstance().illegalArgument(
220: "Expected int value for '" + prop + "' got '"
221: + value + "'");
222: }
223: }
224:
225: /**
226: * Process a list of command line arguments in [-propertyname value]+
227: * format setting each on the bean.
228: * @param args Command line arguments as passed to a main method
229: * @param expected Names of allowed properties
230: * @exception IllegalArgumentException if args are invalid
231: */
232: public static void setCommandLineArgs(Object bean, String[] args,
233: String[] expected) throws IllegalArgumentException {
234:
235: int n = args.length;
236: Object[] v = new Object[1];
237: for (int i = 0; i < n; i++) {
238: String s = args[i];
239: if (!s.startsWith("-")) {
240: throw BindingSupportImpl.getInstance().illegalArgument(
241: "Invalid argument: " + s);
242: }
243: String prop = s.substring(1);
244: int j;
245: for (j = expected.length - 1; j >= 0; j--) {
246: if (expected[j].equals(prop))
247: break;
248: }
249: if (j < 0) {
250: throw BindingSupportImpl.getInstance().illegalArgument(
251: "Invalid argument: " + prop);
252: }
253: if (++i == n) {
254: throw BindingSupportImpl.getInstance().illegalArgument(
255: "Expected value for " + prop);
256: }
257: String value = args[i];
258: Method m = findSetMethod(prop, bean.getClass(), value, v);
259: try {
260: m.invoke(bean, v);
261: } catch (IllegalArgumentException x) {
262: throw x;
263: } catch (Throwable x) {
264: if (x instanceof InvocationTargetException) {
265: x = ((InvocationTargetException) x)
266: .getTargetException();
267: }
268: throw BindingSupportImpl.getInstance().illegalArgument(
269: "Invalid value for " + prop + ": "
270: + x.getClass().getName() + ": "
271: + x.getMessage());
272: }
273: }
274: }
275:
276: /**
277: * Add any properties from a semicolon delimited String ps to props.
278: */
279: public static void parseProperties(String ps, Properties props) {
280: StringTokenizer t = new StringTokenizer(ps, "=", false);
281: for (;;) {
282: String key;
283: try {
284: key = t.nextToken("=");
285: if (key.startsWith(";"))
286: key = key.substring(1);
287: } catch (NoSuchElementException e) {
288: break;
289: }
290: try {
291: String value = t.nextToken(";").substring(1);
292: props.put(key, value);
293: } catch (NoSuchElementException e) {
294: throw BindingSupportImpl.getInstance().runtime(
295: "Expected semicolon delimited property=value pairs: '"
296: + ps + "'");
297: }
298: }
299: }
300:
301: /**
302: * Parse semicolon delimited properties from props and set them on bean.
303: * This can handle String, int and boolean properties.
304: * @exception IllegalArgumentException on errors
305: */
306: public static void parseProperties(String props, Object bean) {
307: if (props == null || props.length() == 0)
308: return;
309: Class cls = bean.getClass();
310: Object[] args = new Object[1];
311: int last = 0;
312: for (;;) {
313: int i = props.indexOf('=', last);
314: if (i < 0) {
315: throw BindingSupportImpl.getInstance().illegalArgument(
316: "Expected property name at position " + last
317: + ": " + props);
318: }
319: String key = props.substring(last, i);
320: int j = props.indexOf(';', ++i);
321: String value;
322: if (j < 0)
323: value = props.substring(i);
324: else
325: value = props.substring(i, j);
326: Method m = findSetMethod(key, cls, value, args);
327: try {
328: m.invoke(bean, args);
329: } catch (Exception e) {
330: Throwable t;
331: if (e instanceof InvocationTargetException) {
332: t = ((InvocationTargetException) e)
333: .getTargetException();
334: } else {
335: t = e;
336: }
337: throw BindingSupportImpl.getInstance().runtime(
338: "Error setting property '" + key + "' to '"
339: + value + "'", t);
340: }
341: if (j < 0)
342: break;
343: last = j + 1;
344: }
345: }
346:
347: /**
348: * Convert a primitive name (int, byte etc.) to a class or null if the
349: * name is not a primitive.
350: */
351: public static Class toClass(String primitiveName) {
352: return (Class) PRIMITIVE_NAME_MAP.get(primitiveName);
353: }
354:
355: /**
356: * Load the class with name using loaded. The name may be a primitive
357: * (int, byte etc) or a single dimensional array (e.g. int[]).
358: */
359: public static Class loadClass(String name, boolean initialize,
360: ClassLoader loader) throws ClassNotFoundException {
361: Class ans = toClass(name);
362: if (ans == null) {
363: int i = name.indexOf("[]");
364: if (i >= 0) {
365: name = "[L" + name.substring(0, i);
366: }
367: ans = ClassHelper.get().classForName(name, initialize,
368: loader);
369: }
370: return ans;
371: }
372:
373: /**
374: * Create a new instance of cname using loader. It must be an instance of
375: * mustBe.
376: */
377: public static Object newInstance(String cname, ClassLoader loader,
378: Class mustBe) {
379: try {
380: Class cls = ClassHelper.get().classForName(cname, true,
381: loader);
382: if (!mustBe.isAssignableFrom(cls)) {
383: throw BindingSupportImpl.getInstance().runtime(
384: cname + " is not a " + mustBe.getName());
385: }
386: return cls.newInstance();
387: } catch (Exception e) {
388: if (BindingSupportImpl.getInstance().isOwnException(e)) {
389: throw (RuntimeException) e;
390: }
391: throw BindingSupportImpl.getInstance().runtime(
392: "Unable to create instance of " + cname + ": "
393: + e.getMessage(), e);
394: }
395: }
396:
397: /**
398: * Get the value of a property of bean.
399: */
400: public static Object getPropertyValue(Object bean, String property)
401: throws Exception {
402: Class cls = bean.getClass();
403: property = Character.toUpperCase(property.charAt(0))
404: + property.substring(1);
405: Method m;
406: try {
407: m = cls.getMethod("get" + property, null);
408: } catch (NoSuchMethodException e) {
409: m = cls.getMethod("is" + property, null);
410: }
411: return m.invoke(bean, null);
412: }
413:
414: /**
415: * Fill o by introspecting all of its public non-static, non-final fields
416: * that are int, Integer, boolean, Boolean or String and looking for
417: * properties named prefix + fieldname to populate them.
418: */
419: public static void fillFields(Object o, String prefix, Map props,
420: Set fieldsToIgnore) {
421: Field[] a = o.getClass().getFields();
422: for (int i = 0; i < a.length; i++) {
423: Field f = a[i];
424: int m = f.getModifiers();
425: if (Modifier.isFinal(m) || Modifier.isStatic(m)
426: || !isSupportedFieldType(f.getType())) {
427: continue;
428: }
429: String name = f.getName();
430: if (fieldsToIgnore != null && fieldsToIgnore.contains(name)) {
431: continue;
432: }
433: String prop = prefix + name;
434: String value = (String) props.get(prop);
435: if (value == null) {
436: continue;
437: }
438: setFieldValue(f, o, value, prop);
439: }
440: }
441:
442: public static boolean isSupportedFieldType(Class t) {
443: return t == Integer.TYPE || t == Integer.class
444: || t == Boolean.TYPE || t == Boolean.class
445: || t == String.class;
446: }
447:
448: /**
449: * Set field f on o converting value to the correct type for the field.
450: * Fields of int, Integer, boolean, Boolean and String are suppported.
451: */
452: private static void setFieldValue(Field f, Object o, String value,
453: String name) {
454: Class t = f.getType();
455: Object arg;
456: if (t == Integer.TYPE || t == Integer.class) {
457: arg = toInteger(value, name);
458: } else if (t == Boolean.TYPE || t == Boolean.class) {
459: arg = toBoolean(value, name);
460: } else if (t == String.class) {
461: arg = value;
462: } else {
463: throw BindingSupportImpl.getInstance().illegalArgument(
464: "Unsupported field type: " + f);
465: }
466: try {
467: f.set(o, arg);
468: } catch (Throwable x) {
469: if (x instanceof InvocationTargetException) {
470: x = ((InvocationTargetException) x)
471: .getTargetException();
472: }
473: throw BindingSupportImpl.getInstance().illegalArgument(
474: x.getClass() + ": " + x.getMessage());
475: }
476: }
477:
478: /**
479: * The result of a call to processCmdline.
480: */
481: public static class CmdLineResult {
482: public HashSet fieldsFilled = new HashSet();
483: public Properties properties;
484: }
485:
486: /**
487: * <p>Process command line arguments. Arguments starting with a dash are
488: * expected to match the names of public fields in o (like
489: * {@link #fillFields}) with non-boolean fields requiring an argument.
490: * If a boolean field does not have an argument then it is set to true.</p>
491: *
492: * <p>If hasProps is true then there is an implicit -p arguement to
493: * specify the resource name or filename of a properties file to load and
494: * return. The default name is versant.properties. The argument is first
495: * treated as a file and if this fails then as a resource. System
496: * properties starting with "versant." or "javax.jdo.option." are added
497: * after the properties are loaded. Finally command line arguments of the
498: * form key=value are added i.e. they override any previous value.</p>
499: *
500: * <p>Errors are printed to System.err followed by usage information and
501: * an IllegalArgumentException is thrown.</p>
502: *
503: * <p>If one of the args is /? or -help or --help then the usage is printed
504: * and an IllegalArgumentException is thrown.</p>
505: *
506: * @return The fieldsFilled set contains the names of all fields that were
507: * set from the command line. If hasProps is true then the properties field
508: * on the result holds all properties with overrides already applied
509: * otherwise it is null.
510: */
511: public static CmdLineResult processCmdLine(String[] args,
512: ClassLoader loader, Object o, String[] requiredFields,
513: String toolName, String toolDescription, boolean hasProps) {
514: try {
515: CmdLineResult res = new CmdLineResult();
516: Properties overrides = hasProps ? new Properties() : null;
517: String propFileName = DEFAULT_PROP_FILE;
518: for (int i = 0; i < args.length;) {
519: String arg = args[i++];
520: if (arg.equals("-p")) {
521: if (i >= args.length) {
522: throw BindingSupportImpl
523: .getInstance()
524: .illegalArgument(
525: "Expected file or resource name for -p");
526: }
527: propFileName = args[i++];
528: } else if (arg.equals("/?") || arg.equals("-help")
529: || arg.equals("--help")) {
530: throw BindingSupportImpl.getInstance()
531: .illegalArgument("");
532: } else if (arg.startsWith("-")) {
533: String key = arg.substring(1);
534: if (key.length() == 0) {
535: throw BindingSupportImpl
536: .getInstance()
537: .illegalArgument(
538: "Expected name of argument after '-'");
539: }
540: try {
541: Field f = o.getClass().getField(key);
542: Class t = f.getType();
543: int m = f.getModifiers();
544: if (Modifier.isFinal(m) || Modifier.isStatic(m)
545: || !isSupportedFieldType(t)) {
546: throw new NoSuchFieldException();
547: }
548: String value = i < args.length ? args[i] : null;
549: if (value.startsWith("-")) {
550: value = null;
551: }
552: if (value == null) {
553: if (t == Boolean.class || t == Boolean.TYPE) {
554: value = "true";
555: } else {
556: throw BindingSupportImpl.getInstance()
557: .illegalArgument(
558: "Expected value for "
559: + arg);
560: }
561: } else {
562: ++i;
563: }
564: setFieldValue(f, o, value, arg);
565: res.fieldsFilled.add(f.getName());
566: } catch (NoSuchFieldException e) {
567: throw BindingSupportImpl.getInstance()
568: .illegalArgument(
569: "Unknown option: " + arg);
570: }
571: } else {
572: int pos = hasProps ? arg.indexOf('=') : -1;
573: if (pos <= 0) {
574: throw BindingSupportImpl.getInstance()
575: .illegalArgument(
576: "Invalid argument: " + arg);
577: }
578: String key = arg.substring(0, i);
579: String value = arg.substring(i + 1);
580: overrides.put(key, value);
581: }
582: }
583: if (hasProps) {
584: Properties p;
585: try {
586: p = PropertiesLoader.loadProperties(propFileName);
587: if (p == null) {
588: p = PropertiesLoader.loadProperties(loader,
589: propFileName);
590: if (p == null) {
591: throw BindingSupportImpl.getInstance()
592: .illegalArgument(
593: "File or resource not found: '"
594: + propFileName
595: + "'");
596: }
597: }
598: } catch (IOException e) {
599: throw BindingSupportImpl.getInstance()
600: .illegalArgument(
601: "Error loading " + propFileName);
602: }
603: Properties sp = System.getProperties();
604: for (Iterator i = sp.keySet().iterator(); i.hasNext();) {
605: String key = (String) i.next();
606: if (key.startsWith("versant.")
607: || key.startsWith("javax.jdo.option.")) {
608: String value = sp.getProperty(key);
609: System.err.println("Using system property: "
610: + key + "=" + value);
611: p.put(key, value);
612: }
613: }
614: for (Iterator i = overrides.keySet().iterator(); i
615: .hasNext();) {
616: String key = (String) i.next();
617: String value = overrides.getProperty(key);
618: System.err.println("Command line override: " + key
619: + "=" + value);
620: p.put(key, value);
621: }
622: res.properties = p;
623: }
624: return res;
625: } catch (IllegalArgumentException e) {
626: System.err.println(e.getMessage());
627: System.err.println();
628: printUsage(o, requiredFields, toolName, toolDescription,
629: hasProps, System.err);
630: throw e;
631: }
632: }
633:
634: /**
635: * <p>Print command line usage information by introspecting o to find all
636: * of its public non-static, non-final fields that are int, Integer,
637: * boolean, Boolean or String. For each field a public static final
638: * String field named HELP_fieldname is used to provided a description
639: * of the field if present. The values of the fields in o are assumed
640: * to be the defaults.</p>
641: *
642: * <p>If hasProps is true the information on how to specify key=value
643: * properties is also printed.</p>
644: */
645: public static void printUsage(Object o, String[] requiredFields,
646: String toolName, String toolDescription, boolean hasProps,
647: PrintStream out) {
648: Field[] all = o.getClass().getFields();
649:
650: // find all of the help text
651: HashMap helpMap = new HashMap();
652: for (int i = 0; i < all.length; i++) {
653: Field f = all[i];
654: int m = f.getModifiers();
655: if (!(Modifier.isFinal(m) && Modifier.isStatic(m))
656: || f.getType() != String.class) {
657: continue;
658: }
659: String name = f.getName();
660: if (name.startsWith("HELP_")) {
661: try {
662: helpMap.put(name.substring(5), f.get(null));
663: } catch (IllegalAccessException e) {
664: throw BindingSupportImpl.getInstance().internal(
665: e.toString(), e);
666: }
667: }
668: }
669:
670: // map all the options to their default values
671: HashMap defaults = new HashMap();
672: ArrayList options = new ArrayList();
673: HashMap fields = new HashMap();
674: int longestOp = 0;
675: if (hasProps) {
676: String s = "-p " + DEFAULT_PROP_FILE;
677: longestOp = s.length();
678: }
679: for (int i = 0; i < all.length; i++) {
680: Field f = all[i];
681: int m = f.getModifiers();
682: if (Modifier.isFinal(m) || Modifier.isStatic(m)
683: || !isSupportedFieldType(f.getType())) {
684: continue;
685: }
686: String op = f.getName();
687: options.add(op);
688: fields.put(op, f);
689: String def;
690: try {
691: Object v = f.get(o);
692: def = v == null ? null : v.toString();
693: } catch (IllegalAccessException e) {
694: throw BindingSupportImpl.getInstance().internal(
695: e.toString(), e);
696: }
697: String s = "-" + op;
698: if (def != null) {
699: defaults.put(op, def);
700: s = s + " " + def;
701: }
702: if (s.length() > longestOp) {
703: longestOp = s.length();
704: }
705: }
706:
707: // now print everything
708: out.println("Versant Open Access " + Debug.VERSION + " "
709: + toolName);
710: out.println("Usage: " + toolName
711: + (hasProps ? " -p <property file or resource>" : "")
712: + " [OPTION] ... "
713: + (hasProps ? " [property=value] ..." : ""));
714: out.println(toolDescription);
715: out.println();
716: HashSet requiredSet = new HashSet();
717: int rfc = requiredFields == null ? 0 : requiredFields.length;
718: if (rfc > 0) {
719: requiredSet.addAll(Arrays.asList(requiredFields));
720: out.println("Required arguements:");
721: for (int i = 0; i < requiredFields.length; i++) {
722: printOption(requiredFields[i], fields, null, helpMap,
723: longestOp, out);
724: }
725: }
726: int n = options.size();
727: if (n - rfc > 0 || hasProps) {
728: out.println("Optional arguements with default values:");
729: if (hasProps) {
730: printOption(
731: "p",
732: DEFAULT_PROP_FILE,
733: "Name of file or resource to load properties from",
734: longestOp, out);
735: }
736: for (int i = 0; i < n; i++) {
737: String op = (String) options.get(i);
738: if (!requiredSet.contains(op)) {
739: printOption(op, fields, defaults, helpMap,
740: longestOp, out);
741: }
742: }
743: out.println();
744: }
745: if (hasProps) {
746: out
747: .println("Use property=value pairs to override properties "
748: + "including properties set from System properties.");
749: out.println();
750: }
751: }
752:
753: private static void printOption(String op, HashMap fields,
754: HashMap defaults, HashMap helpMap, int longestOp,
755: PrintStream out) {
756: Field f = (Field) fields.get(op);
757: String def = defaults == null ? null : (String) defaults
758: .get(op);
759: String help = (String) helpMap.get(op);
760: if (help == null) {
761: Class t = f.getType();
762: if (t == Integer.class || t == Integer.TYPE) {
763: help = "int";
764: } else if (t == Boolean.class || t == Boolean.TYPE) {
765: help = "true|false";
766: } else if (t == String.class) {
767: help = "string";
768: } else {
769: help = "...";
770: }
771: }
772: printOption(op, def, help, longestOp, out);
773: }
774:
775: private static void printOption(String op, String def, String help,
776: int longestOp, PrintStream out) {
777: StringBuffer b = new StringBuffer();
778: b.append('-');
779: b.append(op);
780: if (def != null) {
781: b.append(' ');
782: b.append(def);
783: }
784: for (int j = longestOp - b.length() + 2; j > 0; j--) {
785: b.append(' ');
786: }
787: b.append(help);
788: out.println(b.toString());
789: }
790:
791: /**
792: * Get the closest property that can be set to this s, or return null, if
793: * they are too diffrent.
794: */
795: private static String getClosestProperty(String s, Class cls) {
796: Method[] methods = cls.getMethods();
797: int j = 0;
798: for (int i = 0; i < methods.length; i++) {
799: String methodName = methods[i].getName();
800: if (methodName.startsWith("set") && methodName.length() > 3) {
801: Class params[] = methods[i].getParameterTypes();
802: if (params.length != 0 && !params[0].isArray()) {
803: j++;
804: }
805: }
806:
807: }
808: if (j == 0) {
809: return null;
810: }
811: String[] targets = new String[j];
812: j = 0;
813: for (int i = 0; i < methods.length; i++) {
814: String methodName = methods[i].getName();
815: if (methodName.startsWith("set") && methodName.length() > 3) {
816: Class params[] = methods[i].getParameterTypes();
817: if (params.length != 0 && !params[0].isArray()) {
818: targets[j] = Character.toLowerCase(methodName
819: .charAt(3))
820: + (methodName.length() > 4 ? methodName
821: .substring(4) : "");
822: j++;
823: }
824: }
825: }
826:
827: return getClosest(s, targets, 0.25f);
828: }
829:
830: /**
831: * Does this class contain this property
832: */
833: private static boolean containsProperties(Class cls, String prop) {
834: return getAllProperties(cls).contains(prop);
835: }
836:
837: /**
838: * Get all the valid properties for this class.
839: */
840: public static Set getAllProperties(Class cls) {
841: Method[] methods = cls.getMethods();
842: Set set = new HashSet();
843: for (int i = 0; i < methods.length; i++) {
844: String methodName = methods[i].getName();
845: if (methodName.startsWith("set") && methodName.length() > 3) {
846: String prop = Character.toLowerCase(methodName
847: .charAt(3))
848: + (methodName.length() > 4 ? methodName
849: .substring(4) : "");
850: set.add(prop);
851: }
852:
853: }
854:
855: Field fields[] = cls.getFields();
856: for (int i = 0; i < fields.length; i++) {
857: String fieldName = fields[i].getName();
858: String prop = Character.toLowerCase(fieldName.charAt(0))
859: + (fieldName.length() > 1 ? fieldName.substring(1)
860: : "");
861:
862: set.add(prop);
863: }
864: return set;
865:
866: }
867:
868: /**
869: * format the properties.
870: */
871: private static String getAllPropsFormatted(Class cls) {
872: Set set = getAllProperties(cls);
873: if (set.isEmpty()) {
874: return null;
875: } else {
876: ArrayList list = new ArrayList(set);
877: Collections.sort(list);
878: StringBuffer buff = new StringBuffer();
879: for (Iterator iter = list.iterator(); iter.hasNext();) {
880: String name = (String) iter.next();
881:
882: buff.append(name);
883: buff.append(';');
884: }
885: return buff.toString();
886: }
887: }
888:
889: /**
890: * This method uses a Levenshtein Distance algorithm to find the closest
891: * match.
892: */
893: private static String getClosest(String s, String[] targets,
894: float diffs) {
895: if (s == null || targets == null) {
896: return null;
897: }
898:
899: HashMap map = new HashMap();
900: for (int i = 0; i < targets.length; i++) {
901: map.put(targets[i], targets[i].toUpperCase());
902: }
903: s = s.toUpperCase();
904: int n = s.length(); // length of s
905:
906: float srcLenght = (float) n;
907:
908: float currentWeight = 1;
909: String currentString = null;
910: Set set = map.keySet();
911: for (Iterator iter = set.iterator(); iter.hasNext();) {
912: String origianal = (String) iter.next();
913: String t = (String) map.get(origianal);
914:
915: int m = t.length(); // length of t
916:
917: int p[] = new int[n + 1]; //'previous' cost array, horizontally
918: int d[] = new int[n + 1]; // cost array, horizontally
919: int _d[]; //placeholder to assist in swapping p and d
920:
921: // indexes into strings s and t
922: int i; // iterates through s
923: int j; // iterates through t
924:
925: char t_j; // jth character of t
926:
927: int cost; // cost
928:
929: for (i = 0; i <= n; i++) {
930: p[i] = i;
931: }
932:
933: for (j = 1; j <= m; j++) {
934: t_j = t.charAt(j - 1);
935: d[0] = j;
936:
937: for (i = 1; i <= n; i++) {
938: cost = s.charAt(i - 1) == t_j ? 0 : 1;
939: // minimum of cell to the left+1, to the top+1, diagonally left and up +cost
940: d[i] = Math.min(Math.min(d[i - 1] + 1, p[i] + 1),
941: p[i - 1] + cost);
942: }
943:
944: // copy current distance counts to 'previous row' distance counts
945: _d = p;
946: p = d;
947: d = _d;
948: }
949:
950: // our last action in the above loop was to switch d and p, so p now
951: // actually has the most recent cost counts
952: float diff = ((float) p[n]) / srcLenght;
953:
954: if (currentWeight > diff) {
955: currentWeight = diff;
956: currentString = origianal;
957: }
958: }
959:
960: if (currentWeight <= diffs) {
961: return currentString;
962: } else {
963: return null;
964: }
965: }
966: }
|