001: /*
002: * FindBugs - Find bugs in Java programs
003: * Copyright (C) 2004, University of Maryland
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation; either
008: * version 2.1 of the License, or (at your option) any later version.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: *
015: * You should have received a copy of the GNU Lesser General Public
016: * License along with this library; if not, write to the Free Software
017: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: */
019:
020: package edu.umd.cs.findbugs.config;
021:
022: import java.io.BufferedReader;
023: import java.io.FileInputStream;
024: import java.io.IOException;
025: import java.io.InputStreamReader;
026: import java.io.OutputStream;
027: import java.io.PrintStream;
028: import java.nio.charset.Charset;
029: import java.util.ArrayList;
030: import java.util.HashMap;
031: import java.util.HashSet;
032: import java.util.LinkedList;
033: import java.util.List;
034: import java.util.Map;
035: import java.util.Set;
036:
037: import edu.umd.cs.findbugs.annotations.SuppressWarnings;
038:
039: /**
040: * Helper class for parsing command line arguments.
041: */
042: public abstract class CommandLine {
043: private List<String> optionList;
044: private Set<String> unlistedOptions = new HashSet<String>();
045:
046: private Set<String> requiresArgumentSet;
047: private Map<String, String> optionDescriptionMap;
048: private Map<String, String> optionExtraPartSynopsisMap;
049: private Map<String, String> argumentDescriptionMap;
050: int maxWidth;
051:
052: public CommandLine() {
053: this .optionList = new LinkedList<String>();
054: this .requiresArgumentSet = new HashSet<String>();
055: this .optionDescriptionMap = new HashMap<String, String>();
056: this .optionExtraPartSynopsisMap = new HashMap<String, String>();
057: this .argumentDescriptionMap = new HashMap<String, String>();
058: this .maxWidth = 0;
059: }
060:
061: /**
062: * Add a command line switch.
063: * This method is for adding options that do not require
064: * an argument.
065: *
066: * @param option the option, must start with "-"
067: * @param description single line description of the option
068: */
069: public void addSwitch(String option, String description) {
070: optionList.add(option);
071: optionDescriptionMap.put(option, description);
072:
073: if (option.length() > maxWidth)
074: maxWidth = option.length();
075: }
076:
077: /**
078: * Add a command line switch that allows optional extra
079: * information to be specified as part of it.
080: *
081: * @param option the option, must start with "-"
082: * @param optionExtraPartSynopsis synopsis of the optional extra information
083: * @param description single-line description of the option
084: */
085: public void addSwitchWithOptionalExtraPart(String option,
086: String optionExtraPartSynopsis, String description) {
087: optionList.add(option);
088: optionExtraPartSynopsisMap.put(option, optionExtraPartSynopsis);
089: optionDescriptionMap.put(option, description);
090:
091: // Option will display as -foo[:extraPartSynopsis]
092: int length = option.length() + optionExtraPartSynopsis.length()
093: + 3;
094: if (length > maxWidth)
095: maxWidth = length;
096: }
097:
098: /**
099: * Add an option requiring an argument.
100: *
101: * @param option the option, must start with "-"
102: * @param argumentDesc brief (one or two word) description of the argument
103: * @param description single line description of the option
104: */
105: public void addOption(String option, String argumentDesc,
106: String description) {
107: optionList.add(option);
108: optionDescriptionMap.put(option, description);
109: requiresArgumentSet.add(option);
110: argumentDescriptionMap.put(option, argumentDesc);
111:
112: int width = option.length() + 3 + argumentDesc.length();
113: if (width > maxWidth)
114: maxWidth = width;
115: }
116:
117: /**
118: * Don't list this option when printing Usage information
119: * @param option
120: */
121: public void makeOptionUnlisted(String option) {
122: unlistedOptions.add(option);
123: }
124:
125: /**
126: * Expand option files in given command line.
127: * Any token beginning with "@" is assumed to be an option file.
128: * Option files contain one command line option per line.
129: *
130: * @param argv the original command line
131: * @param ignoreComments ignore comments (lines starting with "#")
132: * @param ignoreBlankLines ignore blank lines
133: * @return the expanded command line
134: */
135: public static String[] expandOptionFiles(String[] argv,
136: boolean ignoreComments, boolean ignoreBlankLines)
137: throws IOException {
138: ArrayList<String> resultList = new ArrayList<String>();
139:
140: for (String arg : argv) {
141: if (!arg.startsWith("@")) {
142: resultList.add(arg);
143: continue;
144: }
145:
146: BufferedReader reader = null;
147: try {
148: reader = new BufferedReader(new InputStreamReader(
149: new FileInputStream(arg.substring(1)), Charset
150: .forName("UTF-8")));
151: String line;
152: while ((line = reader.readLine()) != null) {
153: line = line.trim();
154:
155: if (ignoreComments && line.startsWith("#"))
156: continue;
157:
158: if (ignoreBlankLines && line.equals(""))
159: continue;
160:
161: resultList.add(line);
162: }
163: } finally {
164: if (reader != null) {
165: try {
166: reader.close();
167: } catch (IOException ignore) {
168: // Ignore
169: }
170: }
171: }
172: }
173:
174: return resultList.toArray(new String[resultList.size()]);
175: }
176:
177: public static class HelpRequestedException extends Exception {
178:
179: }
180:
181: @SuppressWarnings("DM_EXIT")
182: public int parse(String argv[], int minArgs, int maxArgs,
183: String usage) {
184: try {
185: int count = parse(argv);
186: int remaining = argv.length - count;
187: if (remaining < minArgs || remaining > maxArgs) {
188: System.out.println(usage);
189: System.out.println("Expected " + minArgs + "..."
190: + maxArgs + " file arguments, found "
191: + remaining);
192: System.out.println("Options:");
193: printUsage(System.out);
194: System.exit(1);
195: }
196: return count;
197: } catch (HelpRequestedException e) {
198: // fall through
199:
200: } catch (RuntimeException e) {
201: e.printStackTrace();
202: } catch (IOException e) {
203: e.printStackTrace();
204: }
205: System.out.println(usage);
206: System.out.println("Options:");
207: printUsage(System.out);
208: System.exit(1);
209: return -1;
210: }
211:
212: /**
213: * Parse a command line.
214: * Calls down to handleOption() and handleOptionWithArgument() methods.
215: * Stops parsing when it reaches the end of the command line,
216: * or when a command line argument not starting with "-" is seen.
217: *
218: * @param argv the arguments
219: * @return the number of arguments parsed; if equal to
220: * argv.length, then the entire command line was parsed
221: * @throws HelpRequestedException
222: */
223: public int parse(String argv[]) throws IOException,
224: HelpRequestedException {
225: int arg = 0;
226:
227: while (arg < argv.length) {
228: String option = argv[arg];
229: if (option.equals("-help"))
230: throw new HelpRequestedException();
231: if (!option.startsWith("-"))
232: break;
233:
234: String optionExtraPart = "";
235: int colon = option.indexOf(':');
236: if (colon >= 0) {
237: optionExtraPart = option.substring(colon + 1);
238: option = option.substring(0, colon);
239: }
240:
241: if (optionDescriptionMap.get(option) == null)
242: throw new IllegalArgumentException("Unknown option: "
243: + option);
244:
245: if (requiresArgumentSet.contains(option)) {
246: ++arg;
247: if (arg >= argv.length)
248: throw new IllegalArgumentException("Option "
249: + option + " requires an argument");
250: String argument = argv[arg];
251: handleOptionWithArgument(option, argument);
252: ++arg;
253: } else {
254: handleOption(option, optionExtraPart);
255: ++arg;
256: }
257: }
258:
259: return arg;
260: }
261:
262: /**
263: * Callback method for handling an option.
264: *
265: * @param option the option
266: * @param optionExtraPart the "extra" part of the option (everything after the
267: * colon: e.g., "withMessages" in "-xml:withMessages");
268: * the empty string if there was no extra part
269: */
270: protected abstract void handleOption(String option,
271: String optionExtraPart) throws IOException;
272:
273: /**
274: * Callback method for handling an option with an argument.
275: *
276: * @param option the option
277: * @param argument the argument
278: */
279: protected abstract void handleOptionWithArgument(String option,
280: String argument) throws IOException;
281:
282: /**
283: * Print command line usage information to given stream.
284: *
285: * @param os the output stream
286: */
287: public void printUsage(OutputStream os) {
288: PrintStream out = new PrintStream(os);
289: for (String option : optionList) {
290: if (unlistedOptions.contains(option))
291: continue;
292: out.print(" ");
293:
294: StringBuffer buf = new StringBuffer();
295: buf.append(option);
296: if (optionExtraPartSynopsisMap.get(option) != null) {
297: String optionExtraPartSynopsis = optionExtraPartSynopsisMap
298: .get(option);
299: buf.append("[:");
300: buf.append(optionExtraPartSynopsis);
301: buf.append("]");
302: }
303: if (requiresArgumentSet.contains(option)) {
304: buf.append(" <");
305: buf.append(argumentDescriptionMap.get(option));
306: buf.append(">");
307: }
308: printField(out, buf.toString(), maxWidth + 1);
309:
310: out.println(optionDescriptionMap.get(option));
311: }
312: out.flush();
313: }
314:
315: private static final String SPACES = " ";
316:
317: private static void printField(PrintStream out, String s, int width) {
318: if (s.length() > width)
319: throw new IllegalArgumentException();
320: int nSpaces = width - s.length();
321: out.print(s);
322: while (nSpaces > 0) {
323: int n = Math.min(SPACES.length(), nSpaces);
324: out.print(SPACES.substring(0, n));
325: nSpaces -= n;
326: }
327: }
328: }
329:
330: // vim:ts=3
|