Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / base / android / java / src / org / chromium / base / CommandLine.java
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.base;
6
7 import android.text.TextUtils;
8 import android.util.Log;
9
10 import java.io.File;
11 import java.io.FileInputStream;
12 import java.io.FileNotFoundException;
13 import java.io.IOException;
14 import java.io.InputStreamReader;
15 import java.io.Reader;
16 import java.util.ArrayList;
17 import java.util.Arrays;
18 import java.util.HashMap;
19 import java.util.concurrent.atomic.AtomicReference;
20
21 /**
22  * Java mirror of base/command_line.h.
23  * Android applications don't have command line arguments. Instead, they're "simulated" by reading a
24  * file at a specific location early during startup. Applications each define their own files, e.g.,
25  * ContentShellActivity.COMMAND_LINE_FILE or ChromeShellApplication.COMMAND_LINE_FILE.
26 **/
27 public abstract class CommandLine {
28     // Public abstract interface, implemented in derived classes.
29     // All these methods reflect their native-side counterparts.
30     /**
31      *  Returns true if this command line contains the given switch.
32      *  (Switch names ARE case-sensitive).
33      */
34     public abstract boolean hasSwitch(String switchString);
35
36     /**
37      * Return the value associated with the given switch, or null.
38      * @param switchString The switch key to lookup. It should NOT start with '--' !
39      * @return switch value, or null if the switch is not set or set to empty.
40      */
41     public abstract String getSwitchValue(String switchString);
42
43     /**
44      * Return the value associated with the given switch, or {@code defaultValue} if the switch
45      * was not specified.
46      * @param switchString The switch key to lookup. It should NOT start with '--' !
47      * @param defaultValue The default value to return if the switch isn't set.
48      * @return Switch value, or {@code defaultValue} if the switch is not set or set to empty.
49      */
50     public String getSwitchValue(String switchString, String defaultValue) {
51         String value = getSwitchValue(switchString);
52         return TextUtils.isEmpty(value) ? defaultValue : value;
53     }
54
55     /**
56      * Append a switch to the command line.  There is no guarantee
57      * this action happens before the switch is needed.
58      * @param switchString the switch to add.  It should NOT start with '--' !
59      */
60     public abstract void appendSwitch(String switchString);
61
62     /**
63      * Append a switch and value to the command line.  There is no
64      * guarantee this action happens before the switch is needed.
65      * @param switchString the switch to add.  It should NOT start with '--' !
66      * @param value the value for this switch.
67      * For example, --foo=bar becomes 'foo', 'bar'.
68      */
69     public abstract void appendSwitchWithValue(String switchString, String value);
70
71     /**
72      * Append switch/value items in "command line" format (excluding argv[0] program name).
73      * E.g. { '--gofast', '--username=fred' }
74      * @param array an array of switch or switch/value items in command line format.
75      *   Unlike the other append routines, these switches SHOULD start with '--' .
76      *   Unlike init(), this does not include the program name in array[0].
77      */
78     public abstract void appendSwitchesAndArguments(String[] array);
79
80     /**
81      * Determine if the command line is bound to the native (JNI) implementation.
82      * @return true if the underlying implementation is delegating to the native command line.
83      */
84     public boolean isNativeImplementation() {
85         return false;
86     }
87
88     private static final AtomicReference<CommandLine> sCommandLine =
89         new AtomicReference<CommandLine>();
90
91     /**
92      * @returns true if the command line has already been initialized.
93      */
94     public static boolean isInitialized() {
95         return sCommandLine.get() != null;
96     }
97
98     // Equivalent to CommandLine::ForCurrentProcess in C++.
99     public static CommandLine getInstance() {
100         CommandLine commandLine = sCommandLine.get();
101         assert commandLine != null;
102         return commandLine;
103     }
104
105     /**
106      * Initialize the singleton instance, must be called exactly once (either directly or
107      * via one of the convenience wrappers below) before using the static singleton instance.
108      * @param args command line flags in 'argv' format: args[0] is the program name.
109      */
110     public static void init(String[] args) {
111         setInstance(new JavaCommandLine(args));
112     }
113
114     /**
115      * Initialize the command line from the command-line file.
116      *
117      * @param file The fully qualified command line file.
118      */
119     public static void initFromFile(String file) {
120         // Arbitrary clamp of 8k on the amount of file we read in.
121         char[] buffer = readUtf8FileFully(file, 8 * 1024);
122         init(buffer == null ? null : tokenizeQuotedAruments(buffer));
123     }
124
125     /**
126      * Resets both the java proxy and the native command lines. This allows the entire
127      * command line initialization to be re-run including the call to onJniLoaded.
128      */
129     @VisibleForTesting
130     public static void reset() {
131         setInstance(null);
132     }
133
134     /**
135      * Public for testing (TODO: why are the tests in a different package?)
136      * Parse command line flags from a flat buffer, supporting double-quote enclosed strings
137      * containing whitespace. argv elements are derived by splitting the buffer on whitepace;
138      * double quote characters may enclose tokens containing whitespace; a double-quote literal
139      * may be escaped with back-slash. (Otherwise backslash is taken as a literal).
140      * @param buffer A command line in command line file format as described above.
141      * @return the tokenized arguments, suitable for passing to init().
142      */
143     public static String[] tokenizeQuotedAruments(char[] buffer) {
144         ArrayList<String> args = new ArrayList<String>();
145         StringBuilder arg = null;
146         final char noQuote = '\0';
147         final char singleQuote = '\'';
148         final char doubleQuote = '"';
149         char currentQuote = noQuote;
150         for (char c : buffer) {
151             // Detect start or end of quote block.
152             if ((currentQuote == noQuote && (c == singleQuote || c == doubleQuote)) ||
153                 c == currentQuote) {
154                 if (arg != null && arg.length() > 0 && arg.charAt(arg.length() - 1) == '\\') {
155                     // Last char was a backslash; pop it, and treat c as a literal.
156                     arg.setCharAt(arg.length() - 1, c);
157                 } else {
158                     currentQuote = currentQuote == noQuote ? c : noQuote;
159                 }
160             } else if (currentQuote == noQuote && Character.isWhitespace(c)) {
161                 if (arg != null) {
162                     args.add(arg.toString());
163                     arg = null;
164                 }
165             } else {
166                 if (arg == null) arg = new StringBuilder();
167                 arg.append(c);
168             }
169         }
170         if (arg != null) {
171             if (currentQuote != noQuote) {
172                 Log.w(TAG, "Unterminated quoted string: " + arg);
173             }
174             args.add(arg.toString());
175         }
176         return args.toArray(new String[args.size()]);
177     }
178
179     private static final String TAG = "CommandLine";
180     private static final String SWITCH_PREFIX = "--";
181     private static final String SWITCH_TERMINATOR = SWITCH_PREFIX;
182     private static final String SWITCH_VALUE_SEPARATOR = "=";
183
184     public static void enableNativeProxy() {
185         // Make a best-effort to ensure we make a clean (atomic) switch over from the old to
186         // the new command line implementation. If another thread is modifying the command line
187         // when this happens, all bets are off. (As per the native CommandLine).
188         sCommandLine.set(new NativeCommandLine());
189     }
190
191     public static String[] getJavaSwitchesOrNull() {
192         CommandLine commandLine = sCommandLine.get();
193         if (commandLine != null) {
194             assert !commandLine.isNativeImplementation();
195             return ((JavaCommandLine) commandLine).getCommandLineArguments();
196         }
197         return null;
198     }
199
200     private static void setInstance(CommandLine commandLine) {
201         CommandLine oldCommandLine = sCommandLine.getAndSet(commandLine);
202         if (oldCommandLine != null && oldCommandLine.isNativeImplementation()) {
203             nativeReset();
204         }
205     }
206
207     /**
208      * @param fileName the file to read in.
209      * @param sizeLimit cap on the file size.
210      * @return Array of chars read from the file, or null if the file cannot be read
211      *         or if its length exceeds |sizeLimit|.
212      */
213     private static char[] readUtf8FileFully(String fileName, int sizeLimit) {
214         Reader reader = null;
215         File f = new File(fileName);
216         long fileLength = f.length();
217
218         if (fileLength == 0) {
219             return null;
220         }
221
222         if (fileLength > sizeLimit) {
223             Log.w(TAG, "File " + fileName + " length " + fileLength + " exceeds limit "
224                     + sizeLimit);
225             return null;
226         }
227
228         try {
229             char[] buffer = new char[(int) fileLength];
230             reader = new InputStreamReader(new FileInputStream(f), "UTF-8");
231             int charsRead = reader.read(buffer);
232             // Debug check that we've exhausted the input stream (will fail e.g. if the
233             // file grew after we inspected its length).
234             assert !reader.ready();
235             return charsRead < buffer.length ? Arrays.copyOfRange(buffer, 0, charsRead) : buffer;
236         } catch (FileNotFoundException e) {
237             return null;
238         } catch (IOException e) {
239             return null;
240         } finally {
241             try {
242                 if (reader != null) reader.close();
243             } catch (IOException e) {
244                 Log.e(TAG, "Unable to close file reader.", e);
245             }
246         }
247     }
248
249     private CommandLine() {}
250
251     private static class JavaCommandLine extends CommandLine {
252         private HashMap<String, String> mSwitches = new HashMap<String, String>();
253         private ArrayList<String> mArgs = new ArrayList<String>();
254
255         // The arguments begin at index 1, since index 0 contains the executable name.
256         private int mArgsBegin = 1;
257
258         JavaCommandLine(String[] args) {
259             if (args == null || args.length == 0 || args[0] == null) {
260                 mArgs.add("");
261             } else {
262                 mArgs.add(args[0]);
263                 appendSwitchesInternal(args, 1);
264             }
265             // Invariant: we always have the argv[0] program name element.
266             assert mArgs.size() > 0;
267         }
268
269         /**
270          * Returns the switches and arguments passed into the program, with switches and their
271          * values coming before all of the arguments.
272          */
273         private String[] getCommandLineArguments() {
274             return mArgs.toArray(new String[mArgs.size()]);
275         }
276
277         @Override
278         public boolean hasSwitch(String switchString) {
279             return mSwitches.containsKey(switchString);
280         }
281
282         @Override
283         public String getSwitchValue(String switchString) {
284             // This is slightly round about, but needed for consistency with the NativeCommandLine
285             // version which does not distinguish empty values from key not present.
286             String value = mSwitches.get(switchString);
287             return value == null || value.isEmpty() ? null : value;
288         }
289
290         @Override
291         public void appendSwitch(String switchString) {
292             appendSwitchWithValue(switchString, null);
293         }
294
295         /**
296          * Appends a switch to the current list.
297          * @param switchString the switch to add.  It should NOT start with '--' !
298          * @param value the value for this switch.
299          */
300         @Override
301         public void appendSwitchWithValue(String switchString, String value) {
302             mSwitches.put(switchString, value == null ? "" : value);
303
304             // Append the switch and update the switches/arguments divider mArgsBegin.
305             String combinedSwitchString = SWITCH_PREFIX + switchString;
306             if (value != null && !value.isEmpty())
307                 combinedSwitchString += SWITCH_VALUE_SEPARATOR + value;
308
309             mArgs.add(mArgsBegin++, combinedSwitchString);
310         }
311
312         @Override
313         public void appendSwitchesAndArguments(String[] array) {
314             appendSwitchesInternal(array, 0);
315         }
316
317         // Add the specified arguments, but skipping the first |skipCount| elements.
318         private void appendSwitchesInternal(String[] array, int skipCount) {
319             boolean parseSwitches = true;
320             for (String arg : array) {
321                 if (skipCount > 0) {
322                     --skipCount;
323                     continue;
324                 }
325
326                 if (arg.equals(SWITCH_TERMINATOR)) {
327                     parseSwitches = false;
328                 }
329
330                 if (parseSwitches && arg.startsWith(SWITCH_PREFIX)) {
331                     String[] parts = arg.split(SWITCH_VALUE_SEPARATOR, 2);
332                     String value = parts.length > 1 ? parts[1] : null;
333                     appendSwitchWithValue(parts[0].substring(SWITCH_PREFIX.length()), value);
334                 } else {
335                     mArgs.add(arg);
336                 }
337             }
338         }
339     }
340
341     private static class NativeCommandLine extends CommandLine {
342         @Override
343         public boolean hasSwitch(String switchString) {
344             return nativeHasSwitch(switchString);
345         }
346
347         @Override
348         public String getSwitchValue(String switchString) {
349             return nativeGetSwitchValue(switchString);
350         }
351
352         @Override
353         public void appendSwitch(String switchString) {
354             nativeAppendSwitch(switchString);
355         }
356
357         @Override
358         public void appendSwitchWithValue(String switchString, String value) {
359             nativeAppendSwitchWithValue(switchString, value);
360         }
361
362         @Override
363         public void appendSwitchesAndArguments(String[] array) {
364             nativeAppendSwitchesAndArguments(array);
365         }
366
367         @Override
368         public boolean isNativeImplementation() {
369             return true;
370         }
371     }
372
373     private static native void nativeReset();
374     private static native boolean nativeHasSwitch(String switchString);
375     private static native String nativeGetSwitchValue(String switchString);
376     private static native void nativeAppendSwitch(String switchString);
377     private static native void nativeAppendSwitchWithValue(String switchString, String value);
378     private static native void nativeAppendSwitchesAndArguments(String[] array);
379 }