blob: 9a32404a8b5909d3e0471960e952773a8e582346 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed 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 com.android.apksigner;
import java.util.Arrays;
/**
* Parser of command-line options/switches/flags.
*
* <p>Supported option formats:
* <ul>
* <li>{@code --name value}</li>
* <li>{@code --name=value}</li>
* <li>{@code -name value}</li>
* <li>{@code --name} (boolean options only)</li>
* </ul>
*
* <p>To use the parser, create an instance, providing it with the command-line parameters, then
* iterate over options by invoking {@link #nextOption()} until it returns {@code null}.
*/
class OptionsParser {
private final String[] mParams;
private int mIndex;
private int mPutBackIndex;
private String mLastOptionValue;
private String mPutBackLastOptionValue;
private String mLastOptionOriginalForm;
private String mPutBackLastOptionOriginalForm;
/**
* Constructs a new {@code OptionsParser} initialized with the provided command-line.
*/
public OptionsParser(String[] params) {
mParams = params.clone();
}
/**
* Returns the name (without leading dashes) of the next option (starting with the very first
* option) or {@code null} if there are no options left.
*
* <p>The value of this option can be obtained via {@link #getRequiredValue(String)},
* {@link #getRequiredIntValue(String)}, and {@link #getOptionalBooleanValue(boolean)}.
*/
public String nextOption() {
if (mIndex >= mParams.length) {
// No more parameters left
return null;
}
String param = mParams[mIndex];
if (!param.startsWith("-")) {
// Not an option
return null;
}
mPutBackIndex = mIndex;
mIndex++;
mPutBackLastOptionOriginalForm = mLastOptionOriginalForm;
mLastOptionOriginalForm = param;
mPutBackLastOptionValue = mLastOptionValue;
mLastOptionValue = null;
if (param.startsWith("--")) {
// FORMAT: --name value OR --name=value
if ("--".equals(param)) {
// End of options marker
return null;
}
int valueDelimiterIndex = param.indexOf('=');
if (valueDelimiterIndex != -1) {
mLastOptionValue = param.substring(valueDelimiterIndex + 1);
mLastOptionOriginalForm = param.substring(0, valueDelimiterIndex);
return param.substring("--".length(), valueDelimiterIndex);
} else {
return param.substring("--".length());
}
} else {
// FORMAT: -name value
return param.substring("-".length());
}
}
/**
* Undoes the last call to nextOption(), if one was made. This allows callers to unwind state
* so as not to eat up an option that is meant to be processed elsewhere.
*/
public void putOption() {
mIndex = mPutBackIndex;
mLastOptionOriginalForm = mPutBackLastOptionOriginalForm;
mLastOptionValue = mPutBackLastOptionValue;
}
/**
* Returns the original form of the current option. The original form includes the leading dash
* or dashes. This is intended to be used for referencing the option in error messages.
*/
public String getOptionOriginalForm() {
return mLastOptionOriginalForm;
}
/**
* Returns the value of the current option, throwing an exception if the value is missing.
*/
public String getRequiredValue(String valueDescription) throws OptionsException {
if (mLastOptionValue != null) {
String result = mLastOptionValue;
mLastOptionValue = null;
return result;
}
if (mIndex >= mParams.length) {
// No more parameters left
throw new OptionsException(
valueDescription + " missing after " + mLastOptionOriginalForm);
}
String param = mParams[mIndex];
if ("--".equals(param)) {
// End of options marker
throw new OptionsException(
valueDescription + " missing after " + mLastOptionOriginalForm);
}
mIndex++;
return param;
}
/**
* Returns the value of the current numeric option, throwing an exception if the value is
* missing or is not numeric.
*/
public int getRequiredIntValue(String valueDescription) throws OptionsException {
String value = getRequiredValue(valueDescription);
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new OptionsException(
valueDescription + " (" + mLastOptionOriginalForm
+ ") must be a decimal number: " + value);
}
}
/**
* Gets the value of the current boolean option. Boolean options are not required to have
* explicitly specified values.
*/
public boolean getOptionalBooleanValue(boolean defaultValue) throws OptionsException {
if (mLastOptionValue != null) {
// --option=value form
String stringValue = mLastOptionValue;
mLastOptionValue = null;
if ("true".equals(stringValue)) {
return true;
} else if ("false".equals(stringValue)) {
return false;
}
throw new OptionsException(
"Unsupported value for " + mLastOptionOriginalForm + ": " + stringValue
+ ". Only true or false supported.");
}
// --option (true|false) form OR just --option
if (mIndex >= mParams.length) {
return defaultValue;
}
String stringValue = mParams[mIndex];
if ("true".equals(stringValue)) {
mIndex++;
return true;
} else if ("false".equals(stringValue)) {
mIndex++;
return false;
} else {
return defaultValue;
}
}
/**
* Returns the remaining command-line parameters. This is intended to be invoked once
* {@link #nextOption()} returns {@code null}.
*/
public String[] getRemainingParams() {
if (mIndex >= mParams.length) {
return new String[0];
}
String param = mParams[mIndex];
if ("--".equals(param)) {
// Skip end of options marker
return Arrays.copyOfRange(mParams, mIndex + 1, mParams.length);
} else {
return Arrays.copyOfRange(mParams, mIndex, mParams.length);
}
}
/**
* Indicates that an error was encountered while parsing command-line options.
*/
public static class OptionsException extends Exception {
private static final long serialVersionUID = 1L;
public OptionsException(String message) {
super(message);
}
}
}