001: /*
002: Copyright (c) 2003, 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 JargP 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.jargp;
030:
031: import java.io.PrintStream;
032: import java.lang.reflect.Field;
033: import java.util.List;
034:
035: /**
036: * Command line parameter processing handler. Organizes all the parameter
037: * information, including the data object to which parameter values defined
038: * by the command line are stored. Provides specialized processing for the
039: * argument strings, including recognizing the '-' character at the start of
040: * an argument as indicating that the argument provides control information
041: * (flags and possibly embedded values) as opposed to data.
042: *
043: * @author Dennis M. Sosnoski
044: * @version 1.0
045: */
046:
047: public class ArgumentProcessor {
048: /** Head of parameter set chain. */
049: private final ParameterSet m_parameterSet;
050:
051: /** Character tracker for current argument. */
052: private CharTracker m_currentArg;
053:
054: /** Current argument position in list. */
055: private int m_currentIndex;
056:
057: /** String tracker for full set of arguments. */
058: private StringTracker m_remainingArgs;
059:
060: /** Argument data object. */
061: private Object m_targetObject;
062:
063: /**
064: * Constructor from parameter set definition.
065: *
066: * @param set head parameter set in possible chain of sets defined
067: */
068:
069: public ArgumentProcessor(ParameterSet set) {
070: m_parameterSet = set;
071: }
072:
073: /**
074: * Constructor from array of parameter definitions.
075: *
076: * @param set head parameter set in possible chain of sets defined
077: */
078:
079: public ArgumentProcessor(ParameterDef[] defs) {
080: this (new ParameterSet(defs, null));
081: }
082:
083: /**
084: * Bind parameter definitions to target object class. This goes through the
085: * set of defined parameters, binding each to the class of the supplied
086: * target object and finding the associated field information. Rather than
087: * doing this binding of all parameter definitions prior to processing the
088: * command line information, it'd also be possible (and even more efficient)
089: * to just lookup the field information for each parameter actually present
090: * in the list. The only advantage of doing this lookup in advance is that
091: * it insures that configuration errors are reported whether the parameter
092: * in error is used or not.
093: *
094: * @param parm data object for parameter values
095: * @throws ArgumentErrorException on error in data
096: * @throws IllegalArgumentException on error in processing
097: */
098:
099: private void bindDefinitions(Object parm) {
100: int index = 0;
101: Class clas = parm.getClass();
102: ParameterDef def;
103: while ((def = m_parameterSet.indexDef(index++)) != null) {
104: def.bindToClass(clas);
105: }
106: }
107:
108: /**
109: * Process argument list control information. Processes control flags
110: * present in the supplied argument list, setting the associated parameter
111: * values. Arguments not consumed in the control flag processing are
112: * available for access using other methods after the return from this
113: * call.
114: *
115: * @param args command line argument string array
116: * @param target application object defining parameter fields
117: * @throws ArgumentErrorException on error in data
118: * @throws IllegalArgumentException on error in processing
119: */
120:
121: public Object processArgs(String[] args, Object target) {
122:
123: // verify field definitions for all parameters
124: bindDefinitions(target);
125:
126: // clean up argument text (may have CR-LF line ends, confusing Linux)
127: String[] trims = new String[args.length];
128: for (int i = 0; i < args.length; i++) {
129: trims[i] = args[i].trim();
130: }
131:
132: // initialize argument list information
133: m_currentArg = new CharTracker("", 0);
134: m_remainingArgs = new StringTracker(trims, 0);
135: m_targetObject = target;
136:
137: // loop for processing all argument values present
138: while (true) {
139: if (m_currentArg.hasNext()) {
140:
141: // find the parameter definition for current flag character
142: char flag = m_currentArg.next();
143: ParameterDef def = m_parameterSet.findDef(flag);
144: if (def != null) {
145:
146: // process the argument
147: def.handle(this );
148:
149: } else if (flag == ' ') {
150: break; // preserve old functionality
151: } else {
152: throw new IllegalArgumentException("Control flag '"
153: + flag + "' in argument " + m_currentIndex
154: + " is not defined");
155: }
156:
157: } else if (m_remainingArgs.hasNext()) {
158:
159: // check if more control flags in next argument
160: String next = m_remainingArgs.peek();
161: if (next.length() > 0 && next.charAt(0) == '-') {
162: m_remainingArgs.next();
163: m_currentIndex = m_remainingArgs.nextOffset();
164: m_currentArg = new CharTracker(next, 1);
165: } else if (next.length() > 0) {
166: m_currentArg = new CharTracker("- ", 1);
167: } else {
168: break;
169: }
170:
171: } else {
172: break;
173: }
174: }
175: return m_targetObject;
176: }
177:
178: /**
179: * Get current control argument character information. The caller can
180: * consume characters from the current argument as needed.
181: *
182: * @return argument string tracking information
183: */
184:
185: /*package*/CharTracker getChars() {
186: return m_currentArg;
187: }
188:
189: /**
190: * Get current argument position in list.
191: *
192: * @return offset in argument list of current flag argument
193: */
194:
195: /*package*/int getIndex() {
196: return m_currentIndex;
197: }
198:
199: /**
200: * Get argument list information. The caller can comsume arguments
201: * from the list as needed.
202: *
203: * @return argument list information
204: */
205:
206: public StringTracker getArgs() {
207: return m_remainingArgs;
208: }
209:
210: /**
211: * Set parameter value. Uses reflection to set a value within the
212: * target data object.
213: *
214: * @param value value to be set for parameter
215: * @param field target field for parameter value
216: * @throws IllegalArgumentException on error in setting parameter value
217: */
218:
219: /*package*/void setValue(Object value, Field field) {
220: try {
221: field.setAccessible(true);
222: field.set(m_targetObject, value);
223: } catch (IllegalAccessException ex) {
224: throw new IllegalArgumentException("Field "
225: + field.getName()
226: + " is not accessible in object of class "
227: + m_targetObject.getClass().getName());
228: }
229: }
230:
231: /**
232: * Add parameter value to list. Uses reflection to retrieve the list object
233: * and add a value to those present in the list.
234: *
235: * @param value value to be added to list
236: * @param field target list field
237: * @throws IllegalArgumentException on error in adding parameter value
238: */
239:
240: /*package*/void addValue(Object value, Field field) {
241: try {
242: field.setAccessible(true);
243: List list = (List) field.get(m_targetObject);
244: if (list == null) {
245: list = (List) field.getType().newInstance();
246: field.set(m_targetObject, list);
247: }
248: list.add(value);
249: } catch (IllegalAccessException ex) {
250: throw new IllegalArgumentException("Field "
251: + field.getName()
252: + " is not accessible in object of class "
253: + m_targetObject.getClass().getName());
254: } catch (InstantiationException ex) {
255: throw new IllegalArgumentException(
256: "Unable to create instance of "
257: + field.getType().getName()
258: + " for storing to list field "
259: + field.getName() + " in object of class "
260: + m_targetObject.getClass().getName());
261: }
262: }
263:
264: /**
265: * Report argument error. Generates an exception with information about
266: * the argument causing the problem.
267: *
268: * @param flag argument flag character
269: * @param text error message text
270: * @throws ArgumentErrorException reporting the error
271: */
272:
273: public void reportArgumentError(char flag, String text) {
274: throw new ArgumentErrorException(text + " for parameter '"
275: + flag + "' in argument " + m_currentIndex);
276: }
277:
278: /**
279: * List known parameter definitions. This lists all known parameter
280: * definitions in fixed maximum width format.
281: *
282: * @param width maximum number of columns in listing
283: * @param print print stream destination for listing definitions
284: */
285:
286: public void listParameters(int width, PrintStream print) {
287:
288: // scan once to find maximum parameter abbreviation length
289: int count = 0;
290: int maxlen = 0;
291: ParameterDef def = null;
292: while ((def = m_parameterSet.indexDef(count)) != null) {
293: int length = def.getAbbreviation().length();
294: if (maxlen < length) {
295: maxlen = length;
296: }
297: count++;
298: }
299:
300: // initialize for handling text generation
301: StringBuffer line = new StringBuffer(width);
302: int lead = maxlen + 2;
303: char[] blanks = new char[lead];
304: for (int i = 0; i < lead; i++) {
305: blanks[i] = ' ';
306: }
307:
308: // scan again to print text of definitions
309: for (int i = 0; i < count; i++) {
310:
311: // set up lead parameter abbreviation for first line
312: line.setLength(0);
313: def = m_parameterSet.indexDef(i);
314: line.append(' ');
315: line.append(def.getAbbreviation());
316: line.append(blanks, 0, lead - line.length());
317:
318: // format description text in as many lines as needed
319: String text = def.getDescription();
320: while (line.length() + text.length() > width) {
321:
322: // scan for first line break position (even if beyond limit)
323: int limit = width - line.length();
324: int mark = text.indexOf(' ');
325: if (mark >= 0) {
326:
327: // find break position closest to limit
328: int split = mark;
329: while (mark >= 0 && mark <= limit) {
330: split = mark;
331: mark = text.indexOf(' ', mark + 1);
332: }
333:
334: // split the description for printing line
335: line.append(text.substring(0, split));
336: print.println(line.toString());
337: line.setLength(0);
338: line.append(blanks);
339: text = text.substring(split + 1);
340:
341: } else {
342: break;
343: }
344: }
345:
346: // print remainder of description in single line
347: line.append(text);
348: print.println(line.toString());
349: }
350: }
351:
352: /**
353: * Process argument list directly. Creates and initializes an instance of
354: * this class, then processes control flags present in the supplied argument
355: * list, setting the associated parameter values in the target object.
356: * Arguments not consumed in the control flag processing are available for
357: * access using other methods after the return from this call.
358: *
359: * @param args command line argument string array
360: * @param parm data object for parameter values
361: * @param target application object defining parameter fields
362: * @return index of first command line argument not consumed by processing
363: * @throws ArgumentErrorException on error in data
364: * @throws IllegalArgumentException on error in processing
365: */
366:
367: public static int processArgs(String[] args, ParameterDef[] parms,
368: Object target) {
369: ArgumentProcessor inst = new ArgumentProcessor(parms);
370: inst.processArgs(args, target);
371: return inst.m_remainingArgs.nextOffset();
372: }
373: }
|