blob: 3f37fcf36c3fe1541a73686ec181941692fee9f0 [file] [log] [blame]
// 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 static com.google.common.base.Predicates.equalTo;
import static com.google.common.base.Predicates.not;
import com.android.aapt.Resources.Attribute;
import com.android.aapt.Resources.Attribute.Symbol;
import com.android.aapt.Resources.Value;
import com.android.resources.ResourceType;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Ordering;
import com.google.devtools.build.android.AndroidDataWritingVisitor;
import com.google.devtools.build.android.AndroidDataWritingVisitor.StartTag;
import com.google.devtools.build.android.AndroidDataWritingVisitor.ValuesResourceDefinition;
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.protobuf.InvalidProtocolBufferException;
import java.io.IOException;
import java.io.OutputStream;
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.CheckReturnValue;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
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 parentTags: &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 multiple types of attributes is actually a composite class that
* contains multiple {@link XmlResourceValue} instances for each resource.
*/
@Immutable
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 FLAGS = "flags";
private static final QName TAG_ENUM = QName.valueOf(ENUM);
private static final QName TAG_FLAG = QName.valueOf("flag");
private final ImmutableMap<String, ResourceXmlAttrValue> formats;
private final boolean weak;
private AttrXmlResourceValue(ImmutableMap<String, ResourceXmlAttrValue> formats, boolean weak) {
this.formats = formats;
this.weak = weak;
}
private static Map<String, String> readSubValues(XMLEventReader reader, QName subTagType)
throws XMLStreamException {
ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
while (reader.hasNext()
&& XmlResourceValues.isTag(XmlResourceValues.peekNextTag(reader), subTagType)) {
StartElement element = reader.nextEvent().asStartElement();
builder.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 builder.build();
}
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 ParentTag:" + endTag, endTag.getLocation());
}
}
private static final class BuilderEntry implements Map.Entry<String, ResourceXmlAttrValue> {
private final String name;
private final ResourceXmlAttrValue value;
BuilderEntry(String name, ResourceXmlAttrValue value) {
this.name = name;
this.value = value;
}
@Override
public String getKey() {
return name;
}
@Override
public ResourceXmlAttrValue getValue() {
return value;
}
@Override
public ResourceXmlAttrValue setValue(ResourceXmlAttrValue value) {
throw new UnsupportedOperationException();
}
}
@SafeVarargs
@VisibleForTesting
public static XmlResourceValue fromFormatEntries(
Map.Entry<String, ResourceXmlAttrValue>... entries) {
return of(ImmutableMap.copyOf(Arrays.asList(entries)));
}
@SafeVarargs
@VisibleForTesting
public static XmlResourceValue weakFromFormatEntries(
Map.Entry<String, ResourceXmlAttrValue>... entries) {
return of(ImmutableMap.copyOf(Arrays.asList(entries)), true);
}
@SuppressWarnings("deprecation")
public static XmlResourceValue from(SerializeFormat.DataValueXml proto)
throws InvalidProtocolBufferException {
ImmutableMap.Builder<String, ResourceXmlAttrValue> formats =
ImmutableMap.<String, AttrXmlResourceValue.ResourceXmlAttrValue>builder();
for (Map.Entry<String, SerializeFormat.DataValueXml> entry :
proto.getMappedXmlValue().entrySet()) {
switch (entry.getKey()) {
case FLAGS:
formats.put(
entry.getKey(), FlagResourceXmlAttrValue.of(entry.getValue().getMappedStringValue()));
break;
case ENUM:
formats.put(
entry.getKey(), EnumResourceXmlAttrValue.of(entry.getValue().getMappedStringValue()));
break;
case REFERENCE:
formats.put(entry.getKey(), ReferenceResourceXmlAttrValue.of());
break;
case COLOR:
formats.put(entry.getKey(), ColorResourceXmlAttrValue.of());
break;
case BOOLEAN:
formats.put(entry.getKey(), BooleanResourceXmlAttrValue.of());
break;
case DIMENSION:
formats.put(entry.getKey(), DimensionResourceXmlAttrValue.of());
break;
case FLOAT:
formats.put(entry.getKey(), FloatResourceXmlAttrValue.of());
break;
case INTEGER:
formats.put(entry.getKey(), IntegerResourceXmlAttrValue.of());
break;
case STRING:
formats.put(entry.getKey(), StringResourceXmlAttrValue.of());
break;
case FRACTION:
formats.put(entry.getKey(), FractionResourceXmlAttrValue.of());
break;
default:
throw new InvalidProtocolBufferException("Unexpected format: " + entry.getKey());
}
}
return of(formats.build());
}
public static XmlResourceValue from(Value proto) throws InvalidProtocolBufferException {
ImmutableMap.Builder<String, ResourceXmlAttrValue> formats = ImmutableMap.builder();
Attribute attribute = proto.getCompoundValue().getAttr();
int formatFlags = attribute.getFormatFlags();
if (formatFlags != 0xFFFF) {
// These flags are defined in AOSP in ResourceTypes.h:ResTable_map
if ((formatFlags & 1 << 0) != 0) {
formats.put("reference", ReferenceResourceXmlAttrValue.of());
}
if ((formatFlags & 1 << 1) != 0) {
formats.put("string", StringResourceXmlAttrValue.of());
}
if ((formatFlags & 1 << 2) != 0) {
formats.put("integer", IntegerResourceXmlAttrValue.of());
}
if ((formatFlags & 1 << 3) != 0) {
formats.put("boolean", BooleanResourceXmlAttrValue.of());
}
if ((formatFlags & 1 << 4) != 0) {
formats.put("color", ColorResourceXmlAttrValue.of());
}
if ((formatFlags & 1 << 5) != 0) {
formats.put("float", FloatResourceXmlAttrValue.of());
}
if ((formatFlags & 1 << 6) != 0) {
formats.put("dimension", DimensionResourceXmlAttrValue.of());
}
if ((formatFlags & 1 << 7) != 0) {
formats.put("fraction", FractionResourceXmlAttrValue.of());
}
if ((formatFlags & 1 << 16) != 0) {
Map<String, String> enums = new HashMap<>();
for (Symbol attrSymbol : attribute.getSymbolList()) {
String name = attrSymbol.getName().getName().replaceFirst("id/", "");
enums.put(name, Integer.toString(attrSymbol.getValue()));
}
formats.put("enum", EnumResourceXmlAttrValue.of(enums));
}
if ((formatFlags & 1 << 17) != 0) {
Map<String, String> flags = new HashMap<>();
for (Symbol attrSymbol : attribute.getSymbolList()) {
String name = attrSymbol.getName().getName().replaceFirst("id/", "");
flags.put(name, Integer.toString(attrSymbol.getValue()));
}
formats.put("flags", FlagResourceXmlAttrValue.of(flags));
}
if ((formatFlags & 0xFFFCFF00) != 0) {
throw new InvalidProtocolBufferException("Unexpected format flags: " + formatFlags);
}
}
return of(formats.build(), proto.getWeak());
}
/** Creates a new {@link AttrXmlResourceValue}. Returns null if there are no formats. */
@Nullable
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()) {
QName tagName = nextTag.asStartElement().getName();
if (TAG_FLAG.equals(tagName)) {
formatNames.add(FLAGS);
} else {
formatNames.add(tagName.getLocalPart().toLowerCase());
}
}
ImmutableMap.Builder<String, ResourceXmlAttrValue> formats = ImmutableMap.builder();
for (String formatName : formatNames) {
switch (formatName) {
case FLAGS:
Map<String, String> flags = readSubValues(eventReader, TAG_FLAG);
endAttrElement(eventReader);
formats.put(formatName, FlagResourceXmlAttrValue.of(flags));
break;
case ENUM:
Map<String, String> enums = readSubValues(eventReader, TAG_ENUM);
endAttrElement(eventReader);
formats.put(formatName, EnumResourceXmlAttrValue.of(enums));
break;
case REFERENCE:
formats.put(formatName, ReferenceResourceXmlAttrValue.of());
break;
case COLOR:
formats.put(formatName, ColorResourceXmlAttrValue.of());
break;
case BOOLEAN:
formats.put(formatName, BooleanResourceXmlAttrValue.of());
break;
case DIMENSION:
formats.put(formatName, DimensionResourceXmlAttrValue.of());
break;
case FLOAT:
formats.put(formatName, FloatResourceXmlAttrValue.of());
break;
case INTEGER:
formats.put(formatName, IntegerResourceXmlAttrValue.of());
break;
case STRING:
formats.put(formatName, StringResourceXmlAttrValue.of());
break;
case FRACTION:
formats.put(formatName, FractionResourceXmlAttrValue.of());
break;
default:
throw new XMLStreamException(
String.format("Unexpected attr format: %S", formatName), attr.getLocation());
}
}
return of(formats.build());
}
public static XmlResourceValue of(ImmutableMap<String, ResourceXmlAttrValue> formats) {
return new AttrXmlResourceValue(formats, /* weak= */ false);
}
public static XmlResourceValue of(
ImmutableMap<String, ResourceXmlAttrValue> formats, boolean weak) {
return new AttrXmlResourceValue(formats, weak);
}
@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) && weak == other.weak;
}
@Override
public int hashCode() {
return Objects.hash(formats, weak);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this).add("formats", formats).add("weak", weak).toString();
}
@Override
public void write(
FullyQualifiedName key, DataSource source, AndroidDataWritingVisitor mergedDataWriter) {
if (formats.isEmpty()) {
mergedDataWriter
.define(key)
.derivedFrom(source)
.startTag("attr")
.named(key)
.closeUnaryTag()
.save();
} else {
ImmutableList<String> formatKeys =
FluentIterable.from(formats.keySet())
.filter(not(equalTo(FLAGS)))
.filter(not(equalTo(ENUM)))
.toSortedList(Ordering.natural());
StartTag startTag =
mergedDataWriter
.define(key)
.derivedFrom(source)
.startTag("attr")
.named(key)
.optional()
.attribute("format")
.setFrom(formatKeys)
.joinedBy("|");
ValuesResourceDefinition definition;
if (formats.keySet().contains(FLAGS) || formats.keySet().contains(ENUM)) {
definition = startTag.closeTag();
for (ResourceXmlAttrValue value : formats.values()) {
definition = value.writeTo(definition);
}
definition = definition.addCharactersOf("\n").endTag();
} else {
definition = startTag.closeUnaryTag();
}
definition.save();
}
}
@Override
public void writeResourceToClass(FullyQualifiedName key, AndroidResourceSymbolSink sink) {
sink.acceptSimpleResource(key.type(), key.name());
// Flags and enums generate ID fields.
if (formats.keySet().contains(FLAGS) || formats.keySet().contains(ENUM)) {
for (ResourceXmlAttrValue value : formats.values()) {
value.writeToClass(sink);
}
}
}
@SuppressWarnings("deprecation")
@Override
public int serializeTo(int sourceId, Namespaces namespaces, OutputStream output)
throws IOException {
SerializeFormat.DataValue.Builder builder =
XmlResourceValues.newSerializableDataValueBuilder(sourceId);
SerializeFormat.DataValueXml.Builder xmlValueBuilder =
SerializeFormat.DataValueXml.newBuilder();
xmlValueBuilder
.setType(SerializeFormat.DataValueXml.XmlType.ATTR)
.putAllNamespace(namespaces.asMap());
for (Map.Entry<String, ResourceXmlAttrValue> entry : formats.entrySet()) {
xmlValueBuilder.putMappedXmlValue(
entry.getKey(), entry.getValue().appendTo(builder.getXmlValueBuilder()));
}
builder.setXmlValue(xmlValueBuilder);
return XmlResourceValues.serializeProtoDataValue(output, builder);
}
@Override
public XmlResourceValue combineWith(XmlResourceValue value) {
throw new IllegalArgumentException(this + " is not a combinable resource.");
}
@Override
public int compareMergePriorityTo(XmlResourceValue value) {
Preconditions.checkNotNull(value);
if (!(value instanceof AttrXmlResourceValue)) {
// NOTE(bcsf): I don't think this can happen. The resource type makes up part of the DataKey,
// so there would never be a collision between resources of different types.
throw new IllegalArgumentException(
String.format(
"Can only compare priority with another %s, but was given a %s",
AttrXmlResourceValue.class.getSimpleName(), value.getClass().getSimpleName()));
}
AttrXmlResourceValue that = (AttrXmlResourceValue) value;
if (!weak && that.weak && (that.formats.isEmpty() || formats.equals(that.formats))) {
return 1;
} else if (!that.weak && weak && (formats.isEmpty() || formats.equals(that.formats))) {
return -1;
} else if (weak && that.weak) {
if (!formats.isEmpty() && that.formats.isEmpty()) {
return 1;
} else if (formats.isEmpty() && !that.formats.isEmpty()) {
return -1;
}
}
return 0;
}
/** Represents the xml value for an attr definition. */
@CheckReturnValue
public interface ResourceXmlAttrValue {
ValuesResourceDefinition writeTo(ValuesResourceDefinition writer);
SerializeFormat.DataValueXml appendTo(SerializeFormat.DataValueXml.Builder builder);
void writeToClass(AndroidResourceSymbolSink writer);
}
// TODO(corysmith): The ResourceXmlAttrValue implementors, other than enum and flag, share a
// lot of boilerplate. Determine how to reduce it.
/** Represents an Android Enum Attribute resource. */
@VisibleForTesting
public static class EnumResourceXmlAttrValue implements ResourceXmlAttrValue {
private Map<String, String> values;
private EnumResourceXmlAttrValue(Map<String, String> values) {
this.values = values;
}
@VisibleForTesting
public static Map.Entry<String, ResourceXmlAttrValue> asEntryOf(String... keyThenValue) {
Preconditions.checkArgument(keyThenValue.length > 0);
Preconditions.checkArgument(keyThenValue.length % 2 == 0);
ImmutableMap.Builder<String, String> builder = ImmutableMap.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 ResourceXmlAttrValue of(Map<String, String> values) {
return new EnumResourceXmlAttrValue(values);
}
@Override
public int hashCode() {
return values.hashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof EnumResourceXmlAttrValue)) {
return false;
}
EnumResourceXmlAttrValue other = (EnumResourceXmlAttrValue) obj;
return Objects.equals(values, other.values);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(getClass()).add("values", values).toString();
}
@Override
public SerializeFormat.DataValueXml appendTo(SerializeFormat.DataValueXml.Builder builder) {
return builder.putAllMappedStringValue(values).build();
}
@Override
public ValuesResourceDefinition writeTo(ValuesResourceDefinition writer) {
for (Map.Entry<String, String> entry : values.entrySet()) {
writer =
writer
.startTag("enum")
.attribute("name")
.setTo(entry.getKey())
.attribute("value")
.setTo(entry.getValue())
.closeUnaryTag()
.addCharactersOf("\n");
}
return writer;
}
@Override
public void writeToClass(AndroidResourceSymbolSink writer) {
for (Map.Entry<String, String> entry : values.entrySet()) {
writer.acceptSimpleResource(ResourceType.ID, entry.getKey());
}
}
}
/** Represents an Android Flag Attribute resource. */
@VisibleForTesting
public static class FlagResourceXmlAttrValue implements ResourceXmlAttrValue {
private Map<String, String> values;
private FlagResourceXmlAttrValue(Map<String, String> values) {
this.values = values;
}
public static ResourceXmlAttrValue of(Map<String, String> values) {
// ImmutableMap guarantees a stable order.
return new FlagResourceXmlAttrValue(ImmutableMap.copyOf(values));
}
@VisibleForTesting
public static Map.Entry<String, ResourceXmlAttrValue> asEntryOf(String... keyThenValue) {
ImmutableMap.Builder<String, String> builder = ImmutableMap.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(FLAGS, of(builder.build()));
}
@Override
public int hashCode() {
return values.hashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof FlagResourceXmlAttrValue)) {
return false;
}
FlagResourceXmlAttrValue other = (FlagResourceXmlAttrValue) obj;
return Objects.equals(values, other.values);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(getClass()).add("values", values).toString();
}
@Override
public SerializeFormat.DataValueXml appendTo(SerializeFormat.DataValueXml.Builder builder) {
return builder.putAllMappedStringValue(values).build();
}
@Override
public ValuesResourceDefinition writeTo(ValuesResourceDefinition writer) {
for (Map.Entry<String, String> entry : values.entrySet()) {
writer =
writer
.startTag("flag")
.attribute("name")
.setTo(entry.getKey())
.attribute("value")
.setTo(entry.getValue())
.closeUnaryTag();
}
return writer;
}
@Override
public void writeToClass(AndroidResourceSymbolSink writer) {
for (Map.Entry<String, String> entry : values.entrySet()) {
writer.acceptSimpleResource(ResourceType.ID, entry.getKey());
}
}
}
/** Represents an Android Reference Attribute resource. */
@VisibleForTesting
public static class ReferenceResourceXmlAttrValue implements ResourceXmlAttrValue {
private static final ReferenceResourceXmlAttrValue INSTANCE =
new ReferenceResourceXmlAttrValue();
public static ResourceXmlAttrValue of() {
return INSTANCE;
}
@VisibleForTesting
public static BuilderEntry asEntry() {
return new BuilderEntry(REFERENCE, of());
}
@Override
public SerializeFormat.DataValueXml appendTo(SerializeFormat.DataValueXml.Builder builder) {
return builder.build();
}
@Override
public ValuesResourceDefinition writeTo(ValuesResourceDefinition writer) {
return writer;
}
@Override
public void writeToClass(AndroidResourceSymbolSink writer) {}
}
/** Represents an Android Color Attribute resource. */
@VisibleForTesting
public static class ColorResourceXmlAttrValue implements ResourceXmlAttrValue {
private static final ColorResourceXmlAttrValue INSTANCE = new ColorResourceXmlAttrValue();
public static ResourceXmlAttrValue of() {
return INSTANCE;
}
@VisibleForTesting
public static BuilderEntry asEntry() {
return new BuilderEntry(COLOR, of());
}
@Override
public SerializeFormat.DataValueXml appendTo(SerializeFormat.DataValueXml.Builder builder) {
return builder.build();
}
@Override
public ValuesResourceDefinition writeTo(ValuesResourceDefinition writer) {
return writer;
}
@Override
public void writeToClass(AndroidResourceSymbolSink writer) {}
}
/** Represents an Android Boolean Attribute resource. */
@VisibleForTesting
public static class BooleanResourceXmlAttrValue implements ResourceXmlAttrValue {
private static final BooleanResourceXmlAttrValue INSTANCE = new BooleanResourceXmlAttrValue();
public static ResourceXmlAttrValue of() {
return INSTANCE;
}
@VisibleForTesting
public static BuilderEntry asEntry() {
return new BuilderEntry(BOOLEAN, of());
}
@Override
public SerializeFormat.DataValueXml appendTo(SerializeFormat.DataValueXml.Builder builder) {
return builder.build();
}
@Override
public ValuesResourceDefinition writeTo(ValuesResourceDefinition writer) {
return writer;
}
@Override
public void writeToClass(AndroidResourceSymbolSink writer) {}
}
/** Represents an Android Float Attribute resource. */
@VisibleForTesting
public static class FloatResourceXmlAttrValue implements ResourceXmlAttrValue {
private static final FloatResourceXmlAttrValue INSTANCE = new FloatResourceXmlAttrValue();
public static ResourceXmlAttrValue of() {
return INSTANCE;
}
@VisibleForTesting
public static BuilderEntry asEntry() {
return new BuilderEntry(FLOAT, of());
}
@Override
public SerializeFormat.DataValueXml appendTo(SerializeFormat.DataValueXml.Builder builder) {
return builder.build();
}
@Override
public ValuesResourceDefinition writeTo(ValuesResourceDefinition writer) {
return writer;
}
@Override
public void writeToClass(AndroidResourceSymbolSink writer) {}
}
/** Represents an Android Dimension Attribute resource. */
@VisibleForTesting
public static class DimensionResourceXmlAttrValue implements ResourceXmlAttrValue {
private static final DimensionResourceXmlAttrValue INSTANCE =
new DimensionResourceXmlAttrValue();
public static ResourceXmlAttrValue of() {
return INSTANCE;
}
@VisibleForTesting
public static BuilderEntry asEntry() {
return new BuilderEntry(DIMENSION, of());
}
@Override
public SerializeFormat.DataValueXml appendTo(SerializeFormat.DataValueXml.Builder builder) {
return builder.build();
}
@Override
public ValuesResourceDefinition writeTo(ValuesResourceDefinition writer) {
return writer;
}
@Override
public void writeToClass(AndroidResourceSymbolSink writer) {}
}
/** Represents an Android Integer Attribute resource. */
@VisibleForTesting
public static class IntegerResourceXmlAttrValue implements ResourceXmlAttrValue {
private static final IntegerResourceXmlAttrValue INSTANCE = new IntegerResourceXmlAttrValue();
public static ResourceXmlAttrValue of() {
return INSTANCE;
}
@VisibleForTesting
public static BuilderEntry asEntry() {
return new BuilderEntry(INTEGER, of());
}
@Override
public SerializeFormat.DataValueXml appendTo(SerializeFormat.DataValueXml.Builder builder) {
return builder.build();
}
@Override
public ValuesResourceDefinition writeTo(ValuesResourceDefinition writer) {
return writer;
}
@Override
public void writeToClass(AndroidResourceSymbolSink writer) {}
}
/** Represents an Android String Attribute resource. */
@VisibleForTesting
public static class StringResourceXmlAttrValue implements ResourceXmlAttrValue {
private static final StringResourceXmlAttrValue INSTANCE = new StringResourceXmlAttrValue();
public static ResourceXmlAttrValue of() {
return INSTANCE;
}
@VisibleForTesting
public static BuilderEntry asEntry() {
return new BuilderEntry(STRING, of());
}
@Override
public SerializeFormat.DataValueXml appendTo(SerializeFormat.DataValueXml.Builder builder) {
return builder.build();
}
@Override
public ValuesResourceDefinition writeTo(ValuesResourceDefinition writer) {
return writer;
}
@Override
public void writeToClass(AndroidResourceSymbolSink writer) {}
}
/** Represents an Android Fraction Attribute resource. */
@VisibleForTesting
public static class FractionResourceXmlAttrValue implements ResourceXmlAttrValue {
private static final FractionResourceXmlAttrValue INSTANCE = new FractionResourceXmlAttrValue();
public static ResourceXmlAttrValue of() {
return INSTANCE;
}
@VisibleForTesting
public static BuilderEntry asEntry() {
return new BuilderEntry(FRACTION, of());
}
@Override
public SerializeFormat.DataValueXml appendTo(SerializeFormat.DataValueXml.Builder builder) {
return builder.build();
}
@Override
public ValuesResourceDefinition writeTo(ValuesResourceDefinition writer) {
return writer;
}
@Override
public void writeToClass(AndroidResourceSymbolSink writer) {}
}
@Override
public String asConflictStringWith(DataSource source) {
return String.format(
"%s [format(s): %s], [weak: %s]",
source.asConflictString(), String.join("|", this.formats.keySet()), weak);
}
}