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 String mLastOptionValue; 39 private String mLastOptionOriginalForm; 40 41 /** 42 * Constructs a new {@code OptionsParser} initialized with the provided command-line. 43 */ OptionsParser(String[] params)44 public OptionsParser(String[] params) { 45 mParams = params.clone(); 46 } 47 48 /** 49 * Returns the name (without leading dashes) of the next option (starting with the very first 50 * option) or {@code null} if there are no options left. 51 * 52 * <p>The value of this option can be obtained via {@link #getRequiredValue(String)}, 53 * {@link #getRequiredIntValue(String)}, and {@link #getOptionalBooleanValue(boolean)}. 54 */ nextOption()55 public String nextOption() { 56 if (mIndex >= mParams.length) { 57 // No more parameters left 58 return null; 59 } 60 String param = mParams[mIndex]; 61 if (!param.startsWith("-")) { 62 // Not an option 63 return null; 64 } 65 66 mIndex++; 67 mLastOptionOriginalForm = param; 68 mLastOptionValue = null; 69 if (param.startsWith("--")) { 70 // FORMAT: --name value OR --name=value 71 if ("--".equals(param)) { 72 // End of options marker 73 return null; 74 } 75 int valueDelimiterIndex = param.indexOf('='); 76 if (valueDelimiterIndex != -1) { 77 mLastOptionValue = param.substring(valueDelimiterIndex + 1); 78 mLastOptionOriginalForm = param.substring(0, valueDelimiterIndex); 79 return param.substring("--".length(), valueDelimiterIndex); 80 } else { 81 return param.substring("--".length()); 82 } 83 } else { 84 // FORMAT: -name value 85 return param.substring("-".length()); 86 } 87 } 88 89 /** 90 * Returns the original form of the current option. The original form includes the leading dash 91 * or dashes. This is intended to be used for referencing the option in error messages. 92 */ getOptionOriginalForm()93 public String getOptionOriginalForm() { 94 return mLastOptionOriginalForm; 95 } 96 97 /** 98 * Returns the value of the current option, throwing an exception if the value is missing. 99 */ getRequiredValue(String valueDescription)100 public String getRequiredValue(String valueDescription) throws OptionsException { 101 if (mLastOptionValue != null) { 102 String result = mLastOptionValue; 103 mLastOptionValue = null; 104 return result; 105 } 106 if (mIndex >= mParams.length) { 107 // No more parameters left 108 throw new OptionsException( 109 valueDescription + " missing after " + mLastOptionOriginalForm); 110 } 111 String param = mParams[mIndex]; 112 if ("--".equals(param)) { 113 // End of options marker 114 throw new OptionsException( 115 valueDescription + " missing after " + mLastOptionOriginalForm); 116 } 117 mIndex++; 118 return param; 119 } 120 121 /** 122 * Returns the value of the current numeric option, throwing an exception if the value is 123 * missing or is not numeric. 124 */ getRequiredIntValue(String valueDescription)125 public int getRequiredIntValue(String valueDescription) throws OptionsException { 126 String value = getRequiredValue(valueDescription); 127 try { 128 return Integer.parseInt(value); 129 } catch (NumberFormatException e) { 130 throw new OptionsException( 131 valueDescription + " (" + mLastOptionOriginalForm 132 + ") must be a decimal number: " + value); 133 } 134 } 135 136 /** 137 * Gets the value of the current boolean option. Boolean options are not required to have 138 * explicitly specified values. 139 */ getOptionalBooleanValue(boolean defaultValue)140 public boolean getOptionalBooleanValue(boolean defaultValue) throws OptionsException { 141 if (mLastOptionValue != null) { 142 // --option=value form 143 String stringValue = mLastOptionValue; 144 mLastOptionValue = null; 145 if ("true".equals(stringValue)) { 146 return true; 147 } else if ("false".equals(stringValue)) { 148 return false; 149 } 150 throw new OptionsException( 151 "Unsupported value for " + mLastOptionOriginalForm + ": " + stringValue 152 + ". Only true or false supported."); 153 } 154 155 // --option (true|false) form OR just --option 156 if (mIndex >= mParams.length) { 157 return defaultValue; 158 } 159 160 String stringValue = mParams[mIndex]; 161 if ("true".equals(stringValue)) { 162 mIndex++; 163 return true; 164 } else if ("false".equals(stringValue)) { 165 mIndex++; 166 return false; 167 } else { 168 return defaultValue; 169 } 170 } 171 172 /** 173 * Returns the remaining command-line parameters. This is intended to be invoked once 174 * {@link #nextOption()} returns {@code null}. 175 */ getRemainingParams()176 public String[] getRemainingParams() { 177 if (mIndex >= mParams.length) { 178 return new String[0]; 179 } 180 String param = mParams[mIndex]; 181 if ("--".equals(param)) { 182 // Skip end of options marker 183 return Arrays.copyOfRange(mParams, mIndex + 1, mParams.length); 184 } else { 185 return Arrays.copyOfRange(mParams, mIndex, mParams.length); 186 } 187 } 188 189 /** 190 * Indicates that an error was encountered while parsing command-line options. 191 */ 192 public static class OptionsException extends Exception { 193 private static final long serialVersionUID = 1L; 194 OptionsException(String message)195 public OptionsException(String message) { 196 super(message); 197 } 198 } 199 } 200