aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/tools/android/java/com/google/devtools/build/android/xml
diff options
context:
space:
mode:
authorGravatar Googler <noreply@google.com>2016-07-14 22:31:10 +0000
committerGravatar Dmitry Lomov <dslomov@google.com>2016-07-15 13:31:04 +0000
commit3b25028750dd7a6df6777f6c70c1feae9063a630 (patch)
treeed88d83b83b6fad418199d48c146a27e35fa0782 /src/tools/android/java/com/google/devtools/build/android/xml
parente629537510a297b587d7204549dd2aae9222728e (diff)
Record and propagate namespaces from the <resources> element correctly.
* Reduces the size of merged values.xml * Improves correctness of merged xml Sadly, this is also backwards compatible by allowing multiple definitions of a prefix with different namespaces. Will be cleaned up after transition. -- MOS_MIGRATED_REVID=127481147
Diffstat (limited to 'src/tools/android/java/com/google/devtools/build/android/xml')
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/xml/ArrayXmlResourceValue.java12
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/xml/AttrXmlResourceValue.java9
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/xml/IdXmlResourceValue.java10
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/xml/Namespaces.java159
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/xml/PluralXmlResourceValue.java6
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/xml/SimpleXmlResourceValue.java9
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/xml/StyleXmlResourceValue.java6
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/xml/StyleableXmlResourceValue.java6
8 files changed, 190 insertions, 27 deletions
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/ArrayXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/ArrayXmlResourceValue.java
index 5c9d64b637..71e24691ca 100644
--- a/src/tools/android/java/com/google/devtools/build/android/xml/ArrayXmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/ArrayXmlResourceValue.java
@@ -23,7 +23,6 @@ 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 java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Path;
@@ -31,7 +30,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
-
import javax.annotation.concurrent.Immutable;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
@@ -139,7 +137,8 @@ public class ArrayXmlResourceValue implements XmlResourceValue {
}
@Override
- public int serializeTo(Path source, OutputStream output) throws IOException {
+ public int serializeTo(Path source, Namespaces namespaces, OutputStream output)
+ throws IOException {
return XmlResourceValues.serializeProtoDataValue(
output,
XmlResourceValues.newSerializableDataValueBuilder(source)
@@ -147,6 +146,7 @@ public class ArrayXmlResourceValue implements XmlResourceValue {
SerializeFormat.DataValueXml.newBuilder()
.addAllListValue(values)
.setType(SerializeFormat.DataValueXml.XmlType.ARRAY)
+ .putAllNamespace(namespaces.asMap())
.putAllAttribute(attributes)
.setValueType(arrayType.toString())));
}
@@ -181,7 +181,8 @@ public class ArrayXmlResourceValue implements XmlResourceValue {
throw new IllegalArgumentException(this + " is not a combinable resource.");
}
- public static XmlResourceValue parseArray(XMLEventReader eventReader, StartElement start)
+ public static XmlResourceValue parseArray(
+ XMLEventReader eventReader, StartElement start, Namespaces.Collector namespacesCollector)
throws XMLStreamException {
List<String> values = new ArrayList<>();
for (XMLEvent element = XmlResourceValues.nextTag(eventReader);
@@ -193,7 +194,8 @@ public class ArrayXmlResourceValue implements XmlResourceValue {
String.format("Expected start element %s", element), element.getLocation());
}
String contents =
- XmlResourceValues.readContentsAsString(eventReader, element.asStartElement().getName());
+ XmlResourceValues.readContentsAsString(
+ eventReader, element.asStartElement().getName(), namespacesCollector);
values.add(contents != null ? contents : "");
}
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/AttrXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/AttrXmlResourceValue.java
index 5686b04e72..ca7a984bd8 100644
--- a/src/tools/android/java/com/google/devtools/build/android/xml/AttrXmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/AttrXmlResourceValue.java
@@ -32,7 +32,6 @@ 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.nio.file.Path;
@@ -43,7 +42,6 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
-
import javax.annotation.CheckReturnValue;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@@ -331,12 +329,15 @@ public class AttrXmlResourceValue implements XmlResourceValue {
@SuppressWarnings("deprecation")
@Override
- public int serializeTo(Path source, OutputStream output) throws IOException {
+ public int serializeTo(Path source, Namespaces namespaces, OutputStream output)
+ throws IOException {
SerializeFormat.DataValue.Builder builder =
XmlResourceValues.newSerializableDataValueBuilder(source);
SerializeFormat.DataValueXml.Builder xmlValueBuilder =
SerializeFormat.DataValueXml.newBuilder();
- xmlValueBuilder.setType(SerializeFormat.DataValueXml.XmlType.ATTR);
+ xmlValueBuilder
+ .setType(SerializeFormat.DataValueXml.XmlType.ATTR)
+ .putAllNamespace(namespaces.asMap());
for (Entry<String, ResourceXmlAttrValue> entry : formats.entrySet()) {
xmlValueBuilder
.getMutableMappedXmlValue()
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/IdXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/IdXmlResourceValue.java
index b300e43851..1b56f8b0c7 100644
--- a/src/tools/android/java/com/google/devtools/build/android/xml/IdXmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/IdXmlResourceValue.java
@@ -23,12 +23,10 @@ import com.google.devtools.build.android.proto.SerializeFormat;
import com.google.devtools.build.android.proto.SerializeFormat.DataValueXml.Builder;
import com.google.devtools.build.android.proto.SerializeFormat.DataValueXml.XmlType;
import com.google.protobuf.CodedOutputStream;
-
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Path;
import java.util.Objects;
-
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@@ -82,8 +80,12 @@ public class IdXmlResourceValue implements XmlResourceValue {
}
@Override
- public int serializeTo(Path source, OutputStream output) throws IOException {
- Builder xmlValue = SerializeFormat.DataValueXml.newBuilder().setType(XmlType.ID);
+ public int serializeTo(Path source, Namespaces namespaces, OutputStream output)
+ throws IOException {
+ Builder xmlValue =
+ SerializeFormat.DataValueXml.newBuilder()
+ .setType(XmlType.ID)
+ .putAllNamespace(namespaces.asMap());
if (value != null) {
xmlValue.setValue(value);
}
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
new file mode 100644
index 0000000000..cbaabea740
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/Namespaces.java
@@ -0,0 +1,159 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.xml;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.android.DataResourceXml;
+import com.google.devtools.build.android.XmlResourceValue;
+import com.google.devtools.build.android.XmlResourceValues;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+import java.util.logging.Logger;
+import javax.xml.namespace.QName;
+import javax.xml.stream.events.Attribute;
+import javax.xml.stream.events.Namespace;
+import javax.xml.stream.events.StartElement;
+
+/**
+ * Represents a collection of xml namespaces.
+ *
+ * <p>Each &lt;resources&gt; can have xmlns declarations. Since the merging process generates the
+ * resources tag to combining multiple {@link DataResourceXml}s, the Namespaces must be tracked and
+ * kept with each value.
+ */
+public class Namespaces implements Iterable<Entry<String, String>> {
+ private static final Logger logger = Logger.getLogger(Namespaces.class.getCanonicalName());
+ private static final Namespaces EMPTY_INSTANCE =
+ new Namespaces(ImmutableMap.<String, String>of());
+
+ /**
+ * Collects prefix and uri pairs from elements.
+ */
+ public static class Collector {
+ private Map<String, String> prefixToUri = new HashMap<>();
+
+ public Namespaces toNamespaces() {
+ return Namespaces.from(prefixToUri);
+ }
+
+ /**
+ * Collects all the prefix and uri pairs from a start element.
+ *
+ * <p>Since {@link Namespaces} represents top level declarations, collectFrom ignores any prefix
+ * that is declared on the element. Those will be handled by the {@link XmlResourceValue}
+ * individually.
+ *
+ * @param start The element to collect prefix and uris from.
+ * @return The current namespace builder.
+ */
+ public Collector collectFrom(StartElement start) {
+ Iterator<Attribute> attributes = XmlResourceValues.iterateAttributesFrom(start);
+ Iterator<Namespace> localNamespaces = XmlResourceValues.iterateNamespacesFrom(start);
+ // Collect the local prefixes to make sure a prefix isn't declared locally.
+ Set<String> prefixes = new HashSet<>();
+ while (localNamespaces.hasNext()) {
+ prefixes.add(localNamespaces.next().getPrefix());
+ }
+ collectFrom(start.getName(), prefixes);
+ while (attributes.hasNext()) {
+ collectFrom(attributes.next().getName(), prefixes);
+ }
+ return this;
+ }
+
+ void collectFrom(QName name, Set<String> localPrefixes) {
+ String prefix = name.getPrefix();
+ if (!prefix.isEmpty() && !localPrefixes.contains(prefix)) {
+ // If the prefix exists and is not a locally declared prefix, add the prefix and uri.
+ prefixToUri.put(prefix, name.getNamespaceURI());
+ }
+ }
+ }
+
+ public static Collector collector() {
+ return new Collector();
+ }
+
+ public static Namespaces from(Map<String, String> prefixToUri) {
+ if (prefixToUri.isEmpty()) {
+ return empty();
+ }
+ return new Namespaces(ImmutableMap.copyOf(prefixToUri));
+ }
+
+ public static Namespaces empty() {
+ return EMPTY_INSTANCE;
+ }
+
+ private ImmutableMap<String, String> prefixToUri;
+
+ private Namespaces(ImmutableMap<String, String> prefixToUri) {
+ this.prefixToUri = prefixToUri;
+ }
+
+ /** Combines two {@link Namespaces} into a new instance and returns it. */
+ public Namespaces union(Namespaces other) {
+ // No prefixes to add, return the other.
+ if (prefixToUri.isEmpty()) {
+ return other;
+ }
+ // TODO(corysmith): Issue error when prefixes are mapped to different uris.
+ // Keeping behavior for backwards compatibility.
+ Map<String, String> combinedNamespaces = new LinkedHashMap<>();
+ combinedNamespaces.putAll(other.prefixToUri);
+ for (Entry<String, String> namespace : prefixToUri.entrySet()) {
+ String prefix = namespace.getKey();
+ String namespaceUri = namespace.getValue();
+ if (combinedNamespaces.containsKey(prefix)
+ && !combinedNamespaces.get(prefix).equals(namespaceUri)) {
+ logger.warning(
+ String.format(
+ "%s has multiple namespaces: %s and %s. Using %s."
+ + " This will be an error in the future.",
+ prefix, namespaceUri, combinedNamespaces.get(prefix), namespaceUri));
+ }
+ combinedNamespaces.put(prefix, namespaceUri);
+ }
+ return Namespaces.from(combinedNamespaces);
+ }
+
+ @Override
+ public Iterator<Entry<String, String>> iterator() {
+ return prefixToUri.entrySet().iterator();
+ }
+
+ public Map<String, String> asMap() {
+ return prefixToUri;
+ }
+
+ @Override
+ public int hashCode() {
+ return prefixToUri.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof Namespaces) {
+ Namespaces other = (Namespaces) obj;
+ return Objects.equals(prefixToUri, other.prefixToUri);
+ }
+ return false;
+ }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/PluralXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/PluralXmlResourceValue.java
index 29b2a2608f..1e708afb1c 100644
--- a/src/tools/android/java/com/google/devtools/build/android/xml/PluralXmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/PluralXmlResourceValue.java
@@ -23,13 +23,11 @@ import com.google.devtools.build.android.XmlResourceValues;
import com.google.devtools.build.android.proto.SerializeFormat;
import com.google.devtools.build.android.proto.SerializeFormat.DataValueXml.XmlType;
import com.google.protobuf.CodedOutputStream;
-
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Path;
import java.util.Map.Entry;
import java.util.Objects;
-
import javax.annotation.concurrent.Immutable;
import javax.xml.namespace.QName;
@@ -128,7 +126,8 @@ public class PluralXmlResourceValue implements XmlResourceValue {
}
@Override
- public int serializeTo(Path source, OutputStream output) throws IOException {
+ public int serializeTo(Path source, Namespaces namespaces, OutputStream output)
+ throws IOException {
SerializeFormat.DataValue.Builder builder =
XmlResourceValues.newSerializableDataValueBuilder(source);
SerializeFormat.DataValue value =
@@ -137,6 +136,7 @@ public class PluralXmlResourceValue implements XmlResourceValue {
builder
.getXmlValueBuilder()
.setType(XmlType.PLURAL)
+ .putAllNamespace(namespaces.asMap())
.putAllAttribute(attributes)
.putAllMappedStringValue(values))
.build();
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/SimpleXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/SimpleXmlResourceValue.java
index 3e308a1fab..ef4e822b4f 100644
--- a/src/tools/android/java/com/google/devtools/build/android/xml/SimpleXmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/SimpleXmlResourceValue.java
@@ -13,6 +13,7 @@
// limitations under the License.
package com.google.devtools.build.android.xml;
+import com.android.resources.ResourceType;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.android.AndroidDataWritingVisitor;
@@ -22,15 +23,11 @@ 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 com.android.resources.ResourceType;
-
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Objects;
-
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.xml.namespace.QName;
@@ -214,12 +211,14 @@ public class SimpleXmlResourceValue implements XmlResourceValue {
}
@Override
- public int serializeTo(Path source, OutputStream output) throws IOException {
+ public int serializeTo(Path source, Namespaces namespaces, OutputStream output)
+ throws IOException {
SerializeFormat.DataValue.Builder builder =
XmlResourceValues.newSerializableDataValueBuilder(source);
Builder xmlValueBuilder =
builder
.getXmlValueBuilder()
+ .putAllNamespace(namespaces.asMap())
.setType(SerializeFormat.DataValueXml.XmlType.SIMPLE)
// TODO(corysmith): Find a way to avoid writing strings to the serialized format
// it's inefficient use of space and costs more when deserializing.
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/StyleXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/StyleXmlResourceValue.java
index c3edb182e4..60abda34fe 100644
--- a/src/tools/android/java/com/google/devtools/build/android/xml/StyleXmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/StyleXmlResourceValue.java
@@ -23,14 +23,12 @@ 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.XmlType;
-
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Path;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
-
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@@ -107,10 +105,12 @@ public class StyleXmlResourceValue implements XmlResourceValue {
}
@Override
- public int serializeTo(Path source, OutputStream output) throws IOException {
+ public int serializeTo(Path source, Namespaces namespaces, OutputStream output)
+ throws IOException {
SerializeFormat.DataValueXml.Builder xmlValueBuilder =
SerializeFormat.DataValueXml.newBuilder()
.setType(XmlType.STYLE)
+ .putAllNamespace(namespaces.asMap())
.putAllMappedStringValue(values);
if (parent != null) {
xmlValueBuilder.setValue(parent);
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 095ff7a1cb..82bf56584a 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
@@ -26,7 +26,6 @@ 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.XmlType;
-
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Path;
@@ -35,7 +34,6 @@ import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
-
import javax.annotation.concurrent.Immutable;
/**
@@ -138,13 +136,15 @@ public class StyleableXmlResourceValue implements XmlResourceValue {
}
@Override
- public int serializeTo(Path source, OutputStream output) throws IOException {
+ public int serializeTo(Path source, Namespaces namespaces, OutputStream output)
+ throws IOException {
return XmlResourceValues.serializeProtoDataValue(
output,
XmlResourceValues.newSerializableDataValueBuilder(source)
.setXmlValue(
SerializeFormat.DataValueXml.newBuilder()
.setType(XmlType.STYLEABLE)
+ .putAllNamespace(namespaces.asMap())
.addAllReferences(
Iterables.transform(attrs.entrySet(), FULLY_QUALIFIED_NAME_TO_DATA_KEY))));
}