| // 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.android.resources.ResourceType; |
| import com.google.common.base.MoreObjects; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.devtools.build.android.AndroidDataWritingVisitor; |
| import com.google.devtools.build.android.AndroidDataWritingVisitor.StartTag; |
| import com.google.devtools.build.android.AndroidResourceSymbolSink; |
| import com.google.devtools.build.android.DataSource; |
| import com.google.devtools.build.android.FullyQualifiedName; |
| import com.google.devtools.build.android.XmlResourceValue; |
| import com.google.devtools.build.android.XmlResourceValues; |
| import com.google.devtools.build.android.proto.SerializeFormat; |
| import com.google.devtools.build.android.proto.SerializeFormat.DataValueXml.Builder; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.util.Arrays; |
| import java.util.Objects; |
| import javax.annotation.Nullable; |
| import javax.annotation.concurrent.Immutable; |
| 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 <<em>resource type</em> name="<em>name</em>" value="<em>value</em>">. In |
| * the interest of keeping the parsing svelte, these are represented by a single class. |
| */ |
| @Immutable |
| public class SimpleXmlResourceValue implements XmlResourceValue { |
| static final QName TAG_BOOL = QName.valueOf("bool"); |
| static final QName TAG_COLOR = QName.valueOf("color"); |
| static final QName TAG_DIMEN = QName.valueOf("dimen"); |
| static final QName TAG_DRAWABLE = QName.valueOf("drawable"); |
| static final QName TAG_FRACTION = QName.valueOf("fraction"); |
| static final QName TAG_INTEGER = QName.valueOf("integer"); |
| static final QName TAG_ITEM = QName.valueOf("item"); |
| static final QName TAG_PUBLIC = QName.valueOf("public"); |
| static final QName TAG_STRING = QName.valueOf("string"); |
| |
| /** Provides an enumeration resource type and simple value validation. */ |
| public enum Type { |
| 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; |
| } |
| }, |
| DRAWABLE(TAG_DRAWABLE) { |
| @Override |
| public boolean validate(String value) { |
| // TODO(corysmith): Validate the drawable type. |
| return true; |
| } |
| }, |
| FRACTION(TAG_FRACTION) { |
| @Override |
| public boolean validate(String value) { |
| // TODO(corysmith): Validate the fraction type. |
| return true; |
| } |
| }, |
| INTEGER(TAG_INTEGER) { |
| @Override |
| public boolean validate(String value) { |
| // TODO(corysmith): Validate the integer type. |
| return true; |
| } |
| }, |
| ITEM(TAG_ITEM) { |
| @Override |
| public boolean validate(String value) { |
| // TODO(corysmith): Validate the item type. |
| return true; |
| } |
| }, |
| PUBLIC(TAG_PUBLIC) { |
| @Override |
| public boolean validate(String value) { |
| // TODO(corysmith): Validate the public type. |
| return true; |
| } |
| }, |
| STRING(TAG_STRING) { |
| @Override |
| public boolean validate(String value) { |
| 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; |
| } else if (resourceType.getName().equalsIgnoreCase(valueType.name())) { |
| return valueType; |
| } |
| } |
| throw new IllegalArgumentException( |
| String.format( |
| "%s resource type not found in available types: %s", |
| resourceType, |
| Arrays.toString(values()))); |
| } |
| } |
| |
| private final ImmutableMap<String, String> attributes; |
| @Nullable private final String value; |
| private final Type valueType; |
| |
| public static XmlResourceValue createWithValue(Type valueType, String value) { |
| return of(valueType, ImmutableMap.<String, String>of(), value); |
| } |
| |
| public static XmlResourceValue withAttributes( |
| Type valueType, ImmutableMap<String, String> attributes) { |
| return of(valueType, attributes, null); |
| } |
| |
| public static XmlResourceValue itemWithFormattedValue( |
| ResourceType resourceType, String format, String value) { |
| return of(Type.ITEM, ImmutableMap.of("type", resourceType.getName(), "format", format), value); |
| } |
| |
| public static XmlResourceValue itemWithValue( |
| ResourceType resourceType, String value) { |
| return of(Type.ITEM, ImmutableMap.of("type", resourceType.getName()), value); |
| } |
| |
| public static XmlResourceValue itemPlaceHolderFor(ResourceType resourceType) { |
| return withAttributes(Type.ITEM, ImmutableMap.of("type", resourceType.getName())); |
| } |
| |
| public static XmlResourceValue of( |
| Type valueType, ImmutableMap<String, String> attributes, @Nullable String value) { |
| return new SimpleXmlResourceValue(valueType, attributes, value); |
| } |
| |
| private SimpleXmlResourceValue( |
| Type valueType, ImmutableMap<String, String> attributes, String value) { |
| this.valueType = valueType; |
| this.value = value; |
| this.attributes = attributes; |
| } |
| |
| @Override |
| public void write( |
| FullyQualifiedName key, DataSource source, AndroidDataWritingVisitor mergedDataWriter) { |
| |
| StartTag startTag = |
| mergedDataWriter |
| .define(key) |
| .derivedFrom(source) |
| .startTag(valueType.tagName) |
| .named(key) |
| .addAttributesFrom(attributes.entrySet()); |
| |
| if (value != null) { |
| startTag.closeTag().addCharactersOf(value).endTag().save(); |
| } else { |
| startTag.closeUnaryTag().save(); |
| } |
| } |
| |
| @SuppressWarnings("deprecation") |
| public static XmlResourceValue from(SerializeFormat.DataValueXml proto) { |
| return of( |
| Type.valueOf(proto.getValueType()), |
| ImmutableMap.copyOf(proto.getAttribute()), |
| proto.hasValue() ? proto.getValue() : null); |
| } |
| |
| @Override |
| public void writeResourceToClass(FullyQualifiedName key, AndroidResourceSymbolSink sink) { |
| sink.acceptSimpleResource(key.type(), key.name()); |
| } |
| |
| @Override |
| public int serializeTo(int sourceId, Namespaces namespaces, OutputStream output) |
| throws IOException { |
| SerializeFormat.DataValue.Builder builder = |
| XmlResourceValues.newSerializableDataValueBuilder(sourceId); |
| Builder xmlValueBuilder = |
| builder |
| .getXmlValueBuilder() |
| .putAllNamespace(namespaces.asMap()) |
| .setType(SerializeFormat.DataValueXml.XmlType.SIMPLE) |
| // TODO(corysmith): Find a way to avoid writing strings to the serialized format |
| // it's inefficient use of space and costs more when deserializing. |
| .putAllAttribute(attributes); |
| if (value != null) { |
| xmlValueBuilder.setValue(value); |
| } |
| builder.setXmlValue(xmlValueBuilder.setValueType(valueType.name())); |
| return XmlResourceValues.serializeProtoDataValue(output, builder); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(valueType, attributes, 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(attributes, other.attributes) |
| && Objects.equals(value, other.value); |
| } |
| |
| @Override |
| public String toString() { |
| return MoreObjects.toStringHelper(getClass()) |
| .add("valueType", valueType) |
| .add("attributes", attributes) |
| .add("value", value) |
| .toString(); |
| } |
| |
| @Override |
| public XmlResourceValue combineWith(XmlResourceValue value) { |
| throw new IllegalArgumentException(this + " is not a combinable resource."); |
| } |
| |
| @Override |
| public String asConflictStringWith(DataSource source) { |
| if (value != null) { |
| return String.format(" %s (with value %s)", source.asConflictString(), value); |
| } |
| return source.asConflictString(); |
| } |
| } |