001: /*
002: Copyright (c) 2007, Dennis M. Sosnoski
003: All rights reserved.
004:
005: Redistribution and use in source and binary forms, with or without modification,
006: are permitted provided that the following conditions are met:
007:
008: * Redistributions of source code must retain the above copyright notice, this
009: list of conditions and the following disclaimer.
010: * Redistributions in binary form must reproduce the above copyright notice,
011: this list of conditions and the following disclaimer in the documentation
012: and/or other materials provided with the distribution.
013: * Neither the name of JiBX nor the names of its contributors may be used
014: to endorse or promote products derived from this software without specific
015: prior written permission.
016:
017: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
018: ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
019: WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
020: DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
021: ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
022: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
023: LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
024: ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
026: SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027: */
028:
029: package org.jibx.binding.generator;
030:
031: import java.io.File;
032: import java.io.IOException;
033: import java.lang.reflect.Field;
034: import java.lang.reflect.InvocationTargetException;
035: import java.lang.reflect.Method;
036: import java.util.ArrayList;
037: import java.util.Arrays;
038: import java.util.HashMap;
039: import java.util.Iterator;
040: import java.util.List;
041: import java.util.Map;
042:
043: import org.jibx.binding.Utility;
044: import org.jibx.binding.classes.ClassCache;
045: import org.jibx.binding.classes.ClassFile;
046: import org.jibx.binding.model.IClassLocator;
047: import org.jibx.runtime.IUnmarshallingContext;
048: import org.jibx.runtime.JiBXException;
049: import org.jibx.ws.wsdl.ClassSourceLocator;
050:
051: /**
052: * Command line processor for customizable tools.
053: */
054: public abstract class CustomizationCommandLineBase {
055: /** Array of method parameter classes for single String parameter. */
056: public static final Class[] STRING_PARAMETER_ARRAY = new Class[] { String.class };
057:
058: /** Array of classes for String and unmarshaller parameters. */
059: public static final Class[] STRING_UNMARSHALLER_PARAMETER_ARRAY = new Class[] {
060: String.class, IUnmarshallingContext.class };
061:
062: /** Ordered array of usage lines. */
063: protected static final String[] BASE_USAGE_LINES = new String[] {
064: " -f path input binding customizations file",
065: " -p path class loading path component",
066: " -s path source path component",
067: " -t path target directory for generated output (default is"
068: + " current directory)",
069: " -v verbose output flag" };
070:
071: /** List of source paths. */
072: private List m_sourcePaths;
073:
074: /** List of specified classes or files. */
075: private List m_extraArgs;
076:
077: /** Target directory for output. */
078: private File m_generateDirectory;
079:
080: /**
081: * Process command line arguments array.
082: *
083: * @param args
084: * @return <code>true</code> if valid, <code>false</code> if not
085: * @throws JiBXException
086: * @throws IOException
087: */
088: public boolean processArgs(String[] args) throws JiBXException,
089: IOException {
090: boolean verbose = false;
091: String custom = null;
092: String genpath = null;
093: ArrayList paths = new ArrayList();
094: m_sourcePaths = new ArrayList();
095: Map overrides = new HashMap();
096: ArgList alist = new ArgList(args);
097: while (alist.hasNext()) {
098: String arg = alist.next();
099: if ("-f".equalsIgnoreCase(arg)) {
100: custom = alist.next();
101: } else if ("-p".equalsIgnoreCase(arg)) {
102: paths.add(alist.next());
103: } else if ("-s".equalsIgnoreCase(arg)) {
104: m_sourcePaths.add(alist.next());
105: } else if ("-t".equalsIgnoreCase(arg)) {
106: genpath = alist.next();
107: } else if ("-v".equalsIgnoreCase(arg)) {
108: verbose = true;
109: } else if (arg.startsWith("--") && arg.length() > 2
110: && Character.isLetter(arg.charAt(2))) {
111: if (!putKeyValue(arg.substring(2), overrides)) {
112: alist.setValid(false);
113: }
114: } else if (!checkParameter(alist)) {
115: if (arg.startsWith("-")) {
116: System.err.println("Unknown option flag '" + arg
117: + '\'');
118: alist.setValid(false);
119: } else {
120: break;
121: }
122: }
123: }
124:
125: // collect the class names to be generated
126: m_extraArgs = new ArrayList();
127: m_extraArgs.add(alist.current());
128: while (alist.hasNext()) {
129: String arg = alist.next();
130: if (arg.startsWith("-")) {
131: System.err
132: .println("Command line options must precede all other arguments: error on '"
133: + arg + '\'');
134: ;
135: alist.setValid(false);
136: break;
137: } else {
138: m_extraArgs.add(arg);
139: }
140: }
141:
142: // check for valid command line arguments
143: if (alist.isValid()) {
144:
145: // set up path and binding lists
146: String[] vmpaths = Utility.getClassPaths();
147: for (int i = 0; i < vmpaths.length; i++) {
148: paths.add(vmpaths[i]);
149: }
150:
151: // set output directory
152: if (genpath == null) {
153: m_generateDirectory = new File(".");
154: } else {
155: m_generateDirectory = new File(genpath);
156: }
157: if (!m_generateDirectory.exists()) {
158: m_generateDirectory.mkdirs();
159: }
160: if (!m_generateDirectory.canWrite()) {
161: System.err.println("Target directory "
162: + m_generateDirectory.getPath()
163: + " is not writable");
164: alist.setValid(false);
165: } else {
166:
167: // report on the configuration
168: if (verbose) {
169: System.out.println("Using class loading paths:");
170: for (int i = 0; i < paths.size(); i++) {
171: System.out.println(" " + paths.get(i));
172: }
173: System.out.println("Using source loading paths:");
174: for (int i = 0; i < m_sourcePaths.size(); i++) {
175: System.out.println(" " + m_sourcePaths.get(i));
176: }
177: verboseDetails();
178: System.out.println("Output to directory "
179: + m_generateDirectory);
180: }
181:
182: // set paths to be used for loading referenced classes
183: String[] parray = (String[]) paths
184: .toArray(new String[paths.size()]);
185: ClassCache.setPaths(parray);
186: ClassFile.setPaths(parray);
187:
188: // load customizations and apply overrides
189: String[] spaths = (String[]) m_sourcePaths
190: .toArray(new String[m_sourcePaths.size()]);
191: loadCustomizations(custom, new ClassSourceLocator(
192: spaths));
193: Map unknowns = applyOverrides(overrides);
194: if (!unknowns.isEmpty()) {
195: for (Iterator iter = unknowns.keySet().iterator(); iter
196: .hasNext();) {
197: String key = (String) iter.next();
198: System.err.println("Unknown override key '"
199: + key + '\'');
200: }
201: alist.setValid(false);
202: }
203: }
204:
205: } else {
206: printUsage();
207: }
208: return alist.isValid();
209: }
210:
211: /**
212: * Get generate directory.
213: *
214: * @return directory
215: */
216: public File getGeneratePath() {
217: return m_generateDirectory;
218: }
219:
220: /**
221: * Get extra arguments from command line. These extra arguments must follow
222: * all parameter flags.
223: *
224: * @return args
225: */
226: public List getExtraArgs() {
227: return m_extraArgs;
228: }
229:
230: /**
231: * Apply a key/value map to a customization object instance. This uses
232: * reflection to match the keys to either set methods (with names of the
233: * form setZZZText, or setZZZ, taking a single String parameter) or String
234: * fields (named m_ZZZ). The ZZZ in the names is based on the key name, with
235: * hyphenation converted to camel case (leading upper camel case, for the
236: * method names).
237: *
238: * @param map
239: * @param obj
240: * @return map for key/values not found in the supplied object
241: */
242: public static Map applyKeyValueMap(Map map, Object obj) {
243: Map missmap = new HashMap();
244: for (Iterator iter = map.keySet().iterator(); iter.hasNext();) {
245: String key = (String) iter.next();
246: String value = (String) map.get(key);
247: boolean fail = true;
248: Throwable t = null;
249: try {
250: StringBuffer buff = new StringBuffer(key);
251: for (int i = 0; i < buff.length(); i++) {
252: char chr = buff.charAt(i);
253: if (chr == '-') {
254: buff.deleteCharAt(i);
255: buff.setCharAt(i, Character.toUpperCase(buff
256: .charAt(i)));
257: }
258: }
259: String fname = "m_" + buff.toString();
260: buff
261: .setCharAt(0, Character.toUpperCase(buff
262: .charAt(0)));
263: String mname = "set" + buff.toString();
264: Method method = null;
265: Class clas = obj.getClass();
266: int argcnt = 1;
267: while (!clas.getName().equals("java.lang.Object")) {
268: try {
269: method = clas.getDeclaredMethod(mname + "Text",
270: STRING_UNMARSHALLER_PARAMETER_ARRAY);
271: argcnt = 2;
272: break;
273: } catch (NoSuchMethodException e) {
274: try {
275: method = clas.getDeclaredMethod(mname,
276: STRING_PARAMETER_ARRAY);
277: break;
278: } catch (NoSuchMethodException e1) {
279: clas = clas.getSuperclass();
280: }
281: }
282: }
283: if (method == null) {
284: clas = obj.getClass();
285: while (!clas.getName().equals("java.lang.Object")) {
286: try {
287: Field field = clas.getDeclaredField(fname);
288: try {
289: field.setAccessible(true);
290: } catch (SecurityException e) { /* deliberately empty */
291: }
292: String type = field.getType().getName();
293: if ("java.lang.String".equals(type)) {
294: field.set(obj, value);
295: fail = false;
296: } else if ("boolean".equals(type)
297: || "java.lang.Boolean".equals(type)) {
298: Boolean bval = null;
299: if ("true".equals(value)
300: || "1".equals(value)) {
301: bval = Boolean.TRUE;
302: } else if ("false".equals(value)
303: || "0".equals(value)) {
304: bval = Boolean.FALSE;
305: }
306: if (bval == null) {
307: throw new IllegalArgumentException(
308: "Unknown value '"
309: + value
310: + "' for boolean parameter "
311: + key);
312: }
313: field.set(obj, bval);
314: fail = false;
315: } else if ("[Ljava.lang.String;"
316: .equals(type)) {
317: try {
318: field
319: .set(
320: obj,
321: org.jibx.runtime.Utility
322: .deserializeTokenList(value));
323: fail = false;
324: } catch (JiBXException e) {
325: throw new IllegalArgumentException(
326: "Error processing list value + '"
327: + value + '\'');
328: }
329: } else {
330: throw new IllegalArgumentException(
331: "Cannot handle field of type "
332: + type);
333: }
334: break;
335: } catch (NoSuchFieldException e) {
336: clas = clas.getSuperclass();
337: }
338: }
339: } else {
340: try {
341: method.setAccessible(true);
342: } catch (SecurityException e) { /* deliberately empty */
343: }
344: Object[] args = new Object[argcnt];
345: args[0] = value;
346: method.invoke(obj, args);
347: fail = false;
348: }
349: } catch (IllegalAccessException e) {
350: t = e;
351: } catch (SecurityException e) {
352: t = e;
353: } catch (IllegalArgumentException e) {
354: t = e;
355: } catch (InvocationTargetException e) {
356: t = e;
357: } finally {
358: if (t != null) {
359: t.printStackTrace();
360: System.exit(1);
361: }
362: }
363: if (fail) {
364: missmap.put(key, value);
365: }
366: }
367: return missmap;
368: }
369:
370: /**
371: * Set a key=value definition in a map. This is a command line processing
372: * assist method that prints an error message directly if the expected
373: * format is not found.
374: *
375: * @param def
376: * @param map
377: * @return <code>true</code> if successful, <code>false</code> if error
378: */
379: public static boolean putKeyValue(String def, Map map) {
380: int split = def.indexOf('=');
381: if (split >= 0) {
382: String key = def.substring(0, split);
383: if (map.containsKey(key)) {
384: System.err.println("Repeated key item: '" + def + '\'');
385: return false;
386: } else {
387: map.put(key, def.substring(split + 1));
388: return true;
389: }
390: } else {
391: System.err
392: .println("Missing '=' in expected key=value item: '"
393: + def + '\'');
394: return false;
395: }
396: }
397:
398: /**
399: * Check extension parameter. This method may be overridden by subclasses
400: * to process parameters beyond those known to this base class.
401: *
402: * @param alist argument list
403: * @return <code>true</code> if parameter processed, <code>false</code> if
404: * unknown
405: */
406: protected boolean checkParameter(ArgList alist) {
407: return false;
408: }
409:
410: /**
411: * Print any extension details. This method may be overridden by subclasses
412: * to print extension parameter values for verbose output.
413: */
414: protected void verboseDetails() {
415: }
416:
417: /**
418: * Load the customizations file. This method must load the specified
419: * customizations file, or create a default customizations instance, of the
420: * appropriate type.
421: *
422: * @param path customizations file path, <code>null</code> if none
423: * @param loc class locator
424: * @throws JiBXException
425: * @throws IOException
426: */
427: protected abstract void loadCustomizations(String path,
428: IClassLocator loc) throws JiBXException, IOException;
429:
430: /**
431: * Apply map of override values to customizations read from file or
432: * created as default.
433: *
434: * @param overmap override key-value map
435: * @return map for key/values not recognized
436: */
437: protected abstract Map applyOverrides(Map overmap);
438:
439: /**
440: * Merge two arrays of strings, returning an ordered array containing all
441: * the strings from both provided arrays.
442: *
443: * @param base
444: * @param adds
445: * @return ordered merged
446: */
447: protected String[] mergeUsageLines(String[] base, String[] adds) {
448: if (adds.length == 0) {
449: return base;
450: } else {
451: String fulls[] = new String[base.length + adds.length];
452: System.arraycopy(base, 0, fulls, 0, base.length);
453: System.arraycopy(adds, 0, fulls, base.length, adds.length);
454: Arrays.sort(fulls);
455: return fulls;
456: }
457: }
458:
459: /**
460: * Print usage information.
461: */
462: public abstract void printUsage();
463:
464: /**
465: * Wrapper class for command line argument list.
466: */
467: protected static class ArgList {
468: private int m_offset;
469: private final String[] m_args;
470: private boolean m_valid;
471:
472: /**
473: * Constructor.
474: *
475: * @param args
476: */
477: protected ArgList(String[] args) {
478: m_offset = -1;
479: m_args = args;
480: m_valid = true;
481: }
482:
483: /**
484: * Check if another argument value is present.
485: *
486: * @return <code>true</code> if argument present, <code>false</code> if
487: * all processed
488: */
489: public boolean hasNext() {
490: return m_args.length - m_offset > 1;
491: }
492:
493: /**
494: * Get current argument value.
495: *
496: * @return argument, or <code>null</code> if none
497: */
498: public String current() {
499: return (m_offset >= 0 && m_offset < m_args.length) ? m_args[m_offset]
500: : null;
501: }
502:
503: /**
504: * Get next argument value. If this is called with no argument value
505: * available it sets the argument list invalid.
506: *
507: * @return argument, or <code>null</code> if none
508: */
509: public String next() {
510: if (++m_offset < m_args.length) {
511: return m_args[m_offset];
512: } else {
513: m_valid = false;
514: return null;
515: }
516: }
517:
518: /**
519: * Set valid state.
520: *
521: * @param valid
522: */
523: public void setValid(boolean valid) {
524: m_valid = valid;
525: }
526:
527: /**
528: * Check if argument list valid.
529: *
530: * @return <code>true</code> if valid, <code>false</code> if not
531: */
532: public boolean isValid() {
533: return m_valid;
534: }
535: }
536: }
|