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