summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
authorAndroid (Google) Code Review <android-gerrit@google.com>2009-04-23 14:22:58 -0700
committerAndroid (Google) Code Review <android-gerrit@google.com>2009-04-23 14:22:58 -0700
commit5616a7d1dea1aecc8353bab144d14446aa33391a (patch)
treeecebd0582a21fdc54a4a206e0dda9ee794809c45 /core/java
parent24b62d0c41cf37ade751fe96a4888fbe7f61eb5a (diff)
parentc4d6dd0bbce846c3b20e907d6a3016e4adc65e22 (diff)
downloadframeworks_base-5616a7d1dea1aecc8353bab144d14446aa33391a.zip
frameworks_base-5616a7d1dea1aecc8353bab144d14446aa33391a.tar.gz
frameworks_base-5616a7d1dea1aecc8353bab144d14446aa33391a.tar.bz2
Merge change 459 into donut
* changes: TypedProperties: initial commit
Diffstat (limited to 'core/java')
-rw-r--r--core/java/com/android/internal/util/TypedProperties.java692
1 files changed, 692 insertions, 0 deletions
diff --git a/core/java/com/android/internal/util/TypedProperties.java b/core/java/com/android/internal/util/TypedProperties.java
new file mode 100644
index 0000000..48479e3
--- /dev/null
+++ b/core/java/com/android/internal/util/TypedProperties.java
@@ -0,0 +1,692 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StreamTokenizer;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * A {@code Map} that publishes a set of typed properties, defined by
+ * zero or more {@code Reader}s containing textual definitions and assignments.
+ */
+public class TypedProperties extends HashMap<String, Object> {
+ /**
+ * Instantiates a {@link java.io.StreamTokenizer} and sets its syntax tables
+ * appropriately for the {@code TypedProperties} file format.
+ *
+ * @param r The {@code Reader} that the {@code StreamTokenizer} will read from
+ * @return a newly-created and initialized {@code StreamTokenizer}
+ */
+ static StreamTokenizer initTokenizer(Reader r) {
+ StreamTokenizer st = new StreamTokenizer(r);
+
+ // Treat everything we don't specify as "ordinary".
+ st.resetSyntax();
+
+ /* The only non-quoted-string words we'll be reading are:
+ * - property names: [._$a-zA-Z0-9]
+ * - type names (case insensitive): [a-zA-Z]
+ * - number literals: [-0-9.eExXA-Za-z] ('x' for 0xNNN hex literals. "NaN", "Infinity")
+ * - "true" or "false" (case insensitive): [a-zA-Z]
+ */
+ st.wordChars('0', '9');
+ st.wordChars('A', 'Z');
+ st.wordChars('a', 'z');
+ st.wordChars('_', '_');
+ st.wordChars('$', '$');
+ st.wordChars('.', '.');
+ st.wordChars('-', '-');
+ st.wordChars('+', '+');
+
+ // Single-character tokens
+ st.ordinaryChar('=');
+
+ // Other special characters
+ st.whitespaceChars(' ', ' ');
+ st.whitespaceChars('\t', '\t');
+
+ st.quoteChar('"');
+
+ st.commentChar('#');
+
+ st.eolIsSignificant(true);
+
+ return st;
+ }
+
+
+ /**
+ * An unchecked exception that is thrown when encountering a syntax
+ * or semantic error in the input.
+ */
+ public static class ParseException extends IllegalArgumentException {
+ ParseException(StreamTokenizer state, String expected) {
+ super("expected " + expected + ", saw " + state.toString());
+ }
+ }
+
+ // A sentinel instance used to indicate a null string.
+ static final String NULL_STRING = new String("<TypedProperties:NULL_STRING>");
+
+ // Constants used to represent the supported types.
+ static final int TYPE_UNSET = 'x';
+ static final int TYPE_BOOLEAN = 'Z';
+ static final int TYPE_BYTE = 'I' | 1 << 8;
+ // TYPE_CHAR: character literal syntax not supported; use short.
+ static final int TYPE_SHORT = 'I' | 2 << 8;
+ static final int TYPE_INT = 'I' | 4 << 8;
+ static final int TYPE_LONG = 'I' | 8 << 8;
+ static final int TYPE_FLOAT = 'F' | 4 << 8;
+ static final int TYPE_DOUBLE = 'F' | 8 << 8;
+ static final int TYPE_STRING = 'L' | 's' << 8;
+ static final int TYPE_ERROR = -1;
+
+ /**
+ * Converts a case-insensitive string to an internal type constant.
+ *
+ * @param typeName the type name to convert
+ * @return the type constant that corresponds to {@code typeName},
+ * or {@code TYPE_ERROR} if the type is unknown
+ */
+ static int interpretType(String typeName) {
+ if ("unset".equalsIgnoreCase(typeName)) {
+ return TYPE_UNSET;
+ } else if ("boolean".equalsIgnoreCase(typeName)) {
+ return TYPE_BOOLEAN;
+ } else if ("byte".equalsIgnoreCase(typeName)) {
+ return TYPE_BYTE;
+ } else if ("short".equalsIgnoreCase(typeName)) {
+ return TYPE_SHORT;
+ } else if ("int".equalsIgnoreCase(typeName)) {
+ return TYPE_INT;
+ } else if ("long".equalsIgnoreCase(typeName)) {
+ return TYPE_LONG;
+ } else if ("float".equalsIgnoreCase(typeName)) {
+ return TYPE_FLOAT;
+ } else if ("double".equalsIgnoreCase(typeName)) {
+ return TYPE_DOUBLE;
+ } else if ("string".equalsIgnoreCase(typeName)) {
+ return TYPE_STRING;
+ }
+ return TYPE_ERROR;
+ }
+
+ /**
+ * Consumes EOL tokens.
+ * Returns when a non-EOL token is found.
+ *
+ * @param st The {@code StreamTokenizer} to read tokens from
+ * @return &gt; 0 if an EOL token was seen, &lt; 0 if EOF was seen,
+ * 0 if no tokens were consumed
+ */
+ static int eatEols(StreamTokenizer st) throws IOException {
+ int token;
+ boolean eolSeen = false;
+ do {
+ token = st.nextToken();
+ if (token == StreamTokenizer.TT_EOF) {
+ return -1;
+ } else if (token == StreamTokenizer.TT_EOL) {
+ eolSeen = true;
+ }
+ } while (token == StreamTokenizer.TT_EOL);
+ st.pushBack();
+ return eolSeen ? 1 : 0;
+ }
+
+ /**
+ * Parses the data in the reader.
+ *
+ * @param r The {@code Reader} containing input data to parse
+ * @param map The {@code Map} to insert parameter values into
+ * @throws ParseException if the input data is malformed
+ * @throws IOException if there is a problem reading from the {@code Reader}
+ */
+ static void parse(Reader r, Map<String, Object> map) throws ParseException, IOException {
+ final StreamTokenizer st = initTokenizer(r);
+
+ /* A property name must be a valid fully-qualified class + package name.
+ * We don't support Unicode, though.
+ */
+ final String identifierPattern = "[a-zA-Z_$][0-9a-zA-Z_$]*";
+ final Pattern propertyNamePattern =
+ Pattern.compile("(" + identifierPattern + "\\.)*" + identifierPattern);
+
+
+ boolean eolNeeded = false;
+ while (true) {
+ int token;
+
+ // Eat one or more EOL, or quit on EOF.
+ int eolStatus = eatEols(st);
+ if (eolStatus < 0) {
+ // EOF occurred.
+ break;
+ } else if (eolNeeded && eolStatus == 0) {
+ throw new ParseException(st, "end of line or end of file");
+ }
+
+ // Read the property name.
+ token = st.nextToken();
+ if (token != StreamTokenizer.TT_WORD) {
+ throw new ParseException(st, "property name");
+ }
+ final String propertyName = st.sval;
+ if (!propertyNamePattern.matcher(propertyName).matches()) {
+ throw new ParseException(st, "valid property name");
+ }
+ st.sval = null;
+
+ // Read the type.
+ token = st.nextToken();
+ if (token != StreamTokenizer.TT_WORD) {
+ throw new ParseException(st, "type name");
+ }
+ final int type = interpretType(st.sval);
+ if (type == TYPE_ERROR) {
+ throw new ParseException(st, "valid type name");
+ }
+ st.sval = null;
+
+ if (type == TYPE_UNSET) {
+ map.remove(propertyName);
+ } else {
+ // Expect '='.
+ token = st.nextToken();
+ if (token != '=') {
+ throw new ParseException(st, "'='");
+ }
+
+ // Read a value of the appropriate type, and insert into the map.
+ final Object value = parseValue(st, type);
+ final Object oldValue = map.remove(propertyName);
+ if (oldValue != null) {
+ // TODO: catch the case where a string is set to null and then
+ // the same property is defined with a different type.
+ if (value.getClass() != oldValue.getClass()) {
+ throw new ParseException(st,
+ "(property previously declared as a different type)");
+ }
+ }
+ map.put(propertyName, value);
+ }
+
+ // Require that we see at least one EOL before the next token.
+ eolNeeded = true;
+ }
+ }
+
+ /**
+ * Parses the next token in the StreamTokenizer as the specified type.
+ *
+ * @param st The token source
+ * @param type The type to interpret next token as
+ * @return a Boolean, Number subclass, or String representing the value.
+ * Null strings are represented by the String instance NULL_STRING
+ * @throws IOException if there is a problem reading from the {@code StreamTokenizer}
+ */
+ static Object parseValue(StreamTokenizer st, final int type) throws IOException {
+ final int token = st.nextToken();
+
+ if (type == TYPE_BOOLEAN) {
+ if (token != StreamTokenizer.TT_WORD) {
+ throw new ParseException(st, "boolean constant");
+ }
+
+ if ("true".equalsIgnoreCase(st.sval)) {
+ return Boolean.TRUE;
+ } else if ("false".equalsIgnoreCase(st.sval)) {
+ return Boolean.FALSE;
+ }
+
+ throw new ParseException(st, "boolean constant");
+ } else if ((type & 0xff) == 'I') {
+ if (token != StreamTokenizer.TT_WORD) {
+ throw new ParseException(st, "integer constant");
+ }
+
+ /* Parse the string. Long.decode() handles C-style integer constants
+ * ("0x" -> hex, "0" -> octal). It also treats numbers with a prefix of "#" as
+ * hex, but our syntax intentionally does not list '#' as a word character.
+ */
+ long value;
+ try {
+ value = Long.decode(st.sval);
+ } catch (NumberFormatException ex) {
+ throw new ParseException(st, "integer constant");
+ }
+
+ // Ensure that the type can hold this value, and return.
+ int width = (type >> 8) & 0xff;
+ switch (width) {
+ case 1:
+ if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
+ throw new ParseException(st, "8-bit integer constant");
+ }
+ return new Byte((byte)value);
+ case 2:
+ if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
+ throw new ParseException(st, "16-bit integer constant");
+ }
+ return new Short((short)value);
+ case 4:
+ if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) {
+ throw new ParseException(st, "32-bit integer constant");
+ }
+ return new Integer((int)value);
+ case 8:
+ if (value < Long.MIN_VALUE || value > Long.MAX_VALUE) {
+ throw new ParseException(st, "64-bit integer constant");
+ }
+ return new Long(value);
+ default:
+ throw new IllegalStateException(
+ "Internal error; unexpected integer type width " + width);
+ }
+ } else if ((type & 0xff) == 'F') {
+ if (token != StreamTokenizer.TT_WORD) {
+ throw new ParseException(st, "float constant");
+ }
+
+ // Parse the string.
+ /* TODO: Maybe just parse as float or double, losing precision if necessary.
+ * Parsing as double and converting to float can change the value
+ * compared to just parsing as float.
+ */
+ double value;
+ try {
+ /* TODO: detect if the string representation loses precision
+ * when being converted to a double.
+ */
+ value = Double.parseDouble(st.sval);
+ } catch (NumberFormatException ex) {
+ throw new ParseException(st, "float constant");
+ }
+
+ // Ensure that the type can hold this value, and return.
+ if (((type >> 8) & 0xff) == 4) {
+ // This property is a float; make sure the value fits.
+ double absValue = Math.abs(value);
+ if (absValue != 0.0 && !Double.isInfinite(value) && !Double.isNaN(value)) {
+ if (absValue < Float.MIN_VALUE || absValue > Float.MAX_VALUE) {
+ throw new ParseException(st, "32-bit float constant");
+ }
+ }
+ return new Float((float)value);
+ } else {
+ // This property is a double; no need to truncate.
+ return new Double(value);
+ }
+ } else if (type == TYPE_STRING) {
+ // Expect a quoted string or the word "null".
+ if (token == '"') {
+ return st.sval;
+ } else if (token == StreamTokenizer.TT_WORD && "null".equalsIgnoreCase(st.sval)) {
+ return NULL_STRING;
+ }
+ throw new ParseException(st, "double-quoted string or 'null'");
+ }
+
+ throw new IllegalStateException("Internal error; unknown type " + type);
+ }
+
+
+ /**
+ * Creates an empty TypedProperties instance.
+ */
+ public TypedProperties() {
+ super();
+ }
+
+ /**
+ * Loads zero or more properties from the specified Reader.
+ * Properties that have already been loaded are preserved unless
+ * the new Reader overrides or unsets earlier values for the
+ * same properties.
+ *
+ * File syntax:
+ *
+ * &lt;property-name&gt; &lt;type&gt; = &lt;value&gt;
+ * &lt;property-name&gt; unset
+ *
+ * '#' is a comment character; it and anything appearing after it
+ * on the line is ignored.
+ *
+ * Blank lines are ignored.
+ *
+ * The only required whitespace is between the property name
+ * and the type.
+ *
+ * Property assignments may not be split across multiple lines.
+ *
+ * &lt;property-name&gt; is a valid fully-qualified class name
+ * (one or more valid identifiers separated by dot characters).
+ *
+ * &lt;type&gt; is one of {boolean, byte, short, int, long,
+ * float, double, string}, and is case-insensitive.
+ *
+ * &lt;value&gt; depends on the type:
+ * - boolean: one of {true, false} (case-insensitive)
+ * - byte, short, int, long: a valid Java integer constant
+ * (including non-base-10 constants like 0xabc and 074)
+ * whose value does not overflow the type. NOTE: these are
+ * interpreted as Java integer values, so they are all signed.
+ * - float, double: a valid Java floating-point constant.
+ * If the type is float, the value must fit in 32 bits.
+ * - string: a double-quoted string value, or the word {@code null}.
+ * NOTE: the contents of the string must be 7-bit clean ASCII;
+ * C-style octal escapes are recognized, but Unicode escapes are not.
+ *
+ *
+ * @param r The Reader to load properties from
+ * @throws IOException if an error occurs when reading the data
+ * @throws IllegalArgumentException if the data is malformed
+ */
+ public void load(Reader r) throws IOException {
+ parse(r, this);
+ }
+
+ @Override
+ public Object get(Object key) {
+ Object value = super.get(key);
+ if (value == NULL_STRING) {
+ return null;
+ }
+ return value;
+ }
+
+ /*
+ * Getters with explicit defaults
+ */
+
+ /**
+ * An unchecked exception that is thrown if a {@code get&lt;TYPE&gt;()} method
+ * is used to retrieve a parameter whose type does not match the method name.
+ */
+ public static class TypeException extends IllegalArgumentException {
+ TypeException(String property, Object value, String requestedType) {
+ super(property + " has type " + value.getClass().getName() +
+ ", not " + requestedType);
+ }
+ }
+
+ /**
+ * Returns the value of a boolean property, or the default if the property
+ * has not been defined.
+ *
+ * @param property The name of the property to return
+ * @param def The default value to return if the property is not set
+ * @return the value of the property
+ * @throws TypeException if the property is set and is not a boolean
+ */
+ public boolean getBoolean(String property, boolean def) {
+ Object value = super.get(property);
+ if (value == null) {
+ return def;
+ }
+ if (value instanceof Boolean) {
+ return ((Boolean)value).booleanValue();
+ }
+ throw new TypeException(property, value, "boolean");
+ }
+
+ /**
+ * Returns the value of a byte property, or the default if the property
+ * has not been defined.
+ *
+ * @param property The name of the property to return
+ * @param def The default value to return if the property is not set
+ * @return the value of the property
+ * @throws TypeException if the property is set and is not a byte
+ */
+ public byte getByte(String property, byte def) {
+ Object value = super.get(property);
+ if (value == null) {
+ return def;
+ }
+ if (value instanceof Byte) {
+ return ((Byte)value).byteValue();
+ }
+ throw new TypeException(property, value, "byte");
+ }
+
+ /**
+ * Returns the value of a short property, or the default if the property
+ * has not been defined.
+ *
+ * @param property The name of the property to return
+ * @param def The default value to return if the property is not set
+ * @return the value of the property
+ * @throws TypeException if the property is set and is not a short
+ */
+ public short getShort(String property, short def) {
+ Object value = super.get(property);
+ if (value == null) {
+ return def;
+ }
+ if (value instanceof Short) {
+ return ((Short)value).shortValue();
+ }
+ throw new TypeException(property, value, "short");
+ }
+
+ /**
+ * Returns the value of an integer property, or the default if the property
+ * has not been defined.
+ *
+ * @param property The name of the property to return
+ * @param def The default value to return if the property is not set
+ * @return the value of the property
+ * @throws TypeException if the property is set and is not an integer
+ */
+ public int getInt(String property, int def) {
+ Object value = super.get(property);
+ if (value == null) {
+ return def;
+ }
+ if (value instanceof Integer) {
+ return ((Integer)value).intValue();
+ }
+ throw new TypeException(property, value, "int");
+ }
+
+ /**
+ * Returns the value of a long property, or the default if the property
+ * has not been defined.
+ *
+ * @param property The name of the property to return
+ * @param def The default value to return if the property is not set
+ * @return the value of the property
+ * @throws TypeException if the property is set and is not a long
+ */
+ public long getLong(String property, long def) {
+ Object value = super.get(property);
+ if (value == null) {
+ return def;
+ }
+ if (value instanceof Long) {
+ return ((Long)value).longValue();
+ }
+ throw new TypeException(property, value, "long");
+ }
+
+ /**
+ * Returns the value of a float property, or the default if the property
+ * has not been defined.
+ *
+ * @param property The name of the property to return
+ * @param def The default value to return if the property is not set
+ * @return the value of the property
+ * @throws TypeException if the property is set and is not a float
+ */
+ public float getFloat(String property, float def) {
+ Object value = super.get(property);
+ if (value == null) {
+ return def;
+ }
+ if (value instanceof Float) {
+ return ((Float)value).floatValue();
+ }
+ throw new TypeException(property, value, "float");
+ }
+
+ /**
+ * Returns the value of a double property, or the default if the property
+ * has not been defined.
+ *
+ * @param property The name of the property to return
+ * @param def The default value to return if the property is not set
+ * @return the value of the property
+ * @throws TypeException if the property is set and is not a double
+ */
+ public double getDouble(String property, double def) {
+ Object value = super.get(property);
+ if (value == null) {
+ return def;
+ }
+ if (value instanceof Double) {
+ return ((Double)value).doubleValue();
+ }
+ throw new TypeException(property, value, "double");
+ }
+
+ /**
+ * Returns the value of a string property, or the default if the property
+ * has not been defined.
+ *
+ * @param property The name of the property to return
+ * @param def The default value to return if the property is not set
+ * @return the value of the property
+ * @throws TypeException if the property is set and is not a string
+ */
+ public String getString(String property, String def) {
+ Object value = super.get(property);
+ if (value == null) {
+ return def;
+ }
+ if (value == NULL_STRING) {
+ return null;
+ } else if (value instanceof String) {
+ return (String)value;
+ }
+ throw new TypeException(property, value, "string");
+ }
+
+ /*
+ * Getters with implicit defaults
+ */
+
+ /**
+ * Returns the value of a boolean property, or false
+ * if the property has not been defined.
+ *
+ * @param property The name of the property to return
+ * @return the value of the property
+ * @throws TypeException if the property is set and is not a boolean
+ */
+ public boolean getBoolean(String property) {
+ return getBoolean(property, false);
+ }
+
+ /**
+ * Returns the value of a byte property, or 0
+ * if the property has not been defined.
+ *
+ * @param property The name of the property to return
+ * @return the value of the property
+ * @throws TypeException if the property is set and is not a byte
+ */
+ public byte getByte(String property) {
+ return getByte(property, (byte)0);
+ }
+
+ /**
+ * Returns the value of a short property, or 0
+ * if the property has not been defined.
+ *
+ * @param property The name of the property to return
+ * @return the value of the property
+ * @throws TypeException if the property is set and is not a short
+ */
+ public short getShort(String property) {
+ return getShort(property, (short)0);
+ }
+
+ /**
+ * Returns the value of an integer property, or 0
+ * if the property has not been defined.
+ *
+ * @param property The name of the property to return
+ * @return the value of the property
+ * @throws TypeException if the property is set and is not an integer
+ */
+ public int getInt(String property) {
+ return getInt(property, 0);
+ }
+
+ /**
+ * Returns the value of a long property, or 0
+ * if the property has not been defined.
+ *
+ * @param property The name of the property to return
+ * @return the value of the property
+ * @throws TypeException if the property is set and is not a long
+ */
+ public long getLong(String property) {
+ return getLong(property, 0L);
+ }
+
+ /**
+ * Returns the value of a float property, or 0.0
+ * if the property has not been defined.
+ *
+ * @param property The name of the property to return
+ * @return the value of the property
+ * @throws TypeException if the property is set and is not a float
+ */
+ public float getFloat(String property) {
+ return getFloat(property, 0.0f);
+ }
+
+ /**
+ * Returns the value of a double property, or 0.0
+ * if the property has not been defined.
+ *
+ * @param property The name of the property to return
+ * @return the value of the property
+ * @throws TypeException if the property is set and is not a double
+ */
+ public double getDouble(String property) {
+ return getDouble(property, 0.0);
+ }
+
+ /**
+ * Returns the value of a String property, or ""
+ * if the property has not been defined.
+ *
+ * @param property The name of the property to return
+ * @return the value of the property
+ * @throws TypeException if the property is set and is not a string
+ */
+ public String getString(String property) {
+ return getString(property, "");
+ }
+}