/* * Licensed to the Apache Software Foundation (ASF) under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional information regarding * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. You may obtain a * copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package org.apache.geode.modules.session.installer.args; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; /** * This class is used to process command line arguments for Java programs in a flexible and powerful * manner. */ public class ArgumentProcessor { /** * Logger. */ private static final Logger LOG = Logger.getLogger(ArgumentProcessor.class.getName()); /** * Description line length. */ private static final int LINE_LENGTH = 60; /** * Map containing all arguments defined, indexed by their unique IDs. */ private final List<Argument> args = new ArrayList<Argument>(); /** * Unknown argument handler. */ private UnknownArgumentHandler handler; /** * Program name to display in usage. */ private String programName; /////////////////////////////////////////////////////////////////////////// // Classes: /** * Structure used to represent an argument match. */ private static class Match { /** * The argument which matched. */ private final Argument arg; /** * The specific form which matched. */ private final String form; /** * The parameters to the argument form. */ private final String[] params; /** * Constructor. * * @param theArgument the argument which matched * @param theForm the form used * @param theParams the parameters supplied */ public Match(final Argument theArgument, final String theForm, final String[] theParams) { arg = theArgument; form = theForm; params = theParams; } /** * Accessor. * * @return argument which matched */ public Argument getArgument() { return arg; } /** * Accessor. * * @return form which was used */ public String getForm() { return form; } /** * Accessor. * * @return parameters supplied */ public String[] getParams() { return params; } } /////////////////////////////////////////////////////////////////////////// // Constructors: /** * Creates a new Argument processor instance for te program name given. * * @param progName program name used in usage */ public ArgumentProcessor(final String progName) { programName = progName; } /////////////////////////////////////////////////////////////////////////// // Public methods: /** * Adds a new argument. * * @param arg argument to add */ public void addArgument(final Argument arg) { args.add(arg); } /** * Sets the handler to call when an unknown argument is encountered. * * @param aHandler unknown arg handler, or null to unset */ public void setUnknownArgumentHandler(final UnknownArgumentHandler aHandler) { handler = aHandler; } /** * Process the command line arguments provided. * * @param programArgs command line arguments supplied to program * @return argument values parsed out of command line * @throws UsageException when usge sucked */ public ArgumentValues process(final String[] programArgs) throws UsageException { ArgumentHandler argHandler; final ArgumentValues result = new ArgumentValues(); List<Argument> unmatched; List<Match> matches; // Find all argument matches and set postArgs matches = checkMatches(programArgs, result); // Find arguments which didnt match unmatched = new ArrayList<Argument>(); unmatched.addAll(args); for (Match match : matches) { unmatched.remove(match.getArgument()); } // Error on unmatched yet required args for (Argument arg : unmatched) { if (arg.isRequired() && !arg.isDefinedInEnv()) { final UsageException usageException = new UsageException("Required argument not provided: " + arg); usageException.setUsage(getUsage()); throw usageException; } } // Handle the arguments for (Match match : matches) { final Argument arg = match.getArgument(); argHandler = arg.getArgumentHandler(); if (argHandler != null) { argHandler.handleArgument(arg, match.getForm(), match.getParams()); } result.addResult(arg, match.getParams()); } return result; } /** * Generates command line usage text for display to user. * * @return usage to dusplay to user */ public String getUsage() { final StringBuilder builder = new StringBuilder(); List<String> descriptionLines; final String blank20 = " "; builder.append("\nUSAGE: "); if (programName == null) { builder.append("<program>"); } else { builder.append(programName); } if (args.isEmpty()) { builder.append("\nNo arguments supported.\n"); } else { builder.append(" <args>\nWHERE <args>:\n\n"); for (Argument arg : args) { for (String form : arg.getForms()) { builder.append(" "); builder.append(form); for (int i = 0; i < arg.getParameterCount(); i++) { builder.append(" <"); builder.append(arg.getParameterName(i)); builder.append(">"); } builder.append("\n"); } descriptionLines = breakupString(arg.getDescription(), LINE_LENGTH); if (descriptionLines.isEmpty()) { builder.append(blank20); builder.append("No argument description provided."); builder.append("\n\n"); } else { for (String line : descriptionLines) { builder.append(blank20); builder.append(line.trim()); builder.append("\n"); } builder.append("\n"); } } } builder.append("\n"); return builder.toString(); } /////////////////////////////////////////////////////////////////////////// // Private methods: /** * Builds a listof all argument matches and sets the postArgs array. * * @param programArgs command line arguments to search through * @param values values object in which to store results * @return list of matches * @throws UsageException when there is EBKAC */ private List<Match> checkMatches(final String[] programArgs, final ArgumentValues values) throws UsageException { final List<Match> result = new ArrayList<Match>(); Match match; String[] params; String[] postArgs; int idx = 0; int idx2; while (idx < programArgs.length) { // Check for end-of-parameters arg if ("--".equals(programArgs[idx])) { if (++idx < programArgs.length) { postArgs = new String[programArgs.length - idx]; System.arraycopy(programArgs, idx, postArgs, 0, postArgs.length); values.setPostArgs(postArgs); } // We're done processing args' break; } // Determine parameter count idx2 = idx; while ((idx2 + 1) < programArgs.length && programArgs[idx2 + 1].charAt(0) != '-') { idx2++; } // Generate parameter array params = new String[idx2 - idx]; System.arraycopy(programArgs, idx + 1, params, 0, params.length); LOG.fine("Arg: " + programArgs[idx]); LOG.fine("Params: " + params.length); // Find first argument matches match = null; for (Argument arg : args) { match = checkMatch(programArgs[idx], arg, params); if (match != null) { result.add(match); LOG.fine("Match found: "); LOG.fine(" ID: " + arg); LOG.fine(" Form: " + match.getForm()); break; } } if (match == null) { if (handler == null) { final UsageException usageException = new UsageException( "Unknown argument: " + programArgs[idx] + " with " + params.length + " parameters."); usageException.setUsage(getUsage()); throw (usageException); } else { handler.handleUnknownArgument(programArgs[idx], params); } } idx += params.length + 1; } return result; } /** * Checks to see if an rgument form matches the suppies parameter list. * * @param argName argument name * @param arg argument * @param params parameters supplied * @return match object on match, null otherwise */ private Match checkMatch(final String argName, final Argument arg, final String[] params) { // Look for a matching form for (String form : arg.getForms()) { if (form.equals(argName) && arg.getParameterCount() == params.length) { return new Match(arg, form, params); } } return null; } /** * Breaks up a string into sub-strings, each with a length equal to or less than the max length * specified. * * @param str string to break up * @param maxLength maximum line length to use * @return broken up string */ private List<String> breakupString(final String str, final int maxLength) { final List<String> result = new ArrayList<String>(); int startIdx = -1; int lastIdx; int idx; if (str == null) { return result; } do { idx = startIdx; do { lastIdx = idx; idx = str.indexOf(' ', lastIdx + 1); LOG.fine("startIdx=" + startIdx + " lastIdx=" + lastIdx + " idx=" + idx); if (idx < 0) { // Canot break line up any further result.add(str.substring(startIdx + 1)); return result; } } while ((idx - startIdx) <= maxLength); result.add(str.substring(startIdx + 1, lastIdx)); startIdx = lastIdx; } while (true); } }