/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2001-2008, Open Source Geospatial Foundation (OSGeo)
*
* 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;
* version 2.1 of the License.
*
* 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.
*/
package org.geotools.resources;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.Writer;
import java.util.Locale;
import java.util.regex.Pattern;
import org.geotools.resources.i18n.Errors;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Vocabulary;
import org.geotools.resources.i18n.VocabularyKeys;
/**
* A helper class for parsing command-line arguments. Instance of this class
* are usually created inside {@code main} methods. For example:
*
* <blockquote><pre>
* public static void main(String[] args) {
* Arguments arguments = new Arguments(args);
* }
* </pre></blockquote>
*
* Then, method likes {@link #getRequiredString} or {@link #getOptionalString} can be used.
* If a parameter is badly formatted or if a required parameter is not presents, then the
* method {@link #illegalArgument} will be invoked with a message that describes the error.
* The default implementation print the localized error message to standard output {@link #out}
* and exits the virtual machine with a call to {@link System#exit} with error code 1.
*
* @since 2.0
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*/
public class Arguments {
/**
* The code given to {@link System#exit} when this class exits because of an illegal
* user argument.
*
* @deprecated Moved to {@link org.geotools.console.CommandLine}.
*/
@Deprecated
public static final int ILLEGAL_ARGUMENT_EXIT_CODE = 1;
/**
* Command-line arguments. Elements are set to
* {@code null} after they have been processed.
*/
private final String[] arguments;
/**
* Output stream to the console. This output stream may use encoding
* specified in the {@code "-encoding"} argument, if presents.
*/
public final PrintWriter out;
/**
* Error stream to the console.
*/
public final PrintWriter err;
/**
* The locale. Locale will be fetch from the {@code "-locale"}
* argument, if presents. Otherwise, the default locale will be used.
*/
public final Locale locale;
/**
* The encoding, or {@code null} for the platform default.
*/
private final String encoding;
/**
* Constructs a set of arguments.
*
* @param args Command line arguments. Arguments {@code "-encoding"} and {@code "-locale"}
* will be automatically parsed.
*/
public Arguments(final String[] args) {
this.arguments = args.clone();
this.locale = getLocale(getOptionalString("-locale"));
this.encoding = getOptionalString("-encoding");
PrintWriter out = null;
Exception error = null;
if (encoding != null) try {
out = new PrintWriter(new OutputStreamWriter(System.out, encoding), true);
} catch (IOException exception) {
error = exception;
}
if (out == null) {
out = getPrintWriter(System.out);
}
this.out = out;
this.err = getPrintWriter(System.err);
if (error != null) {
illegalArgument(error);
}
}
/**
* Returns the specified locale.
*
* @param locale The programmatic locale string (e.g. "fr_CA").
* @return The locale, or the default one if {@code locale} was null.
* @throws IllegalArgumentException if the locale string is invalid.
*/
private Locale getLocale(final String locale) throws IllegalArgumentException {
if (locale != null) {
final String[] s = Pattern.compile("_").split(locale);
switch (s.length) {
case 1: return new Locale(s[0]);
case 2: return new Locale(s[0], s[1]);
case 3: return new Locale(s[0], s[1], s[2]);
default: illegalArgument(new IllegalArgumentException(Errors.format(
ErrorKeys.BAD_LOCALE_$1, locale)));
}
}
return Locale.getDefault();
}
/**
* Returns an optional string value from the command line. This method should be called
* exactly once for each parameter. Second invocation for the same parameter will returns
* {@code null}, unless the same parameter appears many times on the command line.
* <p>
* Paramater may be instructions like "-encoding cp850" or "-encoding=cp850".
* Both forms (with or without "=") are accepted. Spaces around the '=' character,
* if any, are ignored.
*
* @param name The parameter name (e.g. "-encoding"). Name are case-insensitive.
* @return The parameter value, of {@code null} if there is no parameter
* given for the specified name.
*/
public String getOptionalString(final String name) {
for (int i=0; i<arguments.length; i++) {
String arg = arguments[i];
if (arg != null) {
arg = arg.trim();
String value = "";
int split = arg.indexOf('=');
if (split >= 0) {
value = arg.substring(split+1).trim();
arg = arg.substring(0, split).trim();
}
if (arg.equalsIgnoreCase(name)) {
arguments[i] = null;
if (value.length() != 0) {
return value;
}
while (++i < arguments.length) {
value = arguments[i];
arguments[i] = null;
if (value==null) {
break;
}
value = value.trim();
if (split>=0) {
return value;
}
if (!value.equals("=")) {
return value.startsWith("=") ? value.substring(1).trim() : value;
}
split = 0;
}
illegalArgument(new IllegalArgumentException(Errors.getResources(locale).
getString(ErrorKeys.MISSING_PARAMETER_VALUE_$1, arg)));
return null;
}
}
}
return null;
}
/**
* Returns an required string value from the command line. This method
* works like {@link #getOptionalString}, except that it will invokes
* {@link #illegalArgument} if the specified parameter was not given
* on the command line.
*
* @param name The parameter name. Name are case-insensitive.
* @return The parameter value.
*/
public String getRequiredString(final String name) {
final String value = getOptionalString(name);
if (value == null) {
illegalArgument(new IllegalArgumentException(Errors.getResources(locale).
getString(ErrorKeys.MISSING_PARAMETER_$1, name)));
}
return value;
}
/**
* Returns an optional integer value from the command line. Numbers are parsed as
* of the {@link Integer#parseInt(String)} method, which means that the parsing
* is locale-insensitive. Locale insensitive parsing is required in order to use
* arguments in portable scripts.
*
* @param name The parameter name. Name are case-insensitive.
* @return The parameter value, of {@code null} if there is no parameter
* given for the specified name.
*/
public Integer getOptionalInteger(final String name) {
final String value = getOptionalString(name);
if (value != null) try {
return Integer.valueOf(value);
} catch (NumberFormatException exception) {
illegalArgument(exception);
}
return null;
}
/**
* Returns a required integer value from the command line. Numbers are parsed as
* of the {@link Integer#parseInt(String)} method, which means that the parsing
* is locale-insensitive. Locale insensitive parsing is required in order to use
* arguments in portable scripts.
*
* @param name The parameter name. Name are case-insensitive.
* @return The parameter value.
*/
public int getRequiredInteger(final String name) {
final String value = getRequiredString(name);
if (value != null) try {
return Integer.parseInt(value);
} catch (NumberFormatException exception) {
illegalArgument(exception);
}
return 0;
}
/**
* Returns an optional floating-point value from the command line. Numbers are parsed
* as of the {@link Double#parseDouble(String)} method, which means that the parsing
* is locale-insensitive. Locale insensitive parsing is required in order to use
* arguments in portable scripts.
*
* @param name The parameter name. Name are case-insensitive.
* @return The parameter value, of {@code null} if there is no parameter
* given for the specified name.
*/
public Double getOptionalDouble(final String name) {
final String value = getOptionalString(name);
if (value != null) try {
return Double.valueOf(value);
} catch (NumberFormatException exception) {
illegalArgument(exception);
}
return null;
}
/**
* Returns a required floating-point value from the command line. Numbers are parsed
* as of the {@link Double#parseDouble(String)} method, which means that the parsing
* is locale-insensitive. Locale insensitive parsing is required in order to use
* arguments in portable scripts.
*
* @param name The parameter name. Name are case-insensitive.
* @return The parameter value.
*/
public double getRequiredDouble(final String name) {
final String value = getRequiredString(name);
if (value != null) try {
return Double.parseDouble(value);
} catch (NumberFormatException exception) {
illegalArgument(exception);
}
return Double.NaN;
}
/**
* Returns an optional boolean value from the command line.
* The value, if defined, must be "true" or "false".
*
* @param name The parameter name. Name are case-insensitive.
* @return The parameter value, of {@code null} if there is no parameter
* given for the specified name.
*/
public Boolean getOptionalBoolean(final String name) {
final String value = getOptionalString(name);
if (value != null) {
if (value.equalsIgnoreCase("true" )) return Boolean.TRUE;
if (value.equalsIgnoreCase("false")) return Boolean.FALSE;
illegalArgument(new IllegalArgumentException(value));
}
return null;
}
/**
* Returns a required boolean value from the command line.
* The value must be "true" or "false".
*
* @param name The parameter name. Name are case-insensitive.
* @return The parameter value.
*/
public boolean getRequiredBoolean(final String name) {
final String value = getRequiredString(name);
if (value != null) {
if (value.equalsIgnoreCase("true" )) return true;
if (value.equalsIgnoreCase("false")) return false;
illegalArgument(new IllegalArgumentException(value));
}
return false;
}
/**
* Returns {@code true} if the specified flag is set on the command line.
* This method should be called exactly once for each flag. Second invocation
* for the same flag will returns {@code false} (unless the same flag
* appears many times on the command line).
*
* @param name The flag name.
* @return {@code true} if this flag appears on the command line, or {@code false}
* otherwise.
*/
public boolean getFlag(final String name) {
for (int i=0; i<arguments.length; i++) {
String arg = arguments[i];
if (arg!=null) {
arg = arg.trim();
if (arg.equalsIgnoreCase(name)) {
arguments[i] = null;
return true;
}
}
}
return false;
}
/**
* Gets a reader for the specified input stream.
*
* @param in The input stream to wrap.
* @return A {@link Reader} wrapping the specified input stream.
*/
public static Reader getReader(final InputStream in) {
if (in == System.in) {
final Reader candidate = Java6.consoleReader();
if (candidate != null) {
return candidate;
}
}
return new InputStreamReader(in);
}
/**
* Gets a writer for the specified output stream.
*
* @param out The output stream to wrap.
* @return A {@link Writer} wrapping the specified output stream.
*/
public static Writer getWriter(final OutputStream out) {
if (out == System.out || out == System.err) {
final PrintWriter candidate = Java6.consoleWriter();
if (candidate != null) {
return candidate;
}
}
return new OutputStreamWriter(out);
}
/**
* Gets a print writer for the specified print stream.
*
* @param out The print stream to wrap.
* @return A {@link PrintWriter} wrapping the specified print stream.
*/
public static PrintWriter getPrintWriter(final PrintStream out) {
if (out == System.out || out == System.err) {
final PrintWriter candidate = Java6.consoleWriter();
if (candidate != null) {
return candidate;
}
}
return new PrintWriter(out, true);
}
/**
* Returns the list of unprocessed arguments. If the number of remaining arguments is
* greater than the specified maximum, then this method invokes {@link #illegalArgument}.
*
* @param max Maximum remaining arguments autorized.
* @return An array of remaining arguments. Will never be longer than {@code max}.
*/
public String[] getRemainingArguments(final int max) {
int count = 0;
final String[] left = new String[Math.min(max, arguments.length)];
for (int i=0; i<arguments.length; i++) {
final String arg = arguments[i];
if (arg != null) {
if (count >= max) {
illegalArgument(new IllegalArgumentException(Errors.getResources(locale).
getString(ErrorKeys.UNEXPECTED_PARAMETER_$1, arguments[i])));
}
left[count++] = arg;
}
}
return XArray.resize(left, count);
}
/**
* Returns the list of unprocessed arguments, which should not begin by the specified prefix. This
* method invokes <code>{@linkplain #getRemainingArguments(int) getRemainingArguments}(max)</code>
* and verifies that none of the remaining arguments start with {@code forbiddenPrefix}. The
* forbidden prefix is usually {@code '-'}, the character used for options as in
* "{@code -locale}", <cite>etc.</cite>
*
* @param max Maximum remaining arguments autorized.
* @param forbiddenPrefix The forbidden prefix, usually {@code '-'}.
* @return An array of remaining arguments. Will never be longer than {@code max}.
*
* @since 2.4
*/
public String[] getRemainingArguments(final int max, final char forbiddenPrefix) {
final String[] arguments = getRemainingArguments(max);
for (int i=0; i<arguments.length; i++) {
String argument = arguments[i];
if (argument != null) {
argument = argument.trim();
if (argument.length() != 0) {
if (argument.charAt(0) == forbiddenPrefix) {
illegalArgument(new IllegalArgumentException(Errors.getResources(locale).
getString(ErrorKeys.UNKNOW_PARAMETER_$1, argument)));
}
}
}
}
return arguments;
}
/**
* Prints a summary of the specified exception, without stack trace. This method
* is invoked when a non-fatal (and somewhat expected) error occured, for example
* {@link java.io.FileNotFoundException} when the file were specified in argument.
*
* @param exception An exception with a message describing the user's error.
*
* @since 2.3
*/
public void printSummary(final Exception exception) {
final String type = Classes.getShortClassName(exception);
String message = exception.getLocalizedMessage();
if (message == null) {
message = Vocabulary.format(VocabularyKeys.NO_DETAILS_$1, type);
} else {
err.print(type);
err.print(": ");
}
err.println(message);
err.flush();
}
/**
* Invoked when an the user has specified an illegal parameter. The default
* implementation prints the localized error message to the standard output
* {@link #out}, and then exit the virtual machine. User may override this
* method if they want a different behavior.
* <p>
* This method <em>is not</em> invoked when an anormal error occured (for
* example an unexpected {@code NullPointerException} in some of developper's
* module). If such an error occurs, the normal exception mechanism will be used.
*
* @param exception An exception with a message describing the user's error.
*/
protected void illegalArgument(final Exception exception) {
printSummary(exception);
System.exit(ILLEGAL_ARGUMENT_EXIT_CODE);
}
}