aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/tools/android/java/com/google
diff options
context:
space:
mode:
authorGravatar Andrew Pellegrini <apell@google.com>2017-03-13 18:02:16 +0000
committerGravatar Yun Peng <pcloudy@google.com>2017-03-14 08:53:14 +0000
commit82122525effd106e551503f70050bac18d0f85cb (patch)
tree878a3e8f44945f292e450e19f075f61e70c737eb /src/tools/android/java/com/google
parent3a26d6924bf3a6e35d3718eb498def3693155ac6 (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')
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidDataDeserializer.java6
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidDataWriter.java28
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidDataWritingVisitor.java9
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/DataResourceXml.java32
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/FullyQualifiedName.java295
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/XmlResourceValues.java6
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/proto/serialize_format.proto1
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/xml/Namespaces.java11
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/xml/ResourcesAttribute.java117
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} -&gt; {@link
- * DataValue}.
+ * @param consumers The {@link KeyValueConsumers} for the entries {@link DataKey} -&gt;
+ * {@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 &lt;resources&gt; 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 &lt;resources&gt; 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.
+ }
+}