From d9bf45b0ed79aa0a02edccaa5f0b2dab33f25ddc Mon Sep 17 00:00:00 2001 From: Googler Date: Tue, 28 Jun 2016 22:24:56 +0000 Subject: Record whether the attr is defined or referenced in the styleable. Despite the functional equality, the definition type of the attribute has direct impact on the order in which the attribute appears in the styleable array. -- MOS_MIGRATED_REVID=126126122 --- .../devtools/build/android/FullyQualifiedName.java | 11 +- .../devtools/build/android/XmlResourceValues.java | 11 +- .../build/android/proto/serialize_format.proto | 3 + .../android/xml/StyleableXmlResourceValue.java | 127 ++++++++++++++------- 4 files changed, 101 insertions(+), 51 deletions(-) diff --git a/src/tools/android/java/com/google/devtools/build/android/FullyQualifiedName.java b/src/tools/android/java/com/google/devtools/build/android/FullyQualifiedName.java index d554861e3f..eeebbbcfab 100644 --- a/src/tools/android/java/com/google/devtools/build/android/FullyQualifiedName.java +++ b/src/tools/android/java/com/google/devtools/build/android/FullyQualifiedName.java @@ -422,13 +422,14 @@ public class FullyQualifiedName implements DataKey, Comparable members = new ArrayList<>(); + Map members = new LinkedHashMap<>(); for (XMLEvent element = nextTag(eventReader); !isEndTag(element, TAG_DECLARE_STYLEABLE); element = nextTag(eventReader)) { if (isStartTag(element, TAG_ATTR)) { StartElement attr = element.asStartElement(); - String attrName = getElementName(attr); - members.add(attrName); + FullyQualifiedName attrName = fqnFactory.create(ResourceType.ATTR, getElementName(attr)); // If there is format and the next tag is a starting tag, treat it as an attr definition. // Without those, it will be an attr reference. if (XmlResourceValues.getElementAttributeByName(attr, ATTR_FORMAT) != null || (XmlResourceValues.peekNextTag(eventReader) != null && XmlResourceValues.peekNextTag(eventReader).isStartElement())) { overwritingConsumer.consume( - fqnFactory.create(ResourceType.ATTR, attrName), - DataResourceXml.of(path, parseAttr(eventReader, attr))); + attrName, DataResourceXml.of(path, parseAttr(eventReader, attr))); + members.put(attrName, Boolean.TRUE); + } else { + members.put(attrName, Boolean.FALSE); } } } diff --git a/src/tools/android/java/com/google/devtools/build/android/proto/serialize_format.proto b/src/tools/android/java/com/google/devtools/build/android/proto/serialize_format.proto index 32670daab2..e546aa0ad7 100644 --- a/src/tools/android/java/com/google/devtools/build/android/proto/serialize_format.proto +++ b/src/tools/android/java/com/google/devtools/build/android/proto/serialize_format.proto @@ -38,6 +38,8 @@ message DataKey { // The size of the associated value. Useful for calculating an offset. // Required optional int32 value_size = 6; + // Whether this DataKey is a reference to another DataKey. + optional bool reference = 7; } // The serialized format for a DataValue. @@ -72,4 +74,5 @@ message DataValueXml { repeated string list_value = 4; optional string value = 5; optional string value_type = 6; + repeated DataKey references = 7; } 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 index 62fcf4b629..0ed66afaf7 100644 --- 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 @@ -18,8 +18,9 @@ import com.google.common.base.Function; import com.google.common.base.MoreObjects; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Ordering; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMap.Builder; +import com.google.common.collect.Iterables; import com.google.devtools.build.android.AndroidDataWritingVisitor; import com.google.devtools.build.android.FullyQualifiedName; import com.google.devtools.build.android.XmlResourceValue; @@ -30,60 +31,90 @@ import com.google.devtools.build.android.proto.SerializeFormat.DataValueXml.XmlT import java.io.IOException; import java.io.OutputStream; import java.nio.file.Path; -import java.util.Arrays; -import java.util.List; +import java.util.AbstractMap.SimpleEntry; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; -import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; /** * Represent an Android styleable resource. * - *

- * 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 <declare-styleable> - * tags, for example; - * - * - * - * + *

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 is important to note that attributes declared inside + * <declare-styleable> tags, for example; * - * Can also be seen as: - * - * - * - * - * + *

Can also be seen as: * - *

- * The StyleableXmlValue only contains names of the attributes it holds, not definitions. + *

However, aapt will parse these two cases differently. In order to maintain the expected + * indexing for the styleable array + * (http://developer.android.com/reference/android/content/res/Resources.Theme.html + * #obtainStyledAttributes(android.util.AttributeSet, int[], int, int)) the styleable must track + * whether the attr is a reference or a definition, as aapt will sort the attributes first by attr + * format (the absence of format comes first, followed by alphabetical sorting by format, then + * sorting by declaration order in the source xml.) */ @Immutable public class StyleableXmlResourceValue implements XmlResourceValue { - public static final Function ITEM_TO_ATTR = - new Function() { - @Nullable + public static final Function ITEM_TO_ATTR = + new Function() { @Override - public String apply(@Nullable String input) { - return String.format("", input); + public String apply(FullyQualifiedName input) { + return String.format("", input.name()); } }; - private final ImmutableList attrs; - private StyleableXmlResourceValue(ImmutableList attrs) { + static final Function, SerializeFormat.DataKey> + FULLY_QUALIFIED_NAME_TO_DATA_KEY = + new Function, SerializeFormat.DataKey>() { + @Override + public SerializeFormat.DataKey apply(Entry input) { + return input.getKey().toSerializedBuilder().setReference(input.getValue()).build(); + } + }; + + static final Function> + DATA_KEY_TO_FULLY_QUALIFIED_NAME = + new Function>() { + @Override + public Entry apply(SerializeFormat.DataKey input) { + FullyQualifiedName key = FullyQualifiedName.fromProto(input); + return new SimpleEntry(key, input.getReference()); + } + }; + + private final ImmutableMap attrs; + + private StyleableXmlResourceValue(ImmutableMap attrs) { this.attrs = attrs; } - public static XmlResourceValue of(List attrs) { - return new StyleableXmlResourceValue(ImmutableList.copyOf(attrs)); + @VisibleForTesting + public static XmlResourceValue createAllAttrAsReferences(FullyQualifiedName... attrNames) { + return of(createAttrDefinitionMap(attrNames, Boolean.FALSE)); + } + + private static Map createAttrDefinitionMap( + FullyQualifiedName[] attrNames, Boolean definitionType) { + Builder builder = ImmutableMap.builder(); + for (FullyQualifiedName attrName : attrNames) { + builder.put(attrName, definitionType); + } + return builder.build(); } @VisibleForTesting - public static XmlResourceValue of(String... attrs) { - return new StyleableXmlResourceValue( - Ordering.natural().immutableSortedCopy(Arrays.asList(attrs))); + public static XmlResourceValue createAllAttrAsDefinitions(FullyQualifiedName... attrNames) { + return of(createAttrDefinitionMap(attrNames, Boolean.TRUE)); + } + + public static XmlResourceValue of(Map attrs) { + return new StyleableXmlResourceValue(ImmutableMap.copyOf(attrs)); } @Override @@ -95,7 +126,7 @@ public class StyleableXmlResourceValue implements XmlResourceValue { ImmutableList.of( String.format("", source), String.format("", key.name()))) - .append(FluentIterable.from(attrs).transform(ITEM_TO_ATTR)) + .append(FluentIterable.from(attrs.keySet()).transform(ITEM_TO_ATTR)) .append("")); } @@ -107,11 +138,14 @@ public class StyleableXmlResourceValue implements XmlResourceValue { .setXmlValue( SerializeFormat.DataValueXml.newBuilder() .setType(XmlType.STYLEABLE) - .addAllListValue(attrs))); + .addAllReferences( + Iterables.transform(attrs.entrySet(), FULLY_QUALIFIED_NAME_TO_DATA_KEY)))); } public static XmlResourceValue from(SerializeFormat.DataValueXml proto) { - return of(proto.getListValueList()); + return of( + ImmutableMap.copyOf( + Iterables.transform(proto.getReferencesList(), DATA_KEY_TO_FULLY_QUALIFIED_NAME))); } @Override @@ -152,9 +186,20 @@ public class StyleableXmlResourceValue implements XmlResourceValue { throw new IllegalArgumentException(value + "is not combinable with " + this); } StyleableXmlResourceValue styleable = (StyleableXmlResourceValue) value; - return of( - Ordering.natural() - .sortedCopy( - ImmutableSet.builder().addAll(attrs).addAll(styleable.attrs).build())); + Map combined = new LinkedHashMap<>(); + combined.putAll(attrs); + for (Entry attr : styleable.attrs.entrySet()) { + if (combined.containsKey(attr.getKey())) { + // if either attr is defined in the styleable, the attr will be defined in the styleable. + if (attr.getValue() || combined.get(attr.getKey())) { + combined.put(attr.getKey(), Boolean.TRUE); + } else { + combined.put(attr.getKey(), Boolean.FALSE); + } + } else { + combined.put(attr.getKey(), attr.getValue()); + } + } + return of(combined); } } -- cgit v1.2.3