1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.apksigner; 18 19 import java.util.Arrays; 20 21 /** 22 * Parser of command-line options/switches/flags. 23 * 24 * <p>Supported option formats: 25 * <ul> 26 * <li>{@code --name value}</li> 27 * <li>{@code --name=value}</li> 28 * <li>{@code -name value}</li> 29 * <li>{@code --name} (boolean options only)</li> 30 * </ul> 31 * 32 * <p>To use the parser, create an instance, providing it with the command-line parameters, then 33 * iterate over options by invoking {@link #nextOption()} until it returns {@code null}. 34 */ 35 class OptionsParser { 36 private final String[] mParams; 37 private int mIndex; 38 private int mPutBackIndex; 39 private String mLastOptionValue; 40 private String mPutBackLastOptionValue; 41 private String mLastOptionOriginalForm; 42 private String mPutBackLastOptionOriginalForm; 43 44 /** 45 * Constructs a new {@code OptionsParser} initialized with the provided command-line. 46 */ OptionsParser(String[] params)47 public OptionsParser(String[] params) { 48 mParams = params.clone(); 49 } 50 51 /** 52 * Returns the name (without leading dashes) of the next option (starting with the very first 53 * option) or {@code null} if there are no options left. 54 * 55 * <p>The value of this option can be obtained via {@link #getRequiredValue(String)}, 56 * {@link #getRequiredIntValue(String)}, and {@link #getOptionalBooleanValue(boolean)}. 57 */ nextOption()58 public String nextOption() { 59 if (mIndex >= mParams.length) { 60 // No more parameters left 61 return null; 62 } 63 String param = mParams[mIndex]; 64 if (!param.startsWith("-")) { 65 // Not an option 66 return null; 67 } 68 69 mPutBackIndex = mIndex; 70 mIndex++; 71 mPutBackLastOptionOriginalForm = mLastOptionOriginalForm; 72 mLastOptionOriginalForm = param; 73 mPutBackLastOptionValue = mLastOptionValue; 74 mLastOptionValue = null; 75 if (param.startsWith("--")) { 76 // FORMAT: --name value OR --name=value 77 if ("--".equals(param)) { 78 // End of options marker 79 return null; 80 } 81 int valueDelimiterIndex = param.indexOf('='); 82 if (valueDelimiterIndex != -1) { 83 mLastOptionValue = param.substring(valueDelimiterIndex + 1); 84 mLastOptionOriginalForm = param.substring(0, valueDelimiterIndex); 85 return param.substring("--".length(), valueDelimiterIndex); 86 } else { 87 return param.substring("--".length()); 88 } 89 } else { 90 // FORMAT: -name value 91 return param.substring("-".length()); 92 } 93 } 94 95 /** 96 * Undoes the last call to nextOption(), if one was made. This allows callers to unwind state 97 * so as not to eat up an option that is meant to be processed elsewhere. 98 */ putOption()99 public void putOption() { 100 mIndex = mPutBackIndex; 101 mLastOptionOriginalForm = mPutBackLastOptionOriginalForm; 102 mLastOptionValue = mPutBackLastOptionValue; 103 } 104 /** 105 * Returns the original form of the current option. The original form includes the leading dash 106 * or dashes. This is intended to be used for referencing the option in error messages. 107 */ getOptionOriginalForm()108 public String getOptionOriginalForm() { 109 return mLastOptionOriginalForm; 110 } 111 112 /** 113 * Returns the value of the current option, throwing an exception if the value is missing. 114 */ getRequiredValue(String valueDescription)115 public String getRequiredValue(String valueDescription) throws OptionsException { 116 if (mLastOptionValue != null) { 117 String result = mLastOptionValue; 118 mLastOptionValue = null; 119 return result; 120 } 121 if (mIndex >= mParams.length) { 122 // No more parameters left 123 throw new OptionsException( 124 valueDescription + " missing after " + mLastOptionOriginalForm); 125 } 126 String param = mParams[mIndex]; 127 if ("--".equals(param)) { 128 // End of options marker 129 throw new OptionsException( 130 valueDescription + " missing after " + mLastOptionOriginalForm); 131 } 132 mIndex++; 133 return param; 134 } 135 136 /** 137 * Returns the value of the current numeric option, throwing an exception if the value is 138 * missing or is not numeric. 139 */ getRequiredIntValue(String valueDescription)140 public int getRequiredIntValue(String valueDescription) throws OptionsException { 141 String value = getRequiredValue(valueDescription); 142 try { 143 return Integer.parseInt(value); 144 } catch (NumberFormatException e) { 145 throw new OptionsException( 146 valueDescription + " (" + mLastOptionOriginalForm 147 + ") must be a decimal number: " + value); 148 } 149 } 150 151 /** 152 * Gets the value of the current boolean option. Boolean options are not required to have 153 * explicitly specified values. 154 */ getOptionalBooleanValue(boolean defaultValue)155 public boolean getOptionalBooleanValue(boolean defaultValue) throws OptionsException { 156 if (mLastOptionValue != null) { 157 // --option=value form 158 String stringValue = mLastOptionValue; 159 mLastOptionValue = null; 160 if ("true".equals(stringValue)) { 161 return true; 162 } else if ("false".equals(stringValue)) { 163 return false; 164 } 165 throw new OptionsException( 166 "Unsupported value for " + mLastOptionOriginalForm + ": " + stringValue 167 + ". Only true or false supported."); 168 } 169 170 // --option (true|false) form OR just --option 171 if (mIndex >= mParams.length) { 172 return defaultValue; 173 } 174 175 String stringValue = mParams[mIndex]; 176 if ("true".equals(stringValue)) { 177 mIndex++; 178 return true; 179 } else if ("false".equals(stringValue)) { 180 mIndex++; 181 return false; 182 } else { 183 return defaultValue; 184 } 185 } 186 187 /** 188 * Returns the remaining command-line parameters. This is intended to be invoked once 189 * {@link #nextOption()} returns {@code null}. 190 */ getRemainingParams()191 public String[] getRemainingParams() { 192 if (mIndex >= mParams.length) { 193 return new String[0]; 194 } 195 String param = mParams[mIndex]; 196 if ("--".equals(param)) { 197 // Skip end of options marker 198 return Arrays.copyOfRange(mParams, mIndex + 1, mParams.length); 199 } else { 200 return Arrays.copyOfRange(mParams, mIndex, mParams.length); 201 } 202 } 203 204 /** 205 * Indicates that an error was encountered while parsing command-line options. 206 */ 207 public static class OptionsException extends Exception { 208 private static final long serialVersionUID = 1L; 209 OptionsException(String message)210 public OptionsException(String message) { 211 super(message); 212 } 213 } 214 } 215