/* * FindBugs - Find bugs in Java programs * Copyright (C) 2004, University of Maryland * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package edu.umd.cs.findbugs.config; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintStream; import java.net.URL; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import edu.umd.cs.findbugs.DetectorFactoryCollection; import edu.umd.cs.findbugs.annotations.SuppressWarnings; import edu.umd.cs.findbugs.ba.AnalysisContext; import edu.umd.cs.findbugs.charsets.UTF8; import edu.umd.cs.findbugs.util.Util; /** * Helper class for parsing command line arguments. */ public abstract class CommandLine { private static final String SPACES = " "; private List<String> optionList; private Set<String> unlistedOptions; private Map<Integer, String> optionGroups; private Set<String> requiresArgumentSet; private Map<String, String> optionDescriptionMap; private Map<String, String> optionExtraPartSynopsisMap; private Map<String, String> argumentDescriptionMap; int maxWidth; public CommandLine() { this.unlistedOptions = new HashSet<String>(); this.optionList = new LinkedList<String>(); this.optionGroups = new HashMap<Integer, String>(); this.requiresArgumentSet = new HashSet<String>(); this.optionDescriptionMap = new HashMap<String, String>(); this.optionExtraPartSynopsisMap = new HashMap<String, String>(); this.argumentDescriptionMap = new HashMap<String, String>(); this.maxWidth = 0; } /** * Start a new group of related command-line options. * * @param description * description of the group */ public void startOptionGroup(String description) { optionGroups.put(optionList.size(), description); } /** * Add a command line switch. This method is for adding options that do not * require an argument. * * @param option * the option, must start with "-" * @param description * single line description of the option */ public void addSwitch(String option, String description) { optionList.add(option); optionDescriptionMap.put(option, description); if (option.length() > maxWidth) maxWidth = option.length(); } /** * Add a command line switch that allows optional extra information to be * specified as part of it. * * @param option * the option, must start with "-" * @param optionExtraPartSynopsis * synopsis of the optional extra information * @param description * single-line description of the option */ public void addSwitchWithOptionalExtraPart(String option, String optionExtraPartSynopsis, String description) { optionList.add(option); optionExtraPartSynopsisMap.put(option, optionExtraPartSynopsis); optionDescriptionMap.put(option, description); // Option will display as -foo[:extraPartSynopsis] int length = option.length() + optionExtraPartSynopsis.length() + 3; if (length > maxWidth) maxWidth = length; } /** * Add an option requiring an argument. * * @param option * the option, must start with "-" * @param argumentDesc * brief (one or two word) description of the argument * @param description * single line description of the option */ public void addOption(String option, String argumentDesc, String description) { optionList.add(option); optionDescriptionMap.put(option, description); requiresArgumentSet.add(option); argumentDescriptionMap.put(option, argumentDesc); int width = option.length() + 3 + argumentDesc.length(); if (width > maxWidth) maxWidth = width; } /** * Don't list this option when printing Usage information * * @param option */ public void makeOptionUnlisted(String option) { unlistedOptions.add(option); } /** * Expand option files in given command line. Any token beginning with "@" * is assumed to be an option file. Option files contain one command line * option per line. * * @param argv * the original command line * @param ignoreComments * ignore comments (lines starting with "#") * @param ignoreBlankLines * ignore blank lines * @return the expanded command line */ public String[] expandOptionFiles(String[] argv, boolean ignoreComments, boolean ignoreBlankLines) throws IOException, HelpRequestedException { // Add all expanded options at the end of the options list, before the // list of // jar/zip/class files and directories. // At the end of the options to preserve the order of the options (e.g. // -adjustPriority // must always come after -pluginList). int lastOptionIndex = parse(argv, true); ArrayList<String> resultList = new ArrayList<String>(); ArrayList<String> expandedOptionsList = getAnalysisOptionProperties(ignoreComments, ignoreBlankLines); for (int i = 0; i < lastOptionIndex; i++) { String arg = argv[i]; if (!arg.startsWith("@")) { resultList.add(arg); continue; } BufferedReader reader = null; try { reader = new BufferedReader( new InputStreamReader(new FileInputStream(arg.substring(1)))); addCommandLineOptions(expandedOptionsList, reader, ignoreComments, ignoreBlankLines); } finally { Util.closeSilently(reader); } } resultList.addAll(expandedOptionsList); for (int i = lastOptionIndex; i < argv.length; i++) { resultList.add(argv[i]); } return resultList.toArray(new String[resultList.size()]); } public static ArrayList<String> getAnalysisOptionProperties(boolean ignoreComments, boolean ignoreBlankLines) { ArrayList<String> resultList = new ArrayList<String>(); URL u = DetectorFactoryCollection.getCoreResource("analysisOptions.properties"); if (u != null) { BufferedReader reader = null; try { reader = UTF8.bufferedReader(u.openStream()); addCommandLineOptions(resultList, reader, ignoreComments, ignoreBlankLines); } catch (IOException e) { AnalysisContext.logError("unable to load analysisOptions.properties", e); } finally { Util.closeSilently(reader); } } return resultList; } private static void addCommandLineOptions(ArrayList<String> resultList, BufferedReader reader, boolean ignoreComments, boolean ignoreBlankLines) throws IOException { String line; while ((line = reader.readLine()) != null) { line = line.trim(); if (ignoreComments && line.startsWith("#")) continue; if (ignoreBlankLines && line.equals("")) continue; if (line.length() >= 2 && line.charAt(0) == '"' && line.charAt(line.length() - 1) == '"') resultList.add(line.substring(0, line.length() - 1)); else for (String segment : line.split(" ")) resultList.add(segment); } } public static class HelpRequestedException extends Exception { } /** * Parse switches/options, showing usage information if they can't be * parsed, or if we have the wrong number of remaining arguments after * parsing. Calls parse(String[]). * * @param argv * command line arguments * @param minArgs * allowed minimum number of arguments remaining after * switches/options are parsed * @param maxArgs * allowed maximum number of arguments remaining after * switches/options are parsed * @param usage * usage synopsis * @return number of arguments parsed */ @SuppressWarnings("DM_EXIT") public int parse(String argv[], int minArgs, int maxArgs, String usage) { try { int count = parse(argv); int remaining = argv.length - count; if (remaining < minArgs || remaining > maxArgs) { System.out.println(usage); System.out.println("Expected " + minArgs + "..." + maxArgs + " file arguments, found " + remaining); System.out.println("Options:"); printUsage(System.out); System.exit(1); } return count; } catch (HelpRequestedException e) { // fall through } catch (RuntimeException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } System.out.println(usage); System.out.println("Options:"); printUsage(System.out); System.exit(1); return -1; } /** * Parse a command line. Calls down to handleOption() and * handleOptionWithArgument() methods. Stops parsing when it reaches the end * of the command line, or when a command line argument not starting with * "-" is seen. * * @param argv * the arguments * @return the number of arguments parsed; if equal to argv.length, then the * entire command line was parsed * @throws HelpRequestedException */ public int parse(String argv[]) throws IOException, HelpRequestedException { return parse(argv, false); } private int parse(String argv[], boolean dryRun) throws IOException, HelpRequestedException { int arg = 0; while (arg < argv.length) { String option = argv[arg]; if (option.equals("-help")) throw new HelpRequestedException(); if (!option.startsWith("-")) break; String optionExtraPart = ""; int colon = option.indexOf(':'); if (colon >= 0) { optionExtraPart = option.substring(colon + 1); option = option.substring(0, colon); } if (optionDescriptionMap.get(option) == null) throw new IllegalArgumentException("Unknown option: " + option); if (requiresArgumentSet.contains(option)) { ++arg; if (arg >= argv.length) throw new IllegalArgumentException("Option " + option + " requires an argument"); String argument = argv[arg]; if (!dryRun) handleOptionWithArgument(option, argument); ++arg; } else { if (!dryRun) handleOption(option, optionExtraPart); ++arg; } } return arg; } /** * Callback method for handling an option. * * @param option * the option * @param optionExtraPart * the "extra" part of the option (everything after the colon: * e.g., "withMessages" in "-xml:withMessages"); the empty string * if there was no extra part */ protected abstract void handleOption(String option, String optionExtraPart) throws IOException; /** * Callback method for handling an option with an argument. * * @param option * the option * @param argument * the argument */ protected abstract void handleOptionWithArgument(String option, String argument) throws IOException; /** * Print command line usage information to given stream. * * @param os * the output stream */ public void printUsage(OutputStream os) { int count = 0; PrintStream out = new PrintStream(os); for (String option : optionList) { if (optionGroups.containsKey(count)) { out.println(" " + optionGroups.get(count)); } count++; if (unlistedOptions.contains(option)) continue; out.print(" "); StringBuilder buf = new StringBuilder(); buf.append(option); if (optionExtraPartSynopsisMap.get(option) != null) { String optionExtraPartSynopsis = optionExtraPartSynopsisMap.get(option); buf.append("[:"); buf.append(optionExtraPartSynopsis); buf.append("]"); } if (requiresArgumentSet.contains(option)) { buf.append(" <"); buf.append(argumentDescriptionMap.get(option)); buf.append(">"); } printField(out, buf.toString(), maxWidth + 1); out.println(optionDescriptionMap.get(option)); } out.flush(); } private static void printField(PrintStream out, String s, int width) { if (s.length() > width) throw new IllegalArgumentException(); int nSpaces = width - s.length(); out.print(s); while (nSpaces > 0) { int n = Math.min(SPACES.length(), nSpaces); out.print(SPACES.substring(0, n)); nSpaces -= n; } } }