diff options
author | 2017-03-13 18:02:16 +0000 | |
---|---|---|
committer | 2017-03-14 08:53:14 +0000 | |
commit | 82122525effd106e551503f70050bac18d0f85cb (patch) | |
tree | 878a3e8f44945f292e450e19f075f61e70c737eb /src/tools/android/java/com/google | |
parent | 3a26d6924bf3a6e35d3718eb498def3693155ac6 (diff) |
Add <resources> attribute processing to the Android resource processing toolchain.
--
PiperOrigin-RevId: 149963021
MOS_MIGRATED_REVID=149963021
Diffstat (limited to 'src/tools/android/java/com/google')
9 files changed, 439 insertions, 66 deletions
diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidDataDeserializer.java b/src/tools/android/java/com/google/devtools/build/android/AndroidDataDeserializer.java index 3b7d64986b..c42e870bbd 100644 --- a/src/tools/android/java/com/google/devtools/build/android/AndroidDataDeserializer.java +++ b/src/tools/android/java/com/google/devtools/build/android/AndroidDataDeserializer.java @@ -43,8 +43,8 @@ public class AndroidDataDeserializer { * Reads the serialized {@link DataKey} and {@link DataValue} to the {@link KeyValueConsumers}. * * @param inPath The path to the serialized protocol buffer. - * @param consumers The {@link KeyValueConsumers} for the entries {@link DataKey} -> {@link - * DataValue}. + * @param consumers The {@link KeyValueConsumers} for the entries {@link DataKey} -> + * {@link DataValue}. * @throws DeserializationException Raised for an IOException or when the inPath is not a valid * proto buffer. */ @@ -80,7 +80,7 @@ public class AndroidDataDeserializer { FullyQualifiedName resourceName = FullyQualifiedName.fromProto(protoKey); keys.put( resourceName, - FullyQualifiedName.isOverwritable(resourceName) + resourceName.isOverwritable() ? consumers.overwritingConsumer : consumers.combiningConsumer); } else { diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidDataWriter.java b/src/tools/android/java/com/google/devtools/build/android/AndroidDataWriter.java index f035758cfc..7b5f14d501 100644 --- a/src/tools/android/java/com/google/devtools/build/android/AndroidDataWriter.java +++ b/src/tools/android/java/com/google/devtools/build/android/AndroidDataWriter.java @@ -22,6 +22,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Ordering; import com.google.common.util.concurrent.ListenableFuture; @@ -254,7 +255,15 @@ public class AndroidDataWriter implements AndroidDataWritingVisitor { } return valueTags.get(valuesPath).resource(fqn); } - + + @Override + public void defineAttribute(FullyQualifiedName fqn, String value) { + String valuesPath = fqn.valuesPath(); + if (!valueTags.containsKey(valuesPath)) { + valueTags.put(valuesPath, new ResourceValuesDefinitions()); + } + valueTags.get(valuesPath).addAttribute(fqn.name(), value); + } @Override public void defineNamespacesFor(FullyQualifiedName fqn, Namespaces namespaces) { @@ -276,14 +285,17 @@ public class AndroidDataWriter implements AndroidDataWritingVisitor { private final Multimap<FullyQualifiedName, Segment> segments; private final Set<FullyQualifiedName> adopted; private final Namespaces namespaces; + private final Map<String, String> attributes; private WritingTask( Path valuesPath, Namespaces namespaces, + Map<String, String> attributes, Set<FullyQualifiedName> adopted, Multimap<FullyQualifiedName, Segment> segments) { this.valuesPath = valuesPath; this.namespaces = namespaces; + this.attributes = attributes; this.adopted = adopted; this.segments = segments; } @@ -306,6 +318,13 @@ public class AndroidDataWriter implements AndroidDataWritingVisitor { writer.write(prefixToUri.getValue()); writer.write("\""); } + for (Entry<String, String> attribute : attributes.entrySet()) { + writer.write(" "); + writer.write(attribute.getKey()); + writer.write("=\""); + writer.write(attribute.getValue()); + writer.write("\""); + } writer.write(">"); writer.write(LINE_END); Path previousSource = null; @@ -325,18 +344,23 @@ public class AndroidDataWriter implements AndroidDataWritingVisitor { final Multimap<FullyQualifiedName, Segment> segments = ArrayListMultimap.create(); final Set<FullyQualifiedName> adopted = new HashSet<>(); Namespaces namespaces = Namespaces.empty(); + final Map<String, String> attributes = Maps.newHashMap(); private ValueResourceDefinitionMetadata resource(final FullyQualifiedName fqn) { return new StringValueResourceDefinitionMetadata(segments, adopted, fqn); } + public void addAttribute(String name, String value) { + this.attributes.put(name, value); + } + public void addAllNamespaces(Namespaces namespaces) { this.namespaces = namespaces.union(this.namespaces); } /** Generates a {@link Callable} that will write the {@link Segment} to the provided path. */ public Callable<Boolean> createWritingTask(final Path valuesPath) { - return new WritingTask(valuesPath, namespaces, adopted, segments); + return new WritingTask(valuesPath, namespaces, attributes, adopted, segments); } } diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidDataWritingVisitor.java b/src/tools/android/java/com/google/devtools/build/android/AndroidDataWritingVisitor.java index 4dfe1a4935..6e9cd45884 100644 --- a/src/tools/android/java/com/google/devtools/build/android/AndroidDataWritingVisitor.java +++ b/src/tools/android/java/com/google/devtools/build/android/AndroidDataWritingVisitor.java @@ -50,6 +50,15 @@ public interface AndroidDataWritingVisitor extends Flushable { void copyResource(Path source, String relativeDestinationPath) throws MergingException; /** + * Adds the provided attribute to the root <resources> tag. + * + * @param fqn The fully qualified name of the attribute indicating both the name of the attribute + * and which qualified values.xml file to be associated with. + * @param value The value of the attribute. + */ + void defineAttribute(FullyQualifiedName fqn, String value); + + /** * Adds the namespaces associated with a {@link FullyQualifiedName}. * * <p>An xml namespace consists of a prefix and a uri. They are common declared on the root diff --git a/src/tools/android/java/com/google/devtools/build/android/DataResourceXml.java b/src/tools/android/java/com/google/devtools/build/android/DataResourceXml.java index 39f230dbec..f68aaf3807 100644 --- a/src/tools/android/java/com/google/devtools/build/android/DataResourceXml.java +++ b/src/tools/android/java/com/google/devtools/build/android/DataResourceXml.java @@ -22,6 +22,7 @@ import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.devtools.build.android.FullyQualifiedName.Factory; +import com.google.devtools.build.android.FullyQualifiedName.VirtualType; import com.google.devtools.build.android.ParsedAndroidData.KeyValueConsumer; import com.google.devtools.build.android.proto.SerializeFormat; import com.google.devtools.build.android.proto.SerializeFormat.DataValueXml; @@ -31,6 +32,7 @@ import com.google.devtools.build.android.xml.IdXmlResourceValue; import com.google.devtools.build.android.xml.Namespaces; import com.google.devtools.build.android.xml.PluralXmlResourceValue; import com.google.devtools.build.android.xml.PublicXmlResourceValue; +import com.google.devtools.build.android.xml.ResourcesAttribute; import com.google.devtools.build.android.xml.SimpleXmlResourceValue; import com.google.devtools.build.android.xml.StyleXmlResourceValue; import com.google.devtools.build.android.xml.StyleableXmlResourceValue; @@ -41,11 +43,13 @@ import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Iterator; import java.util.Objects; import javax.xml.stream.FactoryConfigurationError; import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.Attribute; import javax.xml.stream.events.StartElement; /** @@ -85,7 +89,29 @@ public class DataResourceXml implements DataResource { StandardCharsets.UTF_8.toString()); try { // TODO(corysmith): Make the xml parsing more readable. - while (XmlResourceValues.moveToResources(eventReader)) { + for (StartElement resources = XmlResourceValues.moveToResources(eventReader); + resources != null; + resources = XmlResourceValues.moveToResources(eventReader)) { + // Record attributes on the <resources> tag. + Iterator<Attribute> attributes = XmlResourceValues.iterateAttributesFrom(resources); + while (attributes.hasNext()) { + Attribute attribute = attributes.next(); + Namespaces namespaces = Namespaces.from(attribute.getName()); + String attributeName = + attribute.getName().getNamespaceURI().isEmpty() + ? attribute.getName().getLocalPart() + : attribute.getName().getPrefix() + ":" + attribute.getName().getLocalPart(); + overwritingConsumer.consume( + fqnFactory.create( + VirtualType.RESOURCES_ATTRIBUTE, + attributeName), + DataResourceXml.createWithNamespaces( + path, + ResourcesAttribute.of(attributeName, attribute.getValue()), + namespaces) + ); + } + // Process resource declarations. for (StartElement start = XmlResourceValues.findNextStart(eventReader); start != null; start = XmlResourceValues.findNextStart(eventReader)) { @@ -163,6 +189,8 @@ public class DataResourceXml implements DataResource { return StyleXmlResourceValue.from(proto); case STYLEABLE: return StyleableXmlResourceValue.from(proto); + case RESOURCES_ATTRIBUTE: + return ResourcesAttribute.from(proto); default: throw new IllegalArgumentException(); } @@ -307,7 +335,7 @@ public class DataResourceXml implements DataResource { @Override public DataResource combineWith(DataResource resource) { if (!(resource instanceof DataResourceXml)) { - throw new IllegalArgumentException(resource + " is not a combinable with " + this); + throw new IllegalArgumentException(resource + " is not combinable with " + this); } DataResourceXml xmlResource = (DataResourceXml) resource; return createWithNamespaces( 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 5ae3e2cc07..836239d6be 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 @@ -13,6 +13,8 @@ // limitations under the License. package com.google.devtools.build.android; +import static com.google.common.base.Preconditions.checkNotNull; + import com.android.ide.common.resources.configuration.FolderConfiguration; import com.android.ide.common.resources.configuration.ResourceQualifier; import com.android.resources.ResourceType; @@ -46,6 +48,158 @@ import javax.annotation.concurrent.Immutable; */ @Immutable public class FullyQualifiedName implements DataKey { + /** Represents the type of a {@link FullyQualifiedName}. */ + public interface Type { + /** + * The category of type that a {@link Type} can be. + * + * <p> + * <em>Note:</em> used for strict ordering of {@link FullyQualifiedName}s. + */ + public enum ConcreteType { + RESOURCE_TYPE, + VIRTUAL_TYPE; + } + + public String getName(); + public ConcreteType getType(); + public boolean isOverwritable(); + public int compareTo(Type other); + @Override public boolean equals(Object obj); + @Override public int hashCode(); + @Override public String toString(); + } + + private static Type createTypeFrom(String rawType) { + ResourceType resourceType = ResourceType.getEnum(rawType); + VirtualType virtualType = VirtualType.getEnum(rawType); + if (resourceType != null) { + return new ResourceTypeWrapper(resourceType); + } else if (virtualType != null) { + return virtualType; + } + return null; + } + + private static class ResourceTypeWrapper implements Type { + private final ResourceType resourceType; + + public ResourceTypeWrapper(ResourceType resourceType) { + this.resourceType = resourceType; + } + + @Override + public String getName() { + return resourceType.getName(); + } + + @Override + public ConcreteType getType() { + return ConcreteType.RESOURCE_TYPE; + } + + @Override + public boolean isOverwritable() { + return !(resourceType == ResourceType.ID + || resourceType == ResourceType.PUBLIC + || resourceType == ResourceType.STYLEABLE); + } + + @Override + public int compareTo(Type other) { + if (!(other instanceof ResourceTypeWrapper)) { + return getType().compareTo(other.getType()); + } + return resourceType.compareTo(((ResourceTypeWrapper) other).resourceType); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ResourceTypeWrapper)) { + return false; + } + ResourceTypeWrapper other = (ResourceTypeWrapper) obj; + return Objects.equals(resourceType, other.resourceType); + } + + @Override + public int hashCode() { + return Objects.hashCode(resourceType); + } + + @Override + public String toString() { + return resourceType.toString(); + } + } + + /** The non-resource {@link Type}s of a {@link FullyQualifiedName}. */ + public enum VirtualType implements Type { + RESOURCES_ATTRIBUTE("<resources>", "Resources Attribute"); + + /** Returns the enum represented by the {@code name}. */ + public static VirtualType getEnum(String name) { + for (VirtualType type : values()) { + if (type.name.equals(name)) { + return type; + } + } + return null; + } + + /** Returns an array with all the names defined by this enum. */ + public static String[] getNames() { + VirtualType[] values = values(); + String[] names = new String[values.length]; + for (int i = values.length - 1; i >= 0; --i) { + names[i] = values[i].getName(); + } + return names; + } + + private final String name; + private final String displayName; + + private VirtualType(String name, String displayName) { + this.name = name; + this.displayName = displayName; + } + + /** Returns the resource type name. */ + @Override + public String getName() { + return name; + } + + /** Returns a translated display name for the resource type. */ + public String getDisplayName() { + return displayName; + } + + @Override + public ConcreteType getType() { + return ConcreteType.VIRTUAL_TYPE; + } + + @Override + public boolean isOverwritable() { + return true; + } + + @Override + public int compareTo(Type other) { + if (!(other instanceof VirtualType)) { + return getType().compareTo(other.getType()); + } + return compareTo(((VirtualType) other)); + } + + @Override + public String toString() { + return getName(); + } + } + public static final String DEFAULT_PACKAGE = "res-auto"; private static final Joiner DASH_JOINER = Joiner.on('-'); @@ -65,13 +219,19 @@ public class FullyQualifiedName implements DataKey { public static final String INVALID_QUALIFIED_NAME_MESSAGE_NO_MATCH = String.format( "%%s is not a valid qualified name. " - + "It should be in the pattern [package:]{%s}/resourcename", - Joiner.on(",").join(ResourceType.values())); + + "It should be in the pattern [package:]{%s}/name", + Joiner.on(",").join(ImmutableList.<String>builder() + .add(ResourceType.getNames()) + .add(VirtualType.getNames()) + .build())); public static final String INVALID_QUALIFIED_NAME_MESSAGE_NO_TYPE_OR_NAME = String.format( "Could not find either resource type (%%s) or name (%%s) in %%s. " - + "It should be in the pattern [package:]{%s}/resourcename", - Joiner.on(",").join(ResourceType.values())); + + "It should be in the pattern [package:]{%s}/name", + Joiner.on(",").join(ImmutableList.<String>builder() + .add(ResourceType.getNames()) + .add(VirtualType.getNames()) + .build())); public static final String INVALID_QUALIFIERS = "%s contains invalid qualifiers."; private final ImmutableList<String> qualifiers; private final String pkg; @@ -157,12 +317,16 @@ public class FullyQualifiedName implements DataKey { return from(ImmutableList.copyOf(qualifiers), DEFAULT_PACKAGE); } - public FullyQualifiedName create(ResourceType resourceType, String resourceName) { - return create(resourceType, resourceName, pkg); + public FullyQualifiedName create(Type type, String name, String pkg) { + return FullyQualifiedName.of(pkg, qualifiers, type, name); + } + + public FullyQualifiedName create(ResourceType type, String name) { + return create(new ResourceTypeWrapper(type), name, pkg); } - public FullyQualifiedName create(ResourceType resourceType, String resourceName, String pkg) { - return FullyQualifiedName.of(pkg, qualifiers, resourceType, resourceName); + public FullyQualifiedName create(VirtualType type, String name) { + return create(type, name, pkg); } /** @@ -179,16 +343,16 @@ public class FullyQualifiedName implements DataKey { String.format(INVALID_QUALIFIED_NAME_MESSAGE_NO_MATCH, raw)); } String parsedPackage = matcher.group("package"); - ResourceType resourceType = ResourceType.getEnum(matcher.group("type")); - String resourceName = matcher.group("name"); + Type type = createTypeFrom(matcher.group("type")); + String name = matcher.group("name"); - if (resourceType == null || resourceName == null) { + if (type == null || name == null) { throw new IllegalArgumentException( String.format( - INVALID_QUALIFIED_NAME_MESSAGE_NO_TYPE_OR_NAME, resourceType, resourceName, raw)); + INVALID_QUALIFIED_NAME_MESSAGE_NO_TYPE_OR_NAME, type, name, raw)); } return FullyQualifiedName.of( - parsedPackage == null ? pkg : parsedPackage, qualifiers, resourceType, resourceName); + parsedPackage == null ? pkg : parsedPackage, qualifiers, type, name); } /** @@ -230,44 +394,56 @@ public class FullyQualifiedName implements DataKey { } } - public static boolean isOverwritable(FullyQualifiedName name) { - return !(name.resourceType == ResourceType.ID - || name.resourceType == ResourceType.PUBLIC - || name.resourceType == ResourceType.STYLEABLE); - } - /** * Creates a new FullyQualifiedName with normalized qualifiers. * * @param pkg The resource package of the name. If unknown the default should be "res-auto" * @param qualifiers The resource qualifiers of the name, such as "en" or "xhdpi". - * @param resourceType The resource type of the name. - * @param resourceName The resource name of the name. + * @param type The type of the name. + * @param name The name of the name. * @return A new FullyQualifiedName. */ public static FullyQualifiedName of( - String pkg, List<String> qualifiers, ResourceType resourceType, String resourceName) { + String pkg, List<String> qualifiers, Type type, String name) { + checkNotNull(pkg); + checkNotNull(qualifiers); + checkNotNull(type); + checkNotNull(name); ImmutableList<String> immutableQualifiers = ImmutableList.copyOf(qualifiers); // TODO(corysmith): Address the GC thrash this creates by managing a simplified, mutable key to // do the instance check. - FullyQualifiedName name = - new FullyQualifiedName(pkg, immutableQualifiers, resourceType, resourceName); + FullyQualifiedName fqn = + new FullyQualifiedName(pkg, immutableQualifiers, type, name); // Use putIfAbsent to get the canonical instance, if there. If it isn't, putIfAbsent will // return null, and we should return the current instance. - FullyQualifiedName cached = instanceCache.putIfAbsent(name, name); + FullyQualifiedName cached = instanceCache.putIfAbsent(fqn, fqn); if (cached == null) { - return name; + return fqn; } else { cacheHit.incrementAndGet(); return cached; } } + /** + * Creates a new FullyQualifiedName with normalized qualifiers. + * + * @param pkg The resource package of the name. If unknown the default should be "res-auto" + * @param qualifiers The resource qualifiers of the name, such as "en" or "xhdpi". + * @param type The resource type of the name. + * @param name The name of the name. + * @return A new FullyQualifiedName. + */ + static FullyQualifiedName of( + String pkg, List<String> qualifiers, ResourceType type, String name) { + return of(pkg, qualifiers, new ResourceTypeWrapper(type), name); + } + public static FullyQualifiedName fromProto(SerializeFormat.DataKey protoKey) { return of( protoKey.getKeyPackage(), protoKey.getQualifiersList(), - ResourceType.valueOf(protoKey.getResourceType()), + createTypeFrom(protoKey.getResourceType()), protoKey.getKeyValue()); } @@ -281,8 +457,19 @@ public class FullyQualifiedName implements DataKey { private final String pkg; private final ImmutableList<String> qualifiers; - private final ResourceType resourceType; - private final String resourceName; + private final Type type; + private final String name; + + private FullyQualifiedName( + String pkg, + ImmutableList<String> qualifiers, + Type type, + String name) { + this.pkg = pkg; + this.qualifiers = qualifiers; + this.type = type; + this.name = name; + } /** * Returns a string path representation of the FullyQualifiedName. @@ -298,10 +485,10 @@ public class FullyQualifiedName implements DataKey { return Paths.get( DASH_JOINER.join( ImmutableList.<String>builder() - .add(resourceType.getName()) + .add(type.getName()) .addAll(qualifiers) .build()), - resourceName + sourceExtension) + name + sourceExtension) .toString(); } @@ -311,8 +498,8 @@ public class FullyQualifiedName implements DataKey { return String.format( "%s/%s", DASH_JOINER.join( - ImmutableList.<String>builder().add(resourceType.getName()).addAll(qualifiers).build()), - resourceName); + ImmutableList.<String>builder().add(type.getName()).addAll(qualifiers).build()), + name); } /** @@ -333,22 +520,18 @@ public class FullyQualifiedName implements DataKey { } public String name() { - return resourceName; + return name; } public ResourceType type() { - return resourceType; + if (type instanceof ResourceTypeWrapper) { + return ((ResourceTypeWrapper) type).resourceType; + } + return null; } - private FullyQualifiedName( - String pkg, - ImmutableList<String> qualifiers, - ResourceType resourceType, - String resourceName) { - this.pkg = pkg; - this.qualifiers = qualifiers; - this.resourceType = resourceType; - this.resourceName = resourceName; + public boolean isOverwritable() { + return type.isOverwritable(); } /** Creates a FullyQualifiedName from this one with a different package. */ @@ -357,12 +540,12 @@ public class FullyQualifiedName implements DataKey { if (pkg.equals(newPackage)) { return this; } - return of(newPackage, qualifiers, resourceType, resourceName); + return of(newPackage, qualifiers, type, name); } @Override public int hashCode() { - return Objects.hash(pkg, qualifiers, resourceType, resourceName); + return Objects.hash(pkg, qualifiers, type, name); } @Override @@ -372,8 +555,8 @@ public class FullyQualifiedName implements DataKey { } FullyQualifiedName other = getClass().cast(obj); return Objects.equals(pkg, other.pkg) - && Objects.equals(resourceType, other.resourceType) - && Objects.equals(resourceName, other.resourceName) + && Objects.equals(type, other.type) + && Objects.equals(name, other.name) && Objects.equals(qualifiers, other.qualifiers); } @@ -382,8 +565,8 @@ public class FullyQualifiedName implements DataKey { return MoreObjects.toStringHelper(getClass()) .add("pkg", pkg) .add("qualifiers", qualifiers) - .add("resourceType", resourceType) - .add("resourceName", resourceName) + .add("type", type) + .add("name", name) .toString(); } @@ -396,11 +579,11 @@ public class FullyQualifiedName implements DataKey { if (!pkg.equals(other.pkg)) { return pkg.compareTo(other.pkg); } - if (!resourceType.equals(other.resourceType)) { - return resourceType.compareTo(other.resourceType); + if (!type.equals(other.type)) { + return type.compareTo(other.type); } - if (!resourceName.equals(other.resourceName)) { - return resourceName.compareTo(other.resourceName); + if (!name.equals(other.name)) { + return name.compareTo(other.name); } if (!qualifiers.equals(other.qualifiers)) { if (qualifiers.size() != other.qualifiers.size()) { @@ -425,8 +608,8 @@ public class FullyQualifiedName implements DataKey { public SerializeFormat.DataKey.Builder toSerializedBuilder() { return SerializeFormat.DataKey.newBuilder() .setKeyPackage(pkg) - .setResourceType(resourceType.getName().toUpperCase()) + .setResourceType(type.getName()) .addAllQualifiers(qualifiers) - .setKeyValue(resourceName); + .setKeyValue(name); } } diff --git a/src/tools/android/java/com/google/devtools/build/android/XmlResourceValues.java b/src/tools/android/java/com/google/devtools/build/android/XmlResourceValues.java index 2cc49a8c34..60cbd1f4c6 100644 --- a/src/tools/android/java/com/google/devtools/build/android/XmlResourceValues.java +++ b/src/tools/android/java/com/google/devtools/build/android/XmlResourceValues.java @@ -409,14 +409,14 @@ public class XmlResourceValues { return null; } - static boolean moveToResources(XMLEventReader eventReader) throws XMLStreamException { + static StartElement moveToResources(XMLEventReader eventReader) throws XMLStreamException { while (eventReader.hasNext()) { StartElement next = findNextStart(eventReader); if (next != null && next.getName().equals(TAG_RESOURCES)) { - return true; + return next; } } - return false; + return null; } public static SerializeFormat.DataValue.Builder newSerializableDataValueBuilder(int sourceId) { 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 122d3569da..b0c7f8b0e1 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 @@ -73,6 +73,7 @@ message DataValueXml { SIMPLE = 5; STYLEABLE = 6; STYLE = 7; + RESOURCES_ATTRIBUTE = 8; } optional XmlType type = 1; diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/Namespaces.java b/src/tools/android/java/com/google/devtools/build/android/xml/Namespaces.java index 27947d6e01..82e8810139 100644 --- a/src/tools/android/java/com/google/devtools/build/android/xml/Namespaces.java +++ b/src/tools/android/java/com/google/devtools/build/android/xml/Namespaces.java @@ -97,6 +97,17 @@ public class Namespaces implements Iterable<Entry<String, String>> { return new Namespaces(ImmutableMap.copyOf(prefixToUri)); } + /** + * Create a {@link Namespaces} containing the singular namespace used by the name, or an empty + * one. + */ + public static Namespaces from(QName name) { + if (name.getPrefix().isEmpty()) { + return empty(); + } + return new Namespaces(ImmutableMap.of(name.getPrefix(), name.getNamespaceURI())); + } + public static Namespaces empty() { return EMPTY_INSTANCE; } diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/ResourcesAttribute.java b/src/tools/android/java/com/google/devtools/build/android/xml/ResourcesAttribute.java new file mode 100644 index 0000000000..b1d97407b8 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/xml/ResourcesAttribute.java @@ -0,0 +1,117 @@ +// Copyright 2017 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.Joiner; +import com.google.common.base.MoreObjects; +import com.google.common.collect.Iterables; +import com.google.devtools.build.android.AndroidDataWritingVisitor; +import com.google.devtools.build.android.AndroidResourceClassWriter; +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.Map; +import java.util.Objects; + +/** + * An attribute associated with the root <resources> tag of an xml file in a values folder. + */ +public class ResourcesAttribute implements XmlResourceValue { + + public static ResourcesAttribute of(String name, String value) { + return new ResourcesAttribute(name, value); + } + + public static ResourcesAttribute from(SerializeFormat.DataValueXml proto) { + Map.Entry<String, String> attribute = + Iterables.getOnlyElement(proto.getAttributeMap().entrySet()); + return of(attribute.getKey(), attribute.getValue()); + } + + private final String name; + private final String value; + + private ResourcesAttribute(String name, String value) { + this.name = name; + this.value = value; + } + + @Override + public void write( + FullyQualifiedName key, DataSource source, AndroidDataWritingVisitor mergedDataWriter) { + mergedDataWriter.defineAttribute(key, value); + } + + @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.RESOURCES_ATTRIBUTE) + .putAttribute(name, value); + builder.setXmlValue(xmlValueBuilder); + return XmlResourceValues.serializeProtoDataValue(output, builder); + } + + @Override + public int hashCode() { + return Objects.hash(name, value); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ResourcesAttribute)) { + return false; + } + ResourcesAttribute other = (ResourcesAttribute) obj; + return Objects.equals(name, other.name) + && Objects.equals(value, other.value); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(getClass()) + .add("name", name) + .add("value", value) + .toString(); + } + + @Override + public XmlResourceValue combineWith(XmlResourceValue value) { + if (!(value instanceof ResourcesAttribute)) { + throw new IllegalArgumentException(value + "is not combinable with " + this); + } + ResourcesAttribute other = (ResourcesAttribute) value; + if ((name.startsWith("tools:keep") && other.name.startsWith("tools:keep")) + || (name.startsWith("tools:discard") && other.name.startsWith("tools:discard"))) { + return of(name, Joiner.on(',').join(value, other.value)); + } + throw new IllegalArgumentException(value + "is not combinable with " + this); + } + + @Override + public void writeResourceToClass( + FullyQualifiedName key, AndroidResourceClassWriter resourceClassWriter) { + // This is an xml attribute and does not have any java representation. + } +} |