aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/tools/android/java/com/google/devtools/build/android/xml
diff options
context:
space:
mode:
authorGravatar Googler <noreply@google.com>2016-03-07 14:14:29 +0000
committerGravatar Damien Martin-Guillerez <dmarting@google.com>2016-03-08 03:44:29 +0000
commit3a55f01cc98068d0133349ddb5c0043b74555565 (patch)
tree2e9fa5753d66d5098922ced4baa9612820e7260d /src/tools/android/java/com/google/devtools/build/android/xml
parenta9b7f4e1bd474afd005ad6943ce84bc8ef61eb97 (diff)
Part 2 of 5: Optimize the resource merge process, XmlProcessing.
Introduces the XmlDataResource and all the associated cruft that comes with parsing Android Resource value xmls: XmlValues, XmlValue. -- MOS_MIGRATED_REVID=116540181
Diffstat (limited to 'src/tools/android/java/com/google/devtools/build/android/xml')
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/xml/ArrayXmlResourceValue.java133
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/xml/AttrXmlResourceValue.java495
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/xml/IdXmlResourceValue.java49
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/xml/PluralXmlResourceValue.java74
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/xml/SimpleXmlResourceValue.java136
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/xml/StyleXmlResourceValue.java81
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/xml/StyleableXmlResourceValue.java89
7 files changed, 1057 insertions, 0 deletions
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/ArrayXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/ArrayXmlResourceValue.java
new file mode 100644
index 0000000000..752e803c3e
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/ArrayXmlResourceValue.java
@@ -0,0 +1,133 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// 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.google.devtools.build.android.xml;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.MoreObjects;
+import com.google.devtools.build.android.FullyQualifiedName;
+import com.google.devtools.build.android.XmlResourceValue;
+import com.google.devtools.build.android.XmlResourceValues;
+
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+
+/**
+ * Represents an Android resource array.
+ *
+ * There are two flavors of Android Resource arrays:
+ * <ul>
+ * <li>Typed arrays (http://developer.android.com/guide/topics/resources/more-resources
+ * .html#TypedArray) which which are indicated by a &lt;array&gt; tag.</li>
+ * <li>Integer array (http://developer.android.com/guide/topics/resources/more-resources
+ * .html#IntegerArray) which are indicated by &lt;integer-array&gt; tag.</li>
+ * </ul>
+ *
+ * Both of these are accessed by R.array.&lt;name&gt; in java.
+ */
+public class ArrayXmlResourceValue implements XmlResourceValue {
+ private static final QName TAG_INTEGER_ARRAY = QName.valueOf("integer-array");
+ private static final QName TAG_ARRAY = QName.valueOf("array");
+
+ /**
+ * Enumerates the different types of array tags.
+ */
+ public enum ArrayType {
+ INTEGER_ARRAY(TAG_INTEGER_ARRAY),
+ ARRAY(TAG_ARRAY);
+
+ public QName tagName;
+
+ ArrayType(QName tagName) {
+ this.tagName = tagName;
+ }
+
+ public static ArrayType fromTagName(StartElement start) {
+ final QName tagQName = start.getName();
+ for (ArrayType arrayType : values()) {
+ if (arrayType.tagName.equals(tagQName)) {
+ return arrayType;
+ }
+ }
+ throw new IllegalArgumentException(
+ String.format("%s not found in %s", tagQName, Arrays.toString(values())));
+ }
+ }
+
+ private final List<String> values;
+ private ArrayType arrayType;
+
+ private ArrayXmlResourceValue(ArrayType arrayType, List<String> values) {
+ this.arrayType = arrayType;
+ this.values = values;
+ }
+
+ @VisibleForTesting
+ public static XmlResourceValue of(ArrayType arrayType, String... values) {
+ return of(arrayType, Arrays.asList(values));
+ }
+
+ public static XmlResourceValue of(ArrayType arrayType, List<String> values) {
+ return new ArrayXmlResourceValue(arrayType, values);
+ }
+
+ @Override
+ public void write(Writer buffer, FullyQualifiedName name) {
+ // TODO(corysmith): Implement writing xml.
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(arrayType, values);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ArrayXmlResourceValue)) {
+ return false;
+ }
+ ArrayXmlResourceValue other = (ArrayXmlResourceValue) obj;
+ return Objects.equals(arrayType, other.arrayType) && Objects.equals(values, other.values);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("arrayType", arrayType)
+ .add("values", values)
+ .toString();
+ }
+
+ public static XmlResourceValue parseArray(XMLEventReader eventReader, StartElement start)
+ throws XMLStreamException {
+ List<String> values = new ArrayList<>();
+ for (XMLEvent element = eventReader.nextTag();
+ !XmlResourceValues.isEndTag(element, start.getName());
+ element = eventReader.nextTag()) {
+ if (XmlResourceValues.isItem(element)) {
+ values.add(eventReader.getElementText());
+ }
+ }
+ return of(ArrayType.fromTagName(start), values);
+ }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/AttrXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/AttrXmlResourceValue.java
new file mode 100644
index 0000000000..52f44dbc6e
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/AttrXmlResourceValue.java
@@ -0,0 +1,495 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// 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.google.devtools.build.android.xml;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMap.Builder;
+import com.google.devtools.build.android.FullyQualifiedName;
+import com.google.devtools.build.android.XmlResourceValue;
+import com.google.devtools.build.android.XmlResourceValues;
+
+import java.io.Writer;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+
+/**
+ * Represents an Android Resource custom attribute.
+ *
+ * <p>Attribute are the most complicated Android resource, and therefore the least documented. Most
+ * of the information about them is found by reading the android compatibility library source. An
+ * Attribute defines a parameter that can be passed into a view class -- as such you can think of
+ * attributes as creating slots for other resources to fit into. Each slot will have at least one
+ * format, and can have multiples. Simple attributes (color, boolean, reference, dimension,
+ * float, integer, string, and fraction) are defined as &lt;attr name="<em>name</em>"
+ * format="<em>format</em>" /&gt; while the complex ones, flag and enum, have sub tags:
+ * &lt;attr name="<em>name</em>" &gt&lt;flag name="<em>name</em>" value="<em>value</em>"&gt;
+ * &lt;/attr&gt;.
+ *
+ * <p>Attributes also have a double duty as defining validation logic for layout resources --
+ * each layout attribute *must* have a corresponding attribute which will be used to validate the
+ * value/resource reference defined in it.
+ *
+ * <p>AttrXmlValue, due to the mutiple types of attributes is actually a composite class that
+ * contains multiple XmlValue instances for each resource.
+ */
+public class AttrXmlResourceValue implements XmlResourceValue {
+
+ private static final String FRACTION = "fraction";
+ private static final String STRING = "string";
+ private static final String INTEGER = "integer";
+ private static final String FLOAT = "float";
+ private static final String DIMENSION = "dimension";
+ private static final String BOOLEAN = "boolean";
+ private static final String COLOR = "color";
+ private static final String REFERENCE = "reference";
+ private static final String ENUM = "enum";
+ private static final String FLAG = "flag";
+ private static final QName TAG_ENUM = QName.valueOf(ENUM);
+ private static final QName TAG_FLAG = QName.valueOf(FLAG);
+ private final Map<String, XmlResourceValue> formats;
+
+ private AttrXmlResourceValue(Map<String, XmlResourceValue> formats) {
+ this.formats = formats;
+ }
+
+ private static Map<String, String> readSubValues(XMLEventReader reader, QName subTagType)
+ throws XMLStreamException {
+ Map<String, String> enumValue = new HashMap<>();
+ while (reader.hasNext()
+ && XmlResourceValues.isTag(XmlResourceValues.peekNextTag(reader), subTagType)) {
+ StartElement element = reader.nextEvent().asStartElement();
+ enumValue.put(
+ XmlResourceValues.getElementName(element), XmlResourceValues.getElementValue(element));
+ XMLEvent endTag = reader.nextEvent();
+ if (!XmlResourceValues.isEndTag(endTag, subTagType)) {
+ throw new XMLStreamException(
+ String.format("Unexpected [%s]; Expected %s", endTag, "</enum>"), endTag.getLocation());
+ }
+ }
+ return enumValue;
+ }
+
+ private static void endAttrElement(XMLEventReader reader) throws XMLStreamException {
+ XMLEvent endTag = reader.nextTag();
+ if (!endTag.isEndElement() || !QName.valueOf("attr").equals(endTag.asEndElement().getName())) {
+ throw new XMLStreamException("Unexpected Tag:" + endTag, endTag.getLocation());
+ }
+ }
+
+ @VisibleForTesting
+ private static final class BuilderEntry implements Map.Entry<String, XmlResourceValue> {
+ private final String name;
+ private final XmlResourceValue value;
+
+ public BuilderEntry(String name, XmlResourceValue value) {
+ this.name = name;
+ this.value = value;
+ }
+
+ @Override
+ public String getKey() {
+ return name;
+ }
+
+ @Override
+ public XmlResourceValue getValue() {
+ return value;
+ }
+
+ @Override
+ public XmlResourceValue setValue(XmlResourceValue value) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @SafeVarargs
+ @VisibleForTesting
+ public static XmlResourceValue fromFormatEntries(Map.Entry<String, XmlResourceValue>... entries) {
+ return of(ImmutableMap.copyOf(Arrays.asList(entries)));
+ }
+
+ public static XmlResourceValue from(
+ StartElement attr, @Nullable String format, XMLEventReader eventReader)
+ throws XMLStreamException {
+ Set<String> formatNames = new HashSet<>();
+ if (format != null) {
+ Collections.addAll(formatNames, format.split("\\|"));
+ }
+ XMLEvent nextTag = XmlResourceValues.peekNextTag(eventReader);
+ if (nextTag != null && nextTag.isStartElement()) {
+ formatNames.add(nextTag.asStartElement().getName().getLocalPart().toLowerCase());
+ }
+
+ Map<String, XmlResourceValue> formats = new HashMap<>();
+ for (String formatName : formatNames) {
+ switch (formatName) {
+ case FLAG:
+ Map<String, String> flags = readSubValues(eventReader, TAG_FLAG);
+ endAttrElement(eventReader);
+ formats.put(formatName, AttrFlagXmlValue.of(flags));
+ break;
+ case ENUM:
+ Map<String, String> enums = readSubValues(eventReader, TAG_ENUM);
+ endAttrElement(eventReader);
+ formats.put(formatName, AttrEnumXmlValue.of(enums));
+ break;
+ case REFERENCE:
+ formats.put(formatName, AttrReferenceXmlValue.of());
+ break;
+ case COLOR:
+ formats.put(formatName, AttrColorXmlValue.of());
+ break;
+ case BOOLEAN:
+ formats.put(formatName, AttrBooleanXmlValue.of());
+ break;
+ case DIMENSION:
+ formats.put(formatName, AttrDimensionXmlValue.of());
+ break;
+ case FLOAT:
+ formats.put(formatName, AttrFloatXmlValue.of());
+ break;
+ case INTEGER:
+ formats.put(formatName, AttrIntegerXmlValue.of());
+ break;
+ case STRING:
+ formats.put(formatName, AttrStringXmlValue.of());
+ break;
+ case FRACTION:
+ formats.put(formatName, AttrFractionXmlValue.of());
+ break;
+ default:
+ throw new XMLStreamException(
+ String.format("Unexpected attr format: %S", formatName), attr.getLocation());
+ }
+ }
+ return of(formats);
+ }
+
+ public static XmlResourceValue of(Map<String, XmlResourceValue> formats) {
+ return new AttrXmlResourceValue(formats);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ AttrXmlResourceValue other = (AttrXmlResourceValue) o;
+ return Objects.equals(formats, other.formats);
+ }
+
+ @Override
+ public int hashCode() {
+ return formats.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this).add("formats", formats).toString();
+ }
+
+ @Override
+ public void write(Writer buffer, FullyQualifiedName name) {
+ // TODO(corysmith): Implement write.
+ throw new UnsupportedOperationException();
+ }
+
+ /** Represents an Android Enum Attribute resource. */
+ @VisibleForTesting
+ public static class AttrEnumXmlValue implements XmlResourceValue {
+
+ private Map<String, String> values;
+
+ private AttrEnumXmlValue(Map<String, String> values) {
+ this.values = values;
+ }
+
+ @VisibleForTesting
+ public static Map.Entry<String, XmlResourceValue> asEntryOf(String... keyThenValue) {
+ Preconditions.checkArgument(keyThenValue.length > 0);
+ Preconditions.checkArgument(keyThenValue.length % 2 == 0);
+ Builder<String, String> builder = ImmutableMap.<String, String>builder();
+ for (int i = 0; i < keyThenValue.length; i += 2) {
+ builder.put(keyThenValue[i], keyThenValue[i + 1]);
+ }
+ return new BuilderEntry(ENUM, of(builder.build()));
+ }
+
+ public static XmlResourceValue of(Map<String, String> values) {
+ return new AttrEnumXmlValue(values);
+ }
+
+ @Override
+ public void write(Writer buffer, FullyQualifiedName name) {
+ // TODO(corysmith): Implement write.
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int hashCode() {
+ return values.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof AttrEnumXmlValue)) {
+ return false;
+ }
+ AttrEnumXmlValue other = (AttrEnumXmlValue) obj;
+ return Objects.equals(values, other.values);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass()).add("values", values).toString();
+ }
+ }
+
+ /** Represents an Android Flag Attribute resource. */
+ @VisibleForTesting
+ public static class AttrFlagXmlValue implements XmlResourceValue {
+
+ private Map<String, String> values;
+
+ private AttrFlagXmlValue(Map<String, String> values) {
+ this.values = values;
+ }
+
+ public static XmlResourceValue of(Map<String, String> values) {
+ return new AttrFlagXmlValue(values);
+ }
+
+ @VisibleForTesting
+ public static Map.Entry<String, XmlResourceValue> asEntryOf(String... keyThenValue) {
+ Builder<String, String> builder = ImmutableMap.<String, String>builder();
+ Preconditions.checkArgument(keyThenValue.length > 0);
+ Preconditions.checkArgument(keyThenValue.length % 2 == 0);
+ for (int i = 0; i < keyThenValue.length; i += 2) {
+ builder.put(keyThenValue[i], keyThenValue[i + 1]);
+ }
+ return new BuilderEntry(FLAG, of(builder.build()));
+ }
+
+ @Override
+ public void write(Writer buffer, FullyQualifiedName name) {
+ // TODO(corysmith): Implement write.
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int hashCode() {
+ return values.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof AttrFlagXmlValue)) {
+ return false;
+ }
+ AttrFlagXmlValue other = (AttrFlagXmlValue) obj;
+ return Objects.equals(values, other.values);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass()).add("values", values).toString();
+ }
+ }
+
+ /** Represents an Android Reference Attribute resource. */
+ @VisibleForTesting
+ public static class AttrReferenceXmlValue implements XmlResourceValue {
+ private static final AttrReferenceXmlValue INSTANCE = new AttrReferenceXmlValue();
+
+ public static XmlResourceValue of() {
+ return INSTANCE;
+ }
+
+ @VisibleForTesting
+ public static BuilderEntry asEntry() {
+ return new BuilderEntry(REFERENCE, of());
+ }
+
+ @Override
+ public void write(Writer buffer, FullyQualifiedName name) {
+ // TODO(corysmith): Implement write.
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /** Represents an Android Color Attribute resource. */
+ @VisibleForTesting
+ public static class AttrColorXmlValue implements XmlResourceValue {
+ private static final AttrColorXmlValue INSTANCE = new AttrColorXmlValue();
+
+ public static XmlResourceValue of() {
+ return INSTANCE;
+ }
+
+ @VisibleForTesting
+ public static BuilderEntry asEntry() {
+ return new BuilderEntry(COLOR, of());
+ }
+
+ @Override
+ public void write(Writer buffer, FullyQualifiedName name) {
+ // TODO(corysmith): Implement write.
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /** Represents an Android Boolean Attribute resource. */
+ @VisibleForTesting
+ public static class AttrBooleanXmlValue implements XmlResourceValue {
+ private static final AttrBooleanXmlValue INSTANCE = new AttrBooleanXmlValue();
+
+ public static XmlResourceValue of() {
+ return INSTANCE;
+ }
+
+ @VisibleForTesting
+ public static BuilderEntry asEntry() {
+ return new BuilderEntry(BOOLEAN, of());
+ }
+
+ @Override
+ public void write(Writer buffer, FullyQualifiedName name) {
+ // TODO(corysmith): Implement write.
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /** Represents an Android Float Attribute resource. */
+ @VisibleForTesting
+ public static class AttrFloatXmlValue implements XmlResourceValue {
+ private static final AttrFloatXmlValue INSTANCE = new AttrFloatXmlValue();
+
+ public static XmlResourceValue of() {
+ return INSTANCE;
+ }
+
+ @VisibleForTesting
+ public static BuilderEntry asEntry() {
+ return new BuilderEntry(FLOAT, of());
+ }
+
+ @Override
+ public void write(Writer buffer, FullyQualifiedName name) {
+ // TODO(corysmith): Implement write.
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /** Represents an Android Dimension Attribute resource. */
+ @VisibleForTesting
+ public static class AttrDimensionXmlValue implements XmlResourceValue {
+ private static final AttrDimensionXmlValue INSTANCE = new AttrDimensionXmlValue();
+
+ @VisibleForTesting
+ public static BuilderEntry asEntry() {
+ return new BuilderEntry(DIMENSION, of());
+ }
+
+ public static XmlResourceValue of() {
+ return INSTANCE;
+ }
+
+ @Override
+ public void write(Writer buffer, FullyQualifiedName name) {
+ // TODO(corysmith): Implement write.
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /** Represents an Android Integer Attribute resource. */
+ @VisibleForTesting
+ public static class AttrIntegerXmlValue implements XmlResourceValue {
+ private static final AttrIntegerXmlValue INSTANCE = new AttrIntegerXmlValue();
+
+ public static XmlResourceValue of() {
+ return INSTANCE;
+ }
+
+ @VisibleForTesting
+ public static BuilderEntry asEntry() {
+ return new BuilderEntry(INTEGER, of());
+ }
+
+ @Override
+ public void write(Writer buffer, FullyQualifiedName name) {
+ // TODO(corysmith): Implement write.
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /** Represents an Android String Attribute resource. */
+ @VisibleForTesting
+ public static class AttrStringXmlValue implements XmlResourceValue {
+ private static final AttrStringXmlValue INSTANCE = new AttrStringXmlValue();
+
+ public static XmlResourceValue of() {
+ return INSTANCE;
+ }
+
+ @VisibleForTesting
+ public static BuilderEntry asEntry() {
+ return new BuilderEntry(STRING, of());
+ }
+
+ @Override
+ public void write(Writer buffer, FullyQualifiedName name) {
+ // TODO(corysmith): Implement write.
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /** Represents an Android Fraction Attribute resource. */
+ @VisibleForTesting
+ public static class AttrFractionXmlValue implements XmlResourceValue {
+ private static final AttrFractionXmlValue INSTANCE = new AttrFractionXmlValue();
+
+ public static XmlResourceValue of() {
+ return INSTANCE;
+ }
+
+ @VisibleForTesting
+ public static BuilderEntry asEntry() {
+ return new BuilderEntry(FRACTION, of());
+ }
+
+ @Override
+ public void write(Writer buffer, FullyQualifiedName name) {
+ // TODO(corysmith): Implement write.
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/IdXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/IdXmlResourceValue.java
new file mode 100644
index 0000000000..f8221ed332
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/IdXmlResourceValue.java
@@ -0,0 +1,49 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// 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.google.devtools.build.android.xml;
+
+import com.google.common.base.MoreObjects;
+import com.google.devtools.build.android.FullyQualifiedName;
+import com.google.devtools.build.android.XmlResourceValue;
+
+import java.io.Writer;
+
+/**
+ * Represents an Android Resource id.
+ *
+ * <p>Ids (http://developer.android.com/guide/topics/resources/more-resources.html#Id) are
+ * special -- unlike other resources they cannot be overridden. This is due to the
+ * nature of their usage. Each id corresponds to context sensitive resource of component, meaning
+ * that they have no intrinsic defined value. They exist to reference parts of other resources.
+ * Ids can also be declared on the fly in components with the syntax @[+][package:]id/resource_name.
+ */
+public class IdXmlResourceValue implements XmlResourceValue {
+
+ static final IdXmlResourceValue SINGLETON = new IdXmlResourceValue();
+
+ public static XmlResourceValue of() {
+ return SINGLETON;
+ }
+
+ @Override
+ public void write(Writer buffer, FullyQualifiedName name) {
+ // TODO(corysmith): Implement write.
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass()).toString();
+ }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/PluralXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/PluralXmlResourceValue.java
new file mode 100644
index 0000000000..f9db759bd1
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/PluralXmlResourceValue.java
@@ -0,0 +1,74 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// 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.google.devtools.build.android.xml;
+
+import com.google.common.base.MoreObjects;
+import com.google.devtools.build.android.FullyQualifiedName;
+import com.google.devtools.build.android.XmlResourceValue;
+
+import java.io.Writer;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Represents an Android Plural Resource.
+ *
+ * <p>Plurals are a localization construct (http://developer.android
+ * .com/guide/topics/resources/string-resource.html#Plurals) that are basically a map of key to
+ * value. They are defined in xml as:
+ * <code>
+ * &lt;plurals name="plural_name"&gt;
+ * &lt;item quantity=["zero" | "one" | "two" | "few" | "many" | "other"]&gt;
+ * text_string
+ * &lt;/item&gt;
+ * &lt;/plurals&gt;
+ * </code>
+ */
+public class PluralXmlResourceValue implements XmlResourceValue {
+
+ private Map<String, String> values;
+
+ private PluralXmlResourceValue(Map<String, String> values) {
+ this.values = values;
+ }
+
+ public static XmlResourceValue of(Map<String, String> values) {
+ return new PluralXmlResourceValue(values);
+ }
+
+ @Override
+ public void write(Writer buffer, FullyQualifiedName name) {
+ // TODO(corysmith): Implement write.
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int hashCode() {
+ return values.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof PluralXmlResourceValue)) {
+ return false;
+ }
+ PluralXmlResourceValue other = (PluralXmlResourceValue) obj;
+ return Objects.equals(values, other.values);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass()).add("values", values).toString();
+ }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/SimpleXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/SimpleXmlResourceValue.java
new file mode 100644
index 0000000000..333fb088a2
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/SimpleXmlResourceValue.java
@@ -0,0 +1,136 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// 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.google.devtools.build.android.xml;
+
+import com.google.common.base.MoreObjects;
+import com.google.devtools.build.android.FullyQualifiedName;
+import com.google.devtools.build.android.XmlResourceValue;
+
+import com.android.resources.ResourceType;
+
+import java.io.Writer;
+import java.util.Arrays;
+import java.util.Objects;
+
+import javax.xml.namespace.QName;
+
+/**
+ * Represents a simple Android resource xml value.
+ *
+ * <p>There is a class of resources that are simple name/value pairs: string(http://developer
+ * .android.com/guide/topics/resources/string-resource.html), bool
+ * (http://developer.android.com/guide/topics/resources/more-resources.html#Bool), color
+ * (http://developer.android.com/guide/topics/resources/more-resources.html#Color), and
+ * dimen (http://developer.android.com/guide/topics/resources/more-resources.html#Dimension).
+ * These are defined in xml as &lt;<em>resource type</em> name="<em>name</em>"
+ * value="<em>value</em>"&gt;. In the interest of keeping the parsing svelte, these are
+ * represented by a single class.
+ */
+public class SimpleXmlResourceValue implements XmlResourceValue {
+ static final QName TAG_STRING = QName.valueOf("string");
+ static final QName TAG_BOOL = QName.valueOf("bool");
+ static final QName TAG_COLOR = QName.valueOf("color");
+ static final QName TAG_DIMEN = QName.valueOf("dimen");
+
+ /** Provides an enumeration resource type and simple value validation. */
+ public enum Type {
+ STRING(TAG_STRING) {
+ @Override
+ public boolean validate(String value) {
+ return true;
+ }
+ },
+ BOOL(TAG_BOOL) {
+ @Override
+ public boolean validate(String value) {
+ final String cleanValue = value.toLowerCase().trim();
+ return "true".equals(cleanValue) || "false".equals(cleanValue);
+ }
+ },
+ COLOR(TAG_COLOR) {
+ @Override
+ public boolean validate(String value) {
+ // TODO(corysmith): Validate the hex color.
+ return true;
+ }
+ },
+ DIMEN(TAG_DIMEN) {
+ @Override
+ public boolean validate(String value) {
+ // TODO(corysmith): Validate the dimension type.
+ return true;
+ }
+ };
+ private QName tagName;
+
+ Type(QName tagName) {
+ this.tagName = tagName;
+ }
+
+ abstract boolean validate(String value);
+
+ public static Type from(ResourceType resourceType) {
+ for (Type valueType : values()) {
+ if (valueType.tagName.getLocalPart().equals(resourceType.getName())) {
+ return valueType;
+ }
+ }
+ throw new IllegalArgumentException(
+ String.format(
+ "%s resource type not found in available types: %s",
+ resourceType,
+ Arrays.toString(values())));
+ }
+ }
+
+ private String value;
+ private Type valueType;
+
+ public static XmlResourceValue of(Type valueType, String value) {
+ return new SimpleXmlResourceValue(valueType, value);
+ }
+
+ private SimpleXmlResourceValue(Type valueType, String value) {
+ this.valueType = valueType;
+ this.value = value;
+ }
+
+ @Override
+ public void write(Writer buffer, FullyQualifiedName name) {
+ // TODO(corysmith): Implement write.
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(valueType, value);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof SimpleXmlResourceValue)) {
+ return false;
+ }
+ SimpleXmlResourceValue other = (SimpleXmlResourceValue) obj;
+ return Objects.equals(valueType, other.valueType) && Objects.equals(value, other.value);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("valueType", valueType)
+ .add("value", value)
+ .toString();
+ }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/StyleXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/StyleXmlResourceValue.java
new file mode 100644
index 0000000000..3055089ea0
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/StyleXmlResourceValue.java
@@ -0,0 +1,81 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// 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.google.devtools.build.android.xml;
+
+import com.google.common.base.MoreObjects;
+import com.google.devtools.build.android.FullyQualifiedName;
+import com.google.devtools.build.android.XmlResourceValue;
+
+import java.io.Writer;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Represents an Android Style Resource.
+ *
+ * <p>Styles (http://developer.android.com/guide/topics/resources/style-resource.html) define a
+ * look and feel for a layout or other ui construct. They are effectively a s set of values that
+ * correspond to &lt;attr&gt; resources defined either in the base android framework or in other
+ * resources. They also allow inheritance on other styles. For a style to valid in a given resource
+ * pass, they must only contain definer attributes with acceptable values.
+ * <code>
+ * &lt;resources&gt;
+ * &lt;style name="CustomText" parent="@style/Text"&gt;
+ * &lt;item name="android:textSize"&gt;20sp&lt;/item&gt;
+ * &lt;item name="android:textColor"&gt;#008&lt;/item&gt;
+ * &lt;/style&gt;
+ * &lt;/resources&gt;
+ * </code>
+ */
+public class StyleXmlResourceValue implements XmlResourceValue {
+ private String parent;
+ private Map<String, String> values;
+
+ public static StyleXmlResourceValue of(String parent, Map<String, String> values) {
+ return new StyleXmlResourceValue(parent, values);
+ }
+
+ private StyleXmlResourceValue(String parent, Map<String, String> values) {
+ this.parent = parent;
+ this.values = values;
+ }
+
+ @Override
+ public void write(Writer buffer, FullyQualifiedName name) {
+ // TODO(corysmith): Implement write.
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(parent, values);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof StyleXmlResourceValue)) {
+ return false;
+ }
+ StyleXmlResourceValue other = (StyleXmlResourceValue) obj;
+ return Objects.equals(parent, other.parent) && Objects.equals(values, other.values);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("parent", parent)
+ .add("values", values)
+ .toString();
+ }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/StyleableXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/StyleableXmlResourceValue.java
new file mode 100644
index 0000000000..f1a0486307
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/StyleableXmlResourceValue.java
@@ -0,0 +1,89 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// 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.google.devtools.build.android.xml;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.MoreObjects;
+import com.google.devtools.build.android.FullyQualifiedName;
+import com.google.devtools.build.android.XmlResourceValue;
+
+import java.io.Writer;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Represent an Android styleable resource.
+ *
+ * <p>Styleable resources are groups of attributes that can be applied to views. They are, for the
+ * most part, vaguely documented (http://developer.android.com/training/custom-views/create-view
+ * .html#customattr). It's worth noting that attributes declared inside a
+ * &lt;declare-styleable&gt; tags, for example;
+ * <code>
+ * <declare-styleable name="PieChart">
+ * <attr name="showText" format="boolean" />
+ * </declare-styleable>
+ * </code>
+ *
+ * Can also be seen as:
+ * <code>
+ * <attr name="showText" format="boolean" />
+ * <declare-styleable name="PieChart">
+ * <attr name="showText"/>
+ * </declare-styleable>
+ * </code>
+ *
+ * <p>The StyleableXmlValue only contains names of the attributes it holds, not definitions.
+ */
+public class StyleableXmlResourceValue implements XmlResourceValue {
+ private final List<String> attrs;
+
+ private StyleableXmlResourceValue(List<String> attrs) {
+ this.attrs = attrs;
+ }
+
+ public static XmlResourceValue of(List<String> attrs) {
+ return new StyleableXmlResourceValue(attrs);
+ }
+
+ @VisibleForTesting
+ public static XmlResourceValue of(String... attrs) {
+ return new StyleableXmlResourceValue(Arrays.asList(attrs));
+ }
+
+ @Override
+ public void write(Writer buffer, FullyQualifiedName name) {
+ // TODO(corysmith): Implement write.
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int hashCode() {
+ return attrs.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof StyleableXmlResourceValue)) {
+ return false;
+ }
+ StyleableXmlResourceValue other = (StyleableXmlResourceValue) obj;
+ return Objects.equals(attrs, other.attrs);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass()).add("attrs", attrs).toString();
+ }
+}