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