1 /*
2  * Copyright (C) 2015 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 package android.util;
17 
18 import android.text.TextUtils;
19 import android.util.proto.ProtoOutputStream;
20 
21 import java.io.PrintWriter;
22 import java.time.Duration;
23 import java.time.format.DateTimeParseException;
24 
25 /**
26  * Parses a list of key=value pairs, separated by some delimiter, and puts the results in
27  * an internal Map. Values can be then queried by key, or if not found, a default value
28  * can be used.
29  * @hide
30  */
31 @android.ravenwood.annotation.RavenwoodKeepWholeClass
32 public class KeyValueListParser {
33     private final ArrayMap<String, String> mValues = new ArrayMap<>();
34     private final TextUtils.StringSplitter mSplitter;
35 
36     /**
37      * Constructs a new KeyValueListParser. This can be reused for different strings
38      * by calling {@link #setString(String)}.
39      * @param delim The delimiter that separates key=value pairs.
40      */
KeyValueListParser(char delim)41     public KeyValueListParser(char delim) {
42         mSplitter = new TextUtils.SimpleStringSplitter(delim);
43     }
44 
45     /**
46      * Resets the parser with a new string to parse. The string is expected to be in the following
47      * format:
48      * <pre>key1=value,key2=value,key3=value</pre>
49      *
50      * where the delimiter is a comma.
51      *
52      * @param str the string to parse.
53      * @throws IllegalArgumentException if the string is malformed.
54      */
setString(String str)55     public void setString(String str) throws IllegalArgumentException {
56         mValues.clear();
57         if (str != null) {
58             mSplitter.setString(str);
59             for (String pair : mSplitter) {
60                 int sep = pair.indexOf('=');
61                 if (sep < 0) {
62                     mValues.clear();
63                     throw new IllegalArgumentException(
64                             "'" + pair + "' in '" + str + "' is not a valid key-value pair");
65                 }
66                 mValues.put(pair.substring(0, sep).trim(), pair.substring(sep + 1).trim());
67             }
68         }
69     }
70 
71     /**
72      * Get the value for key as an int.
73      * @param key The key to lookup.
74      * @param def The value to return if the key was not found, or the value was not a long.
75      * @return the int value associated with the key.
76      */
getInt(String key, int def)77     public int getInt(String key, int def) {
78         String value = mValues.get(key);
79         if (value != null) {
80             try {
81                 return Integer.parseInt(value);
82             } catch (NumberFormatException e) {
83                 // fallthrough
84             }
85         }
86         return def;
87     }
88 
89     /**
90      * Get the value for key as a long.
91      * @param key The key to lookup.
92      * @param def The value to return if the key was not found, or the value was not a long.
93      * @return the long value associated with the key.
94      */
getLong(String key, long def)95     public long getLong(String key, long def) {
96         String value = mValues.get(key);
97         if (value != null) {
98             try {
99                 return Long.parseLong(value);
100             } catch (NumberFormatException e) {
101                 // fallthrough
102             }
103         }
104         return def;
105     }
106 
107     /**
108      * Get the value for key as a float.
109      * @param key The key to lookup.
110      * @param def The value to return if the key was not found, or the value was not a float.
111      * @return the float value associated with the key.
112      */
getFloat(String key, float def)113     public float getFloat(String key, float def) {
114         String value = mValues.get(key);
115         if (value != null) {
116             try {
117                 return Float.parseFloat(value);
118             } catch (NumberFormatException e) {
119                 // fallthrough
120             }
121         }
122         return def;
123     }
124 
125     /**
126      * Get the value for key as a string.
127      * @param key The key to lookup.
128      * @param def The value to return if the key was not found.
129      * @return the string value associated with the key.
130      */
getString(String key, String def)131     public String getString(String key, String def) {
132         String value = mValues.get(key);
133         if (value != null) {
134             return value;
135         }
136         return def;
137     }
138 
139     /**
140      * Get the value for key as a boolean.
141      * @param key The key to lookup.
142      * @param def The value to return if the key was not found.
143      * @return the string value associated with the key.
144      */
getBoolean(String key, boolean def)145     public boolean getBoolean(String key, boolean def) {
146         String value = mValues.get(key);
147         if (value != null) {
148             try {
149                 return Boolean.parseBoolean(value);
150             } catch (NumberFormatException e) {
151                 // fallthrough
152             }
153         }
154         return def;
155     }
156 
157     /**
158      * Get the value for key as an integer array.
159      *
160      * The value should be encoded as "0:1:2:3:4"
161      *
162      * @param key The key to lookup.
163      * @param def The value to return if the key was not found.
164      * @return the int[] value associated with the key.
165      */
getIntArray(String key, int[] def)166     public int[] getIntArray(String key, int[] def) {
167         String value = mValues.get(key);
168         if (value != null) {
169             try {
170                 String[] parts = value.split(":");
171                 if (parts.length > 0) {
172                     int[] ret = new int[parts.length];
173                     for (int i = 0; i < parts.length; i++) {
174                         ret[i] = Integer.parseInt(parts[i]);
175                     }
176                     return ret;
177                 }
178             } catch (NumberFormatException e) {
179                 // fallthrough
180             }
181         }
182         return def;
183     }
184 
185     /**
186      * @return the number of keys.
187      */
size()188     public int size() {
189         return mValues.size();
190     }
191 
192     /**
193      * @return the key at {@code index}. Use with {@link #size()} to enumerate all key-value pairs.
194      */
keyAt(int index)195     public String keyAt(int index) {
196         return mValues.keyAt(index);
197     }
198 
199     /**
200      * {@hide}
201      * Parse a duration in millis based on java.time.Duration or just a number (millis)
202      */
getDurationMillis(String key, long def)203     public long getDurationMillis(String key, long def) {
204         String value = mValues.get(key);
205         if (value != null) {
206             try {
207                 if (value.startsWith("P") || value.startsWith("p")) {
208                     return Duration.parse(value).toMillis();
209                 } else {
210                     return Long.parseLong(value);
211                 }
212             } catch (NumberFormatException | DateTimeParseException e) {
213                 // fallthrough
214             }
215         }
216         return def;
217     }
218 
219     /** Represents an integer config value. */
220     public static class IntValue {
221         private final String mKey;
222         private final int mDefaultValue;
223         private int mValue;
224 
225         /** Constructor, initialize with a config key and a default value. */
IntValue(String key, int defaultValue)226         public IntValue(String key, int defaultValue) {
227             mKey = key;
228             mDefaultValue = defaultValue;
229             mValue = mDefaultValue;
230         }
231 
232         /** Read a value from {@link KeyValueListParser} */
parse(KeyValueListParser parser)233         public void parse(KeyValueListParser parser) {
234             mValue = parser.getInt(mKey, mDefaultValue);
235         }
236 
237         /** Return the config key. */
getKey()238         public String getKey() {
239             return mKey;
240         }
241 
242         /** Return the default value. */
getDefaultValue()243         public int getDefaultValue() {
244             return mDefaultValue;
245         }
246 
247         /** Return the actual config value. */
getValue()248         public int getValue() {
249             return mValue;
250         }
251 
252         /** Overwrites with a value. */
setValue(int value)253         public void setValue(int value) {
254             mValue = value;
255         }
256 
257         /** Used for dumpsys */
dump(PrintWriter pw, String prefix)258         public void dump(PrintWriter pw, String prefix) {
259             pw.print(prefix);
260             pw.print(mKey);
261             pw.print("=");
262             pw.print(mValue);
263             pw.println();
264         }
265 
266         /** Used for proto dumpsys */
dumpProto(ProtoOutputStream proto, long tag)267         public void dumpProto(ProtoOutputStream proto, long tag) {
268             proto.write(tag, mValue);
269         }
270     }
271 
272     /** Represents an long config value. */
273     public static class LongValue {
274         private final String mKey;
275         private final long mDefaultValue;
276         private long mValue;
277 
278         /** Constructor, initialize with a config key and a default value. */
LongValue(String key, long defaultValue)279         public LongValue(String key, long defaultValue) {
280             mKey = key;
281             mDefaultValue = defaultValue;
282             mValue = mDefaultValue;
283         }
284 
285         /** Read a value from {@link KeyValueListParser} */
parse(KeyValueListParser parser)286         public void parse(KeyValueListParser parser) {
287             mValue = parser.getLong(mKey, mDefaultValue);
288         }
289 
290         /** Return the config key. */
getKey()291         public String getKey() {
292             return mKey;
293         }
294 
295         /** Return the default value. */
getDefaultValue()296         public long getDefaultValue() {
297             return mDefaultValue;
298         }
299 
300         /** Return the actual config value. */
getValue()301         public long getValue() {
302             return mValue;
303         }
304 
305         /** Overwrites with a value. */
setValue(long value)306         public void setValue(long value) {
307             mValue = value;
308         }
309 
310         /** Used for dumpsys */
dump(PrintWriter pw, String prefix)311         public void dump(PrintWriter pw, String prefix) {
312             pw.print(prefix);
313             pw.print(mKey);
314             pw.print("=");
315             pw.print(mValue);
316             pw.println();
317         }
318 
319         /** Used for proto dumpsys */
dumpProto(ProtoOutputStream proto, long tag)320         public void dumpProto(ProtoOutputStream proto, long tag) {
321             proto.write(tag, mValue);
322         }
323     }
324 
325     /** Represents an string config value. */
326     public static class StringValue {
327         private final String mKey;
328         private final String mDefaultValue;
329         private String mValue;
330 
331         /** Constructor, initialize with a config key and a default value. */
StringValue(String key, String defaultValue)332         public StringValue(String key, String defaultValue) {
333             mKey = key;
334             mDefaultValue = defaultValue;
335             mValue = mDefaultValue;
336         }
337 
338         /** Read a value from {@link KeyValueListParser} */
parse(KeyValueListParser parser)339         public void parse(KeyValueListParser parser) {
340             mValue = parser.getString(mKey, mDefaultValue);
341         }
342 
343         /** Return the config key. */
getKey()344         public String getKey() {
345             return mKey;
346         }
347 
348         /** Return the default value. */
getDefaultValue()349         public String getDefaultValue() {
350             return mDefaultValue;
351         }
352 
353         /** Return the actual config value. */
getValue()354         public String getValue() {
355             return mValue;
356         }
357 
358         /** Overwrites with a value. */
setValue(String value)359         public void setValue(String value) {
360             mValue = value;
361         }
362 
363         /** Used for dumpsys */
dump(PrintWriter pw, String prefix)364         public void dump(PrintWriter pw, String prefix) {
365             pw.print(prefix);
366             pw.print(mKey);
367             pw.print("=");
368             pw.print(mValue);
369             pw.println();
370         }
371 
372         /** Used for proto dumpsys */
dumpProto(ProtoOutputStream proto, long tag)373         public void dumpProto(ProtoOutputStream proto, long tag) {
374             proto.write(tag, mValue);
375         }
376     }
377 
378     /** Represents an float config value. */
379     public static class FloatValue {
380         private final String mKey;
381         private final float mDefaultValue;
382         private float mValue;
383 
384         /** Constructor, initialize with a config key and a default value. */
FloatValue(String key, float defaultValue)385         public FloatValue(String key, float defaultValue) {
386             mKey = key;
387             mDefaultValue = defaultValue;
388             mValue = mDefaultValue;
389         }
390 
391         /** Read a value from {@link KeyValueListParser} */
parse(KeyValueListParser parser)392         public void parse(KeyValueListParser parser) {
393             mValue = parser.getFloat(mKey, mDefaultValue);
394         }
395 
396         /** Return the config key. */
getKey()397         public String getKey() {
398             return mKey;
399         }
400 
401         /** Return the default value. */
getDefaultValue()402         public float getDefaultValue() {
403             return mDefaultValue;
404         }
405 
406         /** Return the actual config value. */
getValue()407         public float getValue() {
408             return mValue;
409         }
410 
411         /** Overwrites with a value. */
setValue(float value)412         public void setValue(float value) {
413             mValue = value;
414         }
415 
416         /** Used for dumpsys */
dump(PrintWriter pw, String prefix)417         public void dump(PrintWriter pw, String prefix) {
418             pw.print(prefix);
419             pw.print(mKey);
420             pw.print("=");
421             pw.print(mValue);
422             pw.println();
423         }
424 
425         /** Used for proto dumpsys */
dumpProto(ProtoOutputStream proto, long tag)426         public void dumpProto(ProtoOutputStream proto, long tag) {
427             proto.write(tag, mValue);
428         }
429     }
430 }
431