diff options
Diffstat (limited to 'src')
21 files changed, 788 insertions, 81 deletions
diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidDataSerializer.java b/src/tools/android/java/com/google/devtools/build/android/AndroidDataSerializer.java new file mode 100644 index 0000000000..e4d7cead9d --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/AndroidDataSerializer.java @@ -0,0 +1,201 @@ +// 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; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Ordering; +import com.google.devtools.build.android.ParsedAndroidData.KeyValueConsumer; +import com.google.devtools.build.android.proto.SerializeFormat; +import com.google.devtools.build.android.proto.SerializeFormat.Header; +import com.google.devtools.build.android.proto.SerializeFormat.ProtoSource; +import com.google.protobuf.InvalidProtocolBufferException; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NavigableMap; +import java.util.NavigableSet; +import java.util.TreeMap; + +/** + * Writes an {@link UnwrittenMergedAndroidData} to a binary file. + */ +public class AndroidDataSerializer { + + // TODO(corysmith): We might need a more performant comparasion methodology than toString. + private final NavigableMap<DataKey, DataValue> primary = + new TreeMap<DataKey, DataValue>(Ordering.usingToString()); + + private final NavigableMap<DataKey, DataValue> deps = + new TreeMap<DataKey, DataValue>(Ordering.usingToString()); + private Path manifest; + + public static AndroidDataSerializer create() { + return new AndroidDataSerializer(); + } + + private AndroidDataSerializer() {} + + /** + * Writes all of the collected DataKey -> DataValue. + * + * The binary format will be: + * <pre> + * {@link Header} + * {@link com.google.devtools.build.android.proto.SerializeFormat.DataKey} primary... + * {@link com.google.devtools.build.android.proto.SerializeFormat.DataValue} primary... + * {@link com.google.devtools.build.android.proto.SerializeFormat.DataKey} transitive... + * {@link com.google.devtools.build.android.proto.SerializeFormat.DataValue} transitive... + * </pre> + * + * The key and values will be written in comparable order, allowing for the optimization of not + * converting the DataValue from binary, only writing it into a merged serialized binary. + */ + public void flushTo(Path out) throws IOException { + Preconditions.checkNotNull(manifest, "Manifest is required to serialize AndroidData."); + try (OutputStream outStream = + new BufferedOutputStream( + Files.newOutputStream(out, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE))) { + // Set the header for the deserialization process. + Header.newBuilder() + .setPrimaryEntries(primary.size()) + .setTransitiveEntries(deps.size()) + .setManifestPath(ProtoSource.newBuilder().setFilename(manifest.toString())) + .build() + .writeDelimitedTo(outStream); + + writeKeyValuesTo(primary, outStream); + writeKeyValuesTo(deps, outStream); + } + } + + private void writeKeyValuesTo(NavigableMap<DataKey, DataValue> map, OutputStream outStream) + throws IOException { + NavigableSet<DataKey> keys = map.navigableKeySet(); + int[] orderedValueSizes = new int[keys.size()]; + int valueSizeIndex = 0; + // Serialize all the values in sorted order to a intermediate buffer, so that the keys + // can be associated with a value size. + // TODO(corysmith): Tune the size of the byte array. + ByteArrayOutputStream valuesOutputStream = new ByteArrayOutputStream(2048); + for (DataKey key : keys) { + orderedValueSizes[valueSizeIndex++] = map.get(key).serializeTo(key, valuesOutputStream); + } + // Serialize all the keys in sorted order + valueSizeIndex = 0; + for (DataKey key : map.navigableKeySet()) { + key.serializeTo(outStream, orderedValueSizes[valueSizeIndex]); + } + // write the values to the output stream. + outStream.write(valuesOutputStream.toByteArray()); + } + + /** + * Reads the serialized {@link DataKey} and {@link DataValue} to the {@link KeyValueConsumers}. + * + * @param inPath The path to the serialized protocol buffer. + * @param primaryConsumers The {@link KeyValueConsumers} for the primary {@link DataKey} -> + * {@link DataValue} entries. + * @param transitiveConsumers The {@link KeyValueConsumers} for the transitive {@link DataKey} -> + * {@link DataValue} entries. + * @returns The manifest path for the serialized data. + * @throws IOException when io operations fail. + * @throws InvalidProtocolBufferException When the inPath is not a valid proto buffer -- e.g. + * legacy R.txts generated from android_resources. + */ + public Path read( + Path inPath, KeyValueConsumers primaryConsumers, KeyValueConsumers transitiveConsumers) + throws IOException, InvalidProtocolBufferException { + try (InputStream in = Files.newInputStream(inPath, StandardOpenOption.READ)) { + FileSystem currentFileSystem = inPath.getFileSystem(); + Header header = Header.parseDelimitedFrom(in); + readEntriesSegment(primaryConsumers, in, currentFileSystem, header.getPrimaryEntries()); + readEntriesSegment(transitiveConsumers, in, currentFileSystem, header.getTransitiveEntries()); + // Assuming that the inPath will be the same filesystem as the manifest. + // It seems highly unlikely they would be different. + Path manifestPath = inPath.getFileSystem().getPath(header.getManifestPath().getFilename()); + Preconditions.checkArgument( + Files.exists(manifestPath), "The manifest %s, does not exist.", manifestPath); + return manifestPath; + } + } + + private void readEntriesSegment( + KeyValueConsumers consumers, + InputStream in, + FileSystem currentFileSystem, + int numberOfEntries) + throws IOException, InvalidProtocolBufferException { + Map<DataKey, KeyValueConsumer<DataKey, ? extends DataValue>> keys = new LinkedHashMap<>(); + for (int i = 0; i < numberOfEntries; i++) { + SerializeFormat.DataKey protoKey = SerializeFormat.DataKey.parseDelimitedFrom(in); + if (protoKey.hasResourceType()) { + FullyQualifiedName resourceName = FullyQualifiedName.fromProto(protoKey); + keys.put( + resourceName, + FullyQualifiedName.isOverwritable(resourceName) + ? consumers.overwritingConsumer + : consumers.nonOverwritingConsumer); + } else { + keys.put(RelativeAssetPath.fromProto(protoKey, currentFileSystem), consumers.assetConsumer); + } + } + + // TODO(corysmith): Make this a lazy read of the values. + for (Entry<DataKey, KeyValueConsumer<DataKey, ?>> entry : keys.entrySet()) { + SerializeFormat.DataValue protoValue = SerializeFormat.DataValue.parseDelimitedFrom(in); + if (protoValue.hasXmlValue()) { + // TODO(corysmith): Figure out why the generics are wrong. + // If I use Map<DataKey, KeyValueConsumer<DataKey, ? extends DataValue>>, I can put + // consumers into the map, but I can't call consume. + // If I use Map<DataKey, KeyValueConsumer<DataKey, ? super DataValue>>, I can consume + // but I can't put. + // Same for below. + @SuppressWarnings("unchecked") + KeyValueConsumer<DataKey, DataValue> value = + (KeyValueConsumer<DataKey, DataValue>) entry.getValue(); + value.consume(entry.getKey(), DataResourceXml.from(protoValue, currentFileSystem)); + } else { + @SuppressWarnings("unchecked") + KeyValueConsumer<DataKey, DataValue> value = + (KeyValueConsumer<DataKey, DataValue>) entry.getValue(); + value.consume(entry.getKey(), DataValueFile.from(protoValue, currentFileSystem)); + } + } + } + + /** Queues the manifest for serialization. */ + public void serializeManifest(Path manifest) { + this.manifest = manifest; + } + + /** Queues the key and value for serialization as a primary entry. */ + public void serializeToPrimary(DataKey key, DataValue value) { + primary.put(key, value); + } + + /** Queues the key and value for serialization as a transitive entry. */ + public void serializeToTransitive(DataKey key, DataValue value) { + deps.put(key, value); + } +} 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 b954aff3bc..c025871aad 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 @@ -88,10 +88,10 @@ public class AndroidDataWriter implements Flushable, AndroidDataWritingVisitor { public void flush() throws IOException { Path values = Files.createDirectories(resourceDirectory().resolve("values")); try (FileChannel channel = - FileChannel.open( - values.resolve("values.xml"), - StandardOpenOption.CREATE_NEW, - StandardOpenOption.WRITE)) { + FileChannel.open( + values.resolve("values.xml"), + StandardOpenOption.CREATE_NEW, + StandardOpenOption.WRITE)) { channel.write(ByteBuffer.wrap("<resources>".getBytes(UTF_8))); for (FullyQualifiedName key : Ordering.natural().sortedCopy(valueFragments.keySet())) { for (String line : valueFragments.get(key)) { 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 40abe2f184..55ce652bf1 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 @@ -27,6 +27,7 @@ public interface AndroidDataWritingVisitor { /** * Copies the source asset to the relative destination path. + * * @param source The source file to copy. * @param relativeDestinationPath The relative destination path to write the asset to. * @throws IOException if there are errors during copying. @@ -35,6 +36,7 @@ public interface AndroidDataWritingVisitor { /** * Copies the source resource to the relative destination path. + * * @param source The source file to copy. * @param relativeDestinationPath The relative destination path to write the resource to. * @throws IOException if there are errors during copying. diff --git a/src/tools/android/java/com/google/devtools/build/android/BUILD b/src/tools/android/java/com/google/devtools/build/android/BUILD index d794f265e3..4caa6557a9 100644 --- a/src/tools/android/java/com/google/devtools/build/android/BUILD +++ b/src/tools/android/java/com/google/devtools/build/android/BUILD @@ -54,6 +54,7 @@ java_library( "//third_party:asm", "//third_party:guava", "//third_party:jsr305", + "//third_party/bazel/src/tools/android/java/com/google/devtools/build/android/proto:serialize_format_proto", "//third_party/protobuf", ], ) diff --git a/src/tools/android/java/com/google/devtools/build/android/DataKey.java b/src/tools/android/java/com/google/devtools/build/android/DataKey.java index ef447e0e25..e363bd74c4 100644 --- a/src/tools/android/java/com/google/devtools/build/android/DataKey.java +++ b/src/tools/android/java/com/google/devtools/build/android/DataKey.java @@ -13,8 +13,11 @@ // limitations under the License. package com.google.devtools.build.android; +import java.io.IOException; +import java.io.OutputStream; + /** - * A general marker interface for resource and asset keys. + * A general interface for resource and asset keys. * * Resource and Assets are merged on the basis of a key value: * @@ -23,4 +26,13 @@ package com.google.devtools.build.android; * * For Assets, it is the asset path from the assets directory. */ -public interface DataKey {} +public interface DataKey { + /** + * Writes the Key and the value size to a stream. + * + * @param output The destination stream to serialize the key. + * @param valueSize The size, in bytes, of the serialized output for this key. The value size can + * be used for calculating offsets of the value in the stream. + */ + void serializeTo(OutputStream output, int valueSize) throws IOException; +} 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 21e8c92657..8e79ed4738 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 @@ -17,14 +17,25 @@ import static com.android.resources.ResourceType.DECLARE_STYLEABLE; import static com.android.resources.ResourceType.ID; import com.google.common.base.MoreObjects; +import com.google.common.base.Preconditions; import com.google.devtools.build.android.FullyQualifiedName.Factory; import com.google.devtools.build.android.ParsedAndroidData.KeyValueConsumer; +import com.google.devtools.build.android.proto.SerializeFormat; import com.google.devtools.build.android.xml.ArrayXmlResourceValue; +import com.google.devtools.build.android.xml.AttrXmlResourceValue; +import com.google.devtools.build.android.xml.IdXmlResourceValue; +import com.google.devtools.build.android.xml.PluralXmlResourceValue; +import com.google.devtools.build.android.xml.SimpleXmlResourceValue; +import com.google.devtools.build.android.xml.StyleXmlResourceValue; +import com.google.devtools.build.android.xml.StyleableXmlResourceValue; +import com.google.protobuf.InvalidProtocolBufferException; import com.android.resources.ResourceType; import java.io.IOException; +import java.io.OutputStream; import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.Objects; @@ -38,8 +49,9 @@ import javax.xml.stream.events.StartElement; /** * Represents an Android Resource defined in the xml and value folder. * - * <p>Basically, if the resource is defined inside a <resources> tag, this class will - * handle it. Layouts are treated separately as they don't declare anything besides ids. + * <p> + * Basically, if the resource is defined inside a <resources> tag, this class will handle it. + * Layouts are treated separately as they don't declare anything besides ids. */ public class DataResourceXml implements DataResource { @@ -53,7 +65,7 @@ public class DataResourceXml implements DataResource { * @param path The path to the xml resource to be parsed. * @param fqnFactory Used to create {@link FullyQualifiedName}s from the resource names. * @param overwritingConsumer A consumer for overwritable {@link DataResourceXml}s. - * @param nonOverwritingConsumer A consumer for nonoverwritable {@link DataResourceXml}s. + * @param nonOverwritingConsumer A consumer for nonoverwritable {@link DataResourceXml}s. * @throws XMLStreamException Thrown with the resource format is invalid. * @throws FactoryConfigurationError Thrown with the {@link XMLInputFactory} is misconfigured. * @throws IOException Thrown when there is an error reading a file. @@ -91,6 +103,36 @@ public class DataResourceXml implements DataResource { } } + public static DataValue from(SerializeFormat.DataValue protoValue, FileSystem currentFileSystem) + throws InvalidProtocolBufferException { + return of( + currentFileSystem.getPath(protoValue.getSource().getFilename()), + valueFromProto(protoValue.getXmlValue())); + } + + private static XmlResourceValue valueFromProto(SerializeFormat.DataValueXml proto) + throws InvalidProtocolBufferException { + Preconditions.checkArgument(proto.hasType()); + switch (proto.getType()) { + case ARRAY: + return ArrayXmlResourceValue.from(proto); + case SIMPLE: + return SimpleXmlResourceValue.from(proto); + case ATTR: + return AttrXmlResourceValue.from(proto); + case ID: + return IdXmlResourceValue.of(); + case PLURAL: + return PluralXmlResourceValue.from(proto); + case STYLE: + return StyleXmlResourceValue.from(proto); + case STYLEABLE: + return StyleableXmlResourceValue.from(proto); + default: + throw new IllegalArgumentException(); + } + } + private static XmlResourceValue parseXmlElements( ResourceType resourceType, XMLEventReader eventReader, StartElement start) throws XMLStreamException { @@ -109,7 +151,7 @@ public class DataResourceXml implements DataResource { case BOOL: case COLOR: case DIMEN: - return XmlResourceValues.parseSimple(eventReader, resourceType); + return XmlResourceValues.parseSimple(eventReader, resourceType, start.getName()); default: throw new XMLStreamException( String.format("Unhandled resourceType %s", resourceType), start.getLocation()); @@ -163,4 +205,9 @@ public class DataResourceXml implements DataResource { public void writeResource(FullyQualifiedName key, AndroidDataWritingVisitor mergedDataWriter) { xml.write(key, source, mergedDataWriter); } + + @Override + public int serializeTo(DataKey key, OutputStream outStream) throws IOException { + return xml.serializeTo(source, outStream); + } } diff --git a/src/tools/android/java/com/google/devtools/build/android/DataValue.java b/src/tools/android/java/com/google/devtools/build/android/DataValue.java index ded0ce5819..f30916f1f6 100644 --- a/src/tools/android/java/com/google/devtools/build/android/DataValue.java +++ b/src/tools/android/java/com/google/devtools/build/android/DataValue.java @@ -13,6 +13,8 @@ // limitations under the License. package com.google.devtools.build.android; +import java.io.IOException; +import java.io.OutputStream; import java.nio.file.Path; /** @@ -26,4 +28,9 @@ public interface DataValue { * Provides the Path to the file from which the DataValue was derived. */ Path source(); + + /** + * Serializes to a supplied stream and returns the number of bytes written. + */ + int serializeTo(DataKey key, OutputStream output) throws IOException; } diff --git a/src/tools/android/java/com/google/devtools/build/android/DataValueFile.java b/src/tools/android/java/com/google/devtools/build/android/DataValueFile.java index b92002f783..91fcc57dc4 100644 --- a/src/tools/android/java/com/google/devtools/build/android/DataValueFile.java +++ b/src/tools/android/java/com/google/devtools/build/android/DataValueFile.java @@ -14,8 +14,12 @@ package com.google.devtools.build.android; import com.google.common.base.MoreObjects; +import com.google.devtools.build.android.proto.SerializeFormat; +import com.google.protobuf.CodedOutputStream; import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.FileSystem; import java.nio.file.Path; import java.util.Objects; @@ -36,6 +40,14 @@ public class DataValueFile implements DataResource, DataAsset { return new DataValueFile(source); } + /** + * Creates a {@link DataValueFile} from a {@link SerializeFormat.DataValue}. + */ + public static DataValueFile from( + SerializeFormat.DataValue protoValue, FileSystem currentFileSystem) { + return of(currentFileSystem.getPath(protoValue.getSource().getFilename())); + } + @Override public int hashCode() { return source.hashCode(); @@ -71,8 +83,19 @@ public class DataValueFile implements DataResource, DataAsset { throws IOException { mergedDataWriter.copyResource(source, key.toPathString(getSourceExtension())); } - + + @Override + public int serializeTo(DataKey key, OutputStream output) throws IOException { + SerializeFormat.DataValue.Builder builder = SerializeFormat.DataValue.newBuilder(); + SerializeFormat.DataValue value = + builder.setSource(builder.getSourceBuilder().setFilename(source.toString())).build(); + value.writeDelimitedTo(output); + return CodedOutputStream.computeUInt32SizeNoTag(value.getSerializedSize()) + + value.getSerializedSize(); + } + private String getSourceExtension() { + // TODO(corysmith): Switch to a filename parser utility. String fileName = source.getFileName().toString(); int extensionStart = fileName.lastIndexOf('.'); if (extensionStart > 0) { 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 303196cbfa..73f9da2a20 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 @@ -18,9 +18,12 @@ import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.Ordering; +import com.google.devtools.build.android.proto.SerializeFormat; import com.android.resources.ResourceType; +import java.io.IOException; +import java.io.OutputStream; import java.nio.file.Paths; import java.util.List; import java.util.Objects; @@ -38,6 +41,7 @@ import javax.annotation.concurrent.Immutable; @Immutable public class FullyQualifiedName implements DataKey, Comparable<FullyQualifiedName> { public static final String DEFAULT_PACKAGE = "res-auto"; + private static final Joiner DASH_JOINER = Joiner.on('-'); private final String pkg; private final ImmutableList<String> qualifiers; @@ -47,8 +51,8 @@ public class FullyQualifiedName implements DataKey, Comparable<FullyQualifiedNam /** * Returns a string path representation of the FullyQualifiedName. * - * Non-values Android Resource have a well defined file layout: From the resource directory, - * they reside in <resource type>[-<qualifier>]/<resource name>[.extension] + * Non-values Android Resource have a well defined file layout: From the resource directory, they + * reside in <resource type>[-<qualifier>]/<resource name>[.extension] * * @param sourceExtension The extension of the resource represented by the FullyQualifiedName * @return A string representation of the FullyQualifiedName with the provided extension. @@ -56,12 +60,11 @@ public class FullyQualifiedName implements DataKey, Comparable<FullyQualifiedNam public String toPathString(String sourceExtension) { // TODO(corysmith): Does the extension belong in the FullyQualifiedName? return Paths.get( - Joiner.on("-") - .join( - ImmutableList.<String>builder() - .add(resourceType.getName()) - .addAll(qualifiers) - .build()), + DASH_JOINER.join( + ImmutableList.<String>builder() + .add(resourceType.getName()) + .addAll(qualifiers) + .build()), resourceName + sourceExtension) .toString(); } @@ -106,7 +109,7 @@ public class FullyQualifiedName implements DataKey, Comparable<FullyQualifiedNam } /** - * Parses a FullyQualifiedName from a string . + * Parses a FullyQualifiedName from a string. * * @param raw A string in the expected format from * [<package>:]<ResourceType.name>/<resource name>. @@ -131,8 +134,13 @@ public class FullyQualifiedName implements DataKey, Comparable<FullyQualifiedNam } } + public static boolean isOverwritable(FullyQualifiedName name) { + return !(name.resourceType == ResourceType.ID || name.resourceType == ResourceType.STYLEABLE); + } + /** * Creates a new FullyQualifiedName with sorted 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. @@ -145,6 +153,14 @@ public class FullyQualifiedName implements DataKey, Comparable<FullyQualifiedNam pkg, Ordering.natural().immutableSortedCopy(qualifiers), resourceType, resourceName); } + public static FullyQualifiedName fromProto(SerializeFormat.DataKey protoKey) { + return of( + protoKey.getKeyPackage(), + protoKey.getQualifiersList(), + ResourceType.valueOf(protoKey.getResourceType()), + protoKey.getKeyValue()); + } + private FullyQualifiedName( String pkg, ImmutableList<String> qualifiers, @@ -215,4 +231,16 @@ public class FullyQualifiedName implements DataKey, Comparable<FullyQualifiedNam } return 0; } + + @Override + public void serializeTo(OutputStream out, int valueSize) throws IOException { + SerializeFormat.DataKey.newBuilder() + .setKeyPackage(pkg) + .setValueSize(valueSize) + .setResourceType(resourceType.getName().toUpperCase()) + .addAllQualifiers(qualifiers) + .setKeyValue(resourceName) + .build() + .writeDelimitedTo(out); + } } diff --git a/src/tools/android/java/com/google/devtools/build/android/KeyValueConsumers.java b/src/tools/android/java/com/google/devtools/build/android/KeyValueConsumers.java new file mode 100644 index 0000000000..86789b27b4 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/KeyValueConsumers.java @@ -0,0 +1,43 @@ +// 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; + +import com.google.devtools.build.android.ParsedAndroidData.KeyValueConsumer; + +/** + * A group of {@link KeyValueConsumer}s for each DataValue type. + * + * This class acts as a parameter object for organizing the common grouping of consumer instances. + */ +class KeyValueConsumers { + static KeyValueConsumers of( + KeyValueConsumer<DataKey, DataResource> overwritingConsumer, + KeyValueConsumer<DataKey, DataResource> nonOverwritingConsumer, + KeyValueConsumer<DataKey, DataAsset> assetConsumer) { + return new KeyValueConsumers(overwritingConsumer, nonOverwritingConsumer, assetConsumer); + } + + final KeyValueConsumer<DataKey, DataResource> overwritingConsumer; + final KeyValueConsumer<DataKey, DataResource> nonOverwritingConsumer; + final KeyValueConsumer<DataKey, DataAsset> assetConsumer; + + private KeyValueConsumers( + KeyValueConsumer<DataKey, DataResource> overwritingConsumer, + KeyValueConsumer<DataKey, DataResource> nonOverwritingConsumer, + KeyValueConsumer<DataKey, DataAsset> assetConsumer) { + this.overwritingConsumer = overwritingConsumer; + this.nonOverwritingConsumer = nonOverwritingConsumer; + this.assetConsumer = assetConsumer; + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/RelativeAssetPath.java b/src/tools/android/java/com/google/devtools/build/android/RelativeAssetPath.java index 609216ff64..ede9369cf5 100644 --- a/src/tools/android/java/com/google/devtools/build/android/RelativeAssetPath.java +++ b/src/tools/android/java/com/google/devtools/build/android/RelativeAssetPath.java @@ -15,7 +15,11 @@ package com.google.devtools.build.android; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; +import com.google.devtools.build.android.proto.SerializeFormat; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.FileSystem; import java.nio.file.Path; import java.util.Objects; @@ -55,6 +59,13 @@ public class RelativeAssetPath implements DataKey, Comparable<RelativeAssetPath> } } + /** + * Reconstitutes the relative asset path from a protocol buffer and {@link FileSystem}. + */ + static RelativeAssetPath fromProto(SerializeFormat.DataKey serialized, FileSystem fileSystem) { + return of(fileSystem.getPath(serialized.getKeyValue())); + } + private final Path relativeAssetPath; private RelativeAssetPath(Path relativeAssetPath) { @@ -95,4 +106,13 @@ public class RelativeAssetPath implements DataKey, Comparable<RelativeAssetPath> public int compareTo(RelativeAssetPath relativeAssetPath) { return this.relativeAssetPath.compareTo(relativeAssetPath.relativeAssetPath); } + + @Override + public void serializeTo(OutputStream output, int valueSize) throws IOException { + SerializeFormat.DataKey.newBuilder() + .setKeyValue(relativeAssetPath.toString()) + .setValueSize(valueSize) + .build() + .writeDelimitedTo(output); + } } diff --git a/src/tools/android/java/com/google/devtools/build/android/UnwrittenMergedAndroidData.java b/src/tools/android/java/com/google/devtools/build/android/UnwrittenMergedAndroidData.java index 633bd9403a..61248d031f 100644 --- a/src/tools/android/java/com/google/devtools/build/android/UnwrittenMergedAndroidData.java +++ b/src/tools/android/java/com/google/devtools/build/android/UnwrittenMergedAndroidData.java @@ -28,7 +28,7 @@ public class UnwrittenMergedAndroidData { private final Path manifest; private final ParsedAndroidData primary; - private final ParsedAndroidData deps; + private final ParsedAndroidData transitive; public static UnwrittenMergedAndroidData of( Path manifest, ParsedAndroidData resources, ParsedAndroidData deps) { @@ -36,14 +36,15 @@ public class UnwrittenMergedAndroidData { } private UnwrittenMergedAndroidData( - Path manifest, ParsedAndroidData primary, ParsedAndroidData deps) { + Path manifest, ParsedAndroidData primary, ParsedAndroidData transitive) { this.manifest = manifest; this.primary = primary; - this.deps = deps; + this.transitive = transitive; } /** * Writes the android data to the filesystem. + * * @param mergedDataWriter Destination writer. * @return A MergedAndroidData that is ready for further tool processing. * @throws IOException when something goes wrong while writing. @@ -51,7 +52,7 @@ public class UnwrittenMergedAndroidData { public MergedAndroidData write(AndroidDataWriter mergedDataWriter) throws IOException { try { writeParsedAndroidData(primary, mergedDataWriter); - writeParsedAndroidData(deps, mergedDataWriter); + writeParsedAndroidData(transitive, mergedDataWriter); return new MergedAndroidData( mergedDataWriter.resourceDirectory(), mergedDataWriter.assetDirectory(), @@ -84,7 +85,7 @@ public class UnwrittenMergedAndroidData { return MoreObjects.toStringHelper(this) .add("manifest", manifest) .add("primary", primary) - .add("deps", deps) + .add("transitive", transitive) .toString(); } @@ -99,12 +100,12 @@ public class UnwrittenMergedAndroidData { UnwrittenMergedAndroidData that = (UnwrittenMergedAndroidData) other; return Objects.equals(manifest, that.manifest) && Objects.equals(primary, that.primary) - && Objects.equals(deps, that.deps); + && Objects.equals(transitive, that.transitive); } @Override public int hashCode() { - return Objects.hash(manifest, primary, deps); + return Objects.hash(manifest, primary, transitive); } @VisibleForTesting @@ -118,7 +119,24 @@ public class UnwrittenMergedAndroidData { } @VisibleForTesting - ParsedAndroidData getDeps() { - return deps; + ParsedAndroidData getTransitive() { + return transitive; + } + + public void serializeTo(AndroidDataSerializer serializer) { + serializer.serializeManifest(manifest); + for (Entry<DataKey, DataAsset> entry : primary.iterateAssetEntries()) { + serializer.serializeToPrimary(entry.getKey(), entry.getValue()); + } + for (Entry<DataKey, DataResource> entry : primary.iterateDataResourceEntries()) { + serializer.serializeToPrimary(entry.getKey(), entry.getValue()); + } + + for (Entry<DataKey, DataAsset> entry : transitive.iterateAssetEntries()) { + serializer.serializeToTransitive(entry.getKey(), entry.getValue()); + } + for (Entry<DataKey, DataResource> entry : transitive.iterateDataResourceEntries()) { + serializer.serializeToTransitive(entry.getKey(), entry.getValue()); + } } } diff --git a/src/tools/android/java/com/google/devtools/build/android/XmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/XmlResourceValue.java index 1a3cfd1f66..dc916db8d4 100644 --- a/src/tools/android/java/com/google/devtools/build/android/XmlResourceValue.java +++ b/src/tools/android/java/com/google/devtools/build/android/XmlResourceValue.java @@ -13,6 +13,8 @@ // limitations under the License. package com.google.devtools.build.android; +import java.io.IOException; +import java.io.OutputStream; import java.nio.file.Path; /** @@ -27,4 +29,9 @@ public interface XmlResourceValue { * @param mergedDataWriter The target writer. */ void write(FullyQualifiedName key, Path source, AndroidDataWritingVisitor mergedDataWriter); + + /** + * Serializes the resource value to the OutputStream and returns the bytes written. + */ + int serializeTo(Path source, OutputStream out) throws IOException; } 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 f2525712ea..1ed13c1057 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 @@ -13,17 +13,22 @@ // limitations under the License. package com.google.devtools.build.android; +import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.devtools.build.android.ParsedAndroidData.KeyValueConsumer; +import com.google.devtools.build.android.proto.SerializeFormat; import com.google.devtools.build.android.xml.AttrXmlResourceValue; import com.google.devtools.build.android.xml.IdXmlResourceValue; import com.google.devtools.build.android.xml.PluralXmlResourceValue; import com.google.devtools.build.android.xml.SimpleXmlResourceValue; import com.google.devtools.build.android.xml.StyleXmlResourceValue; import com.google.devtools.build.android.xml.StyleableXmlResourceValue; +import com.google.protobuf.CodedOutputStream; import com.android.resources.ResourceType; +import java.io.IOException; +import java.io.OutputStream; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; @@ -40,8 +45,9 @@ import javax.xml.stream.events.XMLEvent; /** * {@link XmlResourceValues} provides methods for getting {@link XmlResourceValue} derived classes. * - * <p>Acts a static factory class containing the general xml parsing logic for resources - * that are declared inside the <resources> tag. + * <p> + * Acts a static factory class containing the general xml parsing logic for resources that are + * declared inside the <resources> tag. */ public class XmlResourceValues { @@ -120,10 +126,46 @@ public class XmlResourceValues { return IdXmlResourceValue.of(); } - static XmlResourceValue parseSimple(XMLEventReader eventReader, ResourceType resourceType) + static XmlResourceValue parseSimple( + XMLEventReader eventReader, ResourceType resourceType, QName startTag) throws XMLStreamException { + StringBuilder contents = new StringBuilder(); + while (!isEndTag(eventReader.peek(), startTag)) { + XMLEvent xmlEvent = eventReader.nextEvent(); + if (xmlEvent.isCharacters()) { + contents.append(xmlEvent.asCharacters().getData()); + } else if (xmlEvent.isStartElement()) { + QName name = xmlEvent.asStartElement().getName(); + contents.append("<"); + if (!name.getNamespaceURI().isEmpty()) { + contents + .append(name.getPrefix()) + .append(':') + .append(name.getLocalPart()) + .append(' ') + .append("xmlns:") + .append(name.getPrefix()) + .append("='") + .append(name.getNamespaceURI()) + .append("'"); + } else { + contents.append(name.getLocalPart()); + } + contents.append(">"); + } else if (xmlEvent.isEndElement()) { + QName name = xmlEvent.asEndElement().getName(); + contents.append("</"); + if (!name.getNamespaceURI().isEmpty()) { + contents.append(name.getPrefix()).append(':').append(name.getLocalPart()); + } else { + contents.append(name.getLocalPart()); + } + contents.append(">"); + } + } + Preconditions.checkArgument(eventReader.nextEvent().asEndElement().getName().equals(startTag)); return SimpleXmlResourceValue.of( - SimpleXmlResourceValue.Type.from(resourceType), eventReader.getElementText()); + SimpleXmlResourceValue.Type.from(resourceType), contents.toString()); } /* XML helper methods follow. */ @@ -209,4 +251,17 @@ public class XmlResourceValues { } return false; } + + public static SerializeFormat.DataValue.Builder newProtoDataBuilder(Path source) { + SerializeFormat.DataValue.Builder builder = SerializeFormat.DataValue.newBuilder(); + return builder.setSource(builder.getSourceBuilder().setFilename(source.toString())); + } + + public static int serializeProtoDataValue( + OutputStream output, SerializeFormat.DataValue.Builder builder) throws IOException { + SerializeFormat.DataValue value = builder.build(); + value.writeDelimitedTo(output); + return CodedOutputStream.computeUInt32SizeNoTag(value.getSerializedSize()) + + value.getSerializedSize(); + } } 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 2e1a10330f..edb30267f7 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 @@ -22,7 +22,10 @@ import com.google.devtools.build.android.AndroidDataWritingVisitor; 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; import java.util.ArrayList; import java.util.Arrays; @@ -42,12 +45,12 @@ import javax.xml.stream.events.XMLEvent; * * There are two flavors of Android Resource arrays: * <ul> - * <li>Typed arrays (http://developer.android.com/guide/topics/resources/more-resources - * .html#TypedArray) which which are indicated by a <array> tag.</li> - * <li>Integer array (http://developer.android.com/guide/topics/resources/more-resources - * .html#IntegerArray) which are indicated by <integer-array> tag.</li> - * <li>String array (http://developer.android.com/guide/topics/resources/string-resource - * .html#StringArray) which are indicated by <string-array> tag.</li> + * <li>Typed arrays (http://developer.android.com/guide/topics/resources/more-resources + * .html#TypedArray) which which are indicated by a <array> tag.</li> + * <li>Integer array (http://developer.android.com/guide/topics/resources/more-resources + * .html#IntegerArray) which are indicated by <integer-array> tag.</li> + * <li>String array (http://developer.android.com/guide/topics/resources/string-resource + * .html#StringArray) which are indicated by <string-array> tag.</li> * </ul> * * Both of these are accessed by R.array.<name> in java. @@ -117,6 +120,10 @@ public class ArrayXmlResourceValue implements XmlResourceValue { return new ArrayXmlResourceValue(arrayType, ImmutableList.copyOf(values)); } + public static XmlResourceValue from(SerializeFormat.DataValueXml proto) { + return of(ArrayType.valueOf(proto.getValueType()), proto.getListValueList()); + } + @Override public void write( FullyQualifiedName key, Path source, AndroidDataWritingVisitor mergedDataWriter) { @@ -129,6 +136,18 @@ public class ArrayXmlResourceValue implements XmlResourceValue { } @Override + public int serializeTo(Path source, OutputStream output) throws IOException { + return XmlResourceValues.serializeProtoDataValue( + output, + XmlResourceValues.newProtoDataBuilder(source) + .setXmlValue( + SerializeFormat.DataValueXml.newBuilder() + .addAllListValue(values) + .setType(SerializeFormat.DataValueXml.XmlType.ARRAY) + .setValueType(arrayType.toString()))); + } + + @Override public int hashCode() { return Objects.hash(arrayType, values); } 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 d245294cb4..f8273b37e6 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 @@ -27,7 +27,11 @@ import com.google.devtools.build.android.AndroidDataWritingVisitor; import com.google.devtools.build.android.FullyQualifiedName; import com.google.devtools.build.android.XmlResourceValue; import com.google.devtools.build.android.XmlResourceValues; +import com.google.devtools.build.android.proto.SerializeFormat; +import com.google.protobuf.InvalidProtocolBufferException; +import java.io.IOException; +import java.io.OutputStream; import java.nio.file.Path; import java.util.Arrays; import java.util.Collections; @@ -49,22 +53,24 @@ import javax.xml.stream.events.XMLEvent; /** * Represents an Android Resource custom attribute. * - * <p>Attribute are the most complicated Android resource, and therefore the least documented. Most - * of the information about them is found by reading the android compatibility library source. An + * <p> + * Attribute are the most complicated Android resource, and therefore the least documented. Most of + * the information about them is found by reading the android compatibility library source. An * Attribute defines a parameter that can be passed into a view class -- as such you can think of * attributes as creating slots for other resources to fit into. Each slot will have at least one - * format, and can have multiples. Simple attributes (color, boolean, reference, dimension, - * float, integer, string, and fraction) are defined as <attr name="<em>name</em>" - * format="<em>format</em>" /> while the complex ones, flag and enum, have sub tags: - * <attr name="<em>name</em>" ><flag name="<em>name</em>" value="<em>value</em>"> - * </attr>. + * format, and can have multiples. Simple attributes (color, boolean, reference, dimension, float, + * integer, string, and fraction) are defined as <attr name="<em>name</em>" format= + * "<em>format</em>" /> while the complex ones, flag and enum, have sub tags: <attr name= + * "<em>name</em>" ><flag name="<em>name</em>" value="<em>value</em>"> </attr>. * - * <p>Attributes also have a double duty as defining validation logic for layout resources -- - * each layout attribute *must* have a corresponding attribute which will be used to validate the + * <p> + * Attributes also have a double duty as defining validation logic for layout resources -- each + * layout attribute *must* have a corresponding attribute which will be used to validate the * value/resource reference defined in it. * - * <p>AttrXmlValue, due to the multiple types of attributes is actually a composite class that - * contains multiple {@link XmlResourceValue} instances for each resource. + * <p> + * AttrXmlValue, due to the multiple types of attributes is actually a composite class that contains + * multiple {@link XmlResourceValue} instances for each resource. */ @Immutable public class AttrXmlResourceValue implements XmlResourceValue { @@ -143,6 +149,51 @@ public class AttrXmlResourceValue implements XmlResourceValue { return of(ImmutableMap.copyOf(Arrays.asList(entries))); } + public static XmlResourceValue from(SerializeFormat.DataValueXml proto) + throws InvalidProtocolBufferException { + Builder<String, ResourceXmlAttrValue> formats = + ImmutableMap.<String, AttrXmlResourceValue.ResourceXmlAttrValue>builder(); + for (Entry<String, SerializeFormat.DataValueXml> entry : proto.getMappedXmlValue().entrySet()) { + switch (entry.getKey()) { + case FLAG: + formats.put( + entry.getKey(), FlagResourceXmlAttrValue.of(entry.getValue().getMappedStringValue())); + break; + case ENUM: + formats.put( + entry.getKey(), EnumResourceXmlAttrValue.of(entry.getValue().getMappedStringValue())); + break; + case REFERENCE: + formats.put(entry.getKey(), ReferenceResourceXmlAttrValue.of()); + break; + case COLOR: + formats.put(entry.getKey(), ColorResourceXmlAttrValue.of()); + break; + case BOOLEAN: + formats.put(entry.getKey(), BooleanResourceXmlAttrValue.of()); + break; + case DIMENSION: + formats.put(entry.getKey(), DimensionResourceXmlAttrValue.of()); + break; + case FLOAT: + formats.put(entry.getKey(), FloatResourceXmlAttrValue.of()); + break; + case INTEGER: + formats.put(entry.getKey(), IntegerResourceXmlAttrValue.of()); + break; + case STRING: + formats.put(entry.getKey(), StringResourceXmlAttrValue.of()); + break; + case FRACTION: + formats.put(entry.getKey(), FractionResourceXmlAttrValue.of()); + break; + default: + throw new InvalidProtocolBufferException("Unexpected format: " + entry.getKey()); + } + } + return of(formats.build()); + } + public static XmlResourceValue from( StartElement attr, @Nullable String format, XMLEventReader eventReader) throws XMLStreamException { @@ -242,9 +293,26 @@ public class AttrXmlResourceValue implements XmlResourceValue { mergedDataWriter.writeToValuesXml(key, iterable.append("</attr>")); } + @Override + public int serializeTo(Path source, OutputStream output) throws IOException { + SerializeFormat.DataValue.Builder builder = XmlResourceValues.newProtoDataBuilder(source); + SerializeFormat.DataValueXml.Builder xmlValueBuilder = + SerializeFormat.DataValueXml.newBuilder(); + xmlValueBuilder.setType(SerializeFormat.DataValueXml.XmlType.ATTR); + for (Entry<String, ResourceXmlAttrValue> entry : formats.entrySet()) { + xmlValueBuilder + .getMutableMappedXmlValue() + .put(entry.getKey(), entry.getValue().appendTo(builder.getXmlValueBuilder())); + } + builder.setXmlValue(xmlValueBuilder); + return XmlResourceValues.serializeProtoDataValue(output, builder); + } + @CheckReturnValue interface ResourceXmlAttrValue { FluentIterable<String> appendTo(FluentIterable<String> iterable); + + SerializeFormat.DataValueXml appendTo(SerializeFormat.DataValueXml.Builder builder); } // TODO(corysmith): The ResourceXmlAttrValue implementors, other than enum and flag, share a @@ -305,6 +373,11 @@ public class AttrXmlResourceValue implements XmlResourceValue { public FluentIterable<String> appendTo(FluentIterable<String> iterable) { return iterable.append(FluentIterable.from(values.entrySet()).transform(MAP_TO_ENUM)); } + + @Override + public SerializeFormat.DataValueXml appendTo(SerializeFormat.DataValueXml.Builder builder) { + return builder.putAllMappedStringValue(values).build(); + } } /** Represents an Android Flag Attribute resource. */ @@ -366,6 +439,11 @@ public class AttrXmlResourceValue implements XmlResourceValue { } })); } + + @Override + public SerializeFormat.DataValueXml appendTo(SerializeFormat.DataValueXml.Builder builder) { + return builder.putAllMappedStringValue(values).build(); + } } /** Represents an Android Reference Attribute resource. */ @@ -387,6 +465,11 @@ public class AttrXmlResourceValue implements XmlResourceValue { public FluentIterable<String> appendTo(FluentIterable<String> iterable) { return iterable; } + + @Override + public SerializeFormat.DataValueXml appendTo(SerializeFormat.DataValueXml.Builder builder) { + return builder.build(); + } } /** Represents an Android Color Attribute resource. */ @@ -407,6 +490,11 @@ public class AttrXmlResourceValue implements XmlResourceValue { public FluentIterable<String> appendTo(FluentIterable<String> iterable) { return iterable; } + + @Override + public SerializeFormat.DataValueXml appendTo(SerializeFormat.DataValueXml.Builder builder) { + return builder.build(); + } } /** Represents an Android Boolean Attribute resource. */ @@ -427,6 +515,11 @@ public class AttrXmlResourceValue implements XmlResourceValue { public FluentIterable<String> appendTo(FluentIterable<String> iterable) { return iterable; } + + @Override + public SerializeFormat.DataValueXml appendTo(SerializeFormat.DataValueXml.Builder builder) { + return builder.build(); + } } /** Represents an Android Float Attribute resource. */ @@ -447,6 +540,11 @@ public class AttrXmlResourceValue implements XmlResourceValue { public FluentIterable<String> appendTo(FluentIterable<String> iterable) { return iterable; } + + @Override + public SerializeFormat.DataValueXml appendTo(SerializeFormat.DataValueXml.Builder builder) { + return builder.build(); + } } /** Represents an Android Dimension Attribute resource. */ @@ -468,6 +566,11 @@ public class AttrXmlResourceValue implements XmlResourceValue { public FluentIterable<String> appendTo(FluentIterable<String> iterable) { return iterable; } + + @Override + public SerializeFormat.DataValueXml appendTo(SerializeFormat.DataValueXml.Builder builder) { + return builder.build(); + } } /** Represents an Android Integer Attribute resource. */ @@ -488,6 +591,11 @@ public class AttrXmlResourceValue implements XmlResourceValue { public FluentIterable<String> appendTo(FluentIterable<String> iterable) { return iterable; } + + @Override + public SerializeFormat.DataValueXml appendTo(SerializeFormat.DataValueXml.Builder builder) { + return builder.build(); + } } /** Represents an Android String Attribute resource. */ @@ -508,6 +616,11 @@ public class AttrXmlResourceValue implements XmlResourceValue { public FluentIterable<String> appendTo(FluentIterable<String> iterable) { return iterable; } + + @Override + public SerializeFormat.DataValueXml appendTo(SerializeFormat.DataValueXml.Builder builder) { + return builder.build(); + } } /** Represents an Android Fraction Attribute resource. */ @@ -528,5 +641,10 @@ public class AttrXmlResourceValue implements XmlResourceValue { public FluentIterable<String> appendTo(FluentIterable<String> iterable) { return iterable; } + + @Override + public SerializeFormat.DataValueXml appendTo(SerializeFormat.DataValueXml.Builder builder) { + return builder.build(); + } } } 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 2938256af4..07e3ad5940 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 @@ -18,7 +18,12 @@ import com.google.common.collect.ImmutableList; import com.google.devtools.build.android.AndroidDataWritingVisitor; import com.google.devtools.build.android.FullyQualifiedName; import com.google.devtools.build.android.XmlResourceValue; +import com.google.devtools.build.android.XmlResourceValues; +import com.google.devtools.build.android.proto.SerializeFormat; +import com.google.protobuf.CodedOutputStream; +import java.io.IOException; +import java.io.OutputStream; import java.nio.file.Path; import javax.annotation.concurrent.Immutable; @@ -26,11 +31,12 @@ import javax.annotation.concurrent.Immutable; /** * Represents an Android Resource id. * - * <p>Ids (http://developer.android.com/guide/topics/resources/more-resources.html#Id) are - * special -- unlike other resources they cannot be overridden. This is due to the - * nature of their usage. Each id corresponds to context sensitive resource of component, meaning - * that they have no intrinsic defined value. They exist to reference parts of other resources. - * Ids can also be declared on the fly in components with the syntax @[+][package:]id/resource_name. + * <p> + * Ids (http://developer.android.com/guide/topics/resources/more-resources.html#Id) are special -- + * unlike other resources they cannot be overridden. This is due to the nature of their usage. Each + * id corresponds to context sensitive resource of component, meaning that they have no intrinsic + * defined value. They exist to reference parts of other resources. Ids can also be declared on the + * fly in components with the syntax @[+][package:]id/resource_name. */ @Immutable public class IdXmlResourceValue implements XmlResourceValue { @@ -52,6 +58,19 @@ public class IdXmlResourceValue implements XmlResourceValue { } @Override + public int serializeTo(Path source, OutputStream output) throws IOException { + SerializeFormat.DataValue value = + XmlResourceValues.newProtoDataBuilder(source) + .setXmlValue( + SerializeFormat.DataValueXml.newBuilder() + .setType(SerializeFormat.DataValueXml.XmlType.ID)) + .build(); + value.writeDelimitedTo(output); + return CodedOutputStream.computeUInt32SizeNoTag(value.getSerializedSize()) + + value.getSerializedSize(); + } + + @Override public String toString() { return MoreObjects.toStringHelper(getClass()).toString(); } 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 e47b5611c0..64397ef35f 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 @@ -21,7 +21,14 @@ import com.google.common.collect.ImmutableMap; import com.google.devtools.build.android.AndroidDataWritingVisitor; 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.DataValue.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.Map.Entry; import java.util.Objects; @@ -32,10 +39,10 @@ import javax.annotation.concurrent.Immutable; /** * Represents an Android Plural Resource. * - * <p>Plurals are a localization construct (http://developer.android.com/guide/topics/resources/ - * string-resource.html#Plurals) that are basically a map of key to - * value. They are defined in xml as: - * <code> + * <p> + * Plurals are a localization construct (http://developer.android.com/guide/topics/resources/ + * string-resource.html#Plurals) that are basically a map of key to value. They are defined in xml + * as: <code> * <plurals name="plural_name"> * <item quantity=["zero" | "one" | "two" | "few" | "many" | "other"]> * text_string @@ -95,4 +102,24 @@ public class PluralXmlResourceValue implements XmlResourceValue { public String toString() { return MoreObjects.toStringHelper(getClass()).add("values", values).toString(); } + + public static XmlResourceValue from(SerializeFormat.DataValueXml proto) { + return of(ImmutableMap.copyOf(proto.getMappedStringValue())); + } + + @Override + public int serializeTo(Path source, OutputStream output) throws IOException { + Builder builder = XmlResourceValues.newProtoDataBuilder(source); + SerializeFormat.DataValue value = + builder + .setXmlValue( + builder + .getXmlValueBuilder() + .setType(XmlType.PLURAL) + .putAllMappedStringValue(values)) + .build(); + value.writeDelimitedTo(output); + return CodedOutputStream.computeUInt32SizeNoTag(value.getSerializedSize()) + + value.getSerializedSize(); + } } 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 dadba36c9f..ff8c6ad742 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 @@ -18,9 +18,13 @@ import com.google.common.collect.ImmutableList; import com.google.devtools.build.android.AndroidDataWritingVisitor; 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.android.resources.ResourceType; +import java.io.IOException; +import java.io.OutputStream; import java.nio.file.Path; import java.util.Arrays; import java.util.Objects; @@ -31,14 +35,14 @@ import javax.xml.namespace.QName; /** * Represents a simple Android resource xml value. * - * <p>There is a class of resources that are simple name/value pairs: - * string (http://developer.android.com/guide/topics/resources/string-resource.html), - * bool (http://developer.android.com/guide/topics/resources/more-resources.html#Bool), - * color (http://developer.android.com/guide/topics/resources/more-resources.html#Color), and - * dimen (http://developer.android.com/guide/topics/resources/more-resources.html#Dimension). - * These are defined in xml as <<em>resource type</em> name="<em>name</em>" - * value="<em>value</em>">. In the interest of keeping the parsing svelte, these are - * represented by a single class. + * <p> + * There is a class of resources that are simple name/value pairs: string + * (http://developer.android.com/guide/topics/resources/string-resource.html), bool + * (http://developer.android.com/guide/topics/resources/more-resources.html#Bool), color + * (http://developer.android.com/guide/topics/resources/more-resources.html#Color), and dimen + * (http://developer.android.com/guide/topics/resources/more-resources.html#Dimension). These are + * defined in xml as <<em>resource type</em> name="<em>name</em>" value="<em>value</em>">. In + * the interest of keeping the parsing svelte, these are represented by a single class. */ @Immutable public class SimpleXmlResourceValue implements XmlResourceValue { @@ -125,6 +129,22 @@ public class SimpleXmlResourceValue implements XmlResourceValue { valueType.tagName.getLocalPart()))); } + public static XmlResourceValue from(SerializeFormat.DataValueXml proto) { + return of(Type.valueOf(proto.getValueType()), proto.getValue()); + } + + @Override + public int serializeTo(Path source, OutputStream output) throws IOException { + SerializeFormat.DataValue.Builder builder = XmlResourceValues.newProtoDataBuilder(source); + builder.setXmlValue( + builder + .getXmlValueBuilder() + .setType(SerializeFormat.DataValueXml.XmlType.SIMPLE) + .setValue(value) + .setValueType(valueType.name())); + return XmlResourceValues.serializeProtoDataValue(output, builder); + } + @Override public int hashCode() { return Objects.hash(valueType, value); 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 ce11bbbda1..cb73d07cd1 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 @@ -21,7 +21,11 @@ import com.google.common.collect.ImmutableMap; import com.google.devtools.build.android.AndroidDataWritingVisitor; 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; import java.util.Map; import java.util.Map.Entry; @@ -33,12 +37,12 @@ import javax.annotation.concurrent.Immutable; /** * Represents an Android Style Resource. * - * <p>Styles (http://developer.android.com/guide/topics/resources/style-resource.html) define a - * look and feel for a layout or other ui construct. They are effectively a s set of values that + * <p> + * Styles (http://developer.android.com/guide/topics/resources/style-resource.html) define a look + * and feel for a layout or other ui construct. They are effectively a s set of values that * correspond to <attr> resources defined either in the base android framework or in other * resources. They also allow inheritance on other styles. For a style to valid in a given resource - * pass, they must only contain definer attributes with acceptable values. - * <code> + * pass, they must only contain definer attributes with acceptable values. <code> * <resources> * <style name="CustomText" parent="@style/Text"> * <item name="android:textSize">20sp</item> @@ -64,7 +68,11 @@ public class StyleXmlResourceValue implements XmlResourceValue { return new StyleXmlResourceValue(parent, ImmutableMap.copyOf(values)); } - private StyleXmlResourceValue(String parent, ImmutableMap<String, String> values) { + public static XmlResourceValue from(SerializeFormat.DataValueXml proto) { + return of(proto.hasValue() ? proto.getValue() : null, proto.getMappedStringValue()); + } + + private StyleXmlResourceValue(@Nullable String parent, ImmutableMap<String, String> values) { this.parent = parent; this.values = values; } @@ -79,12 +87,25 @@ public class StyleXmlResourceValue implements XmlResourceValue { String.format("<!-- %s -->", source), parent == null || parent.isEmpty() ? String.format("<style name='%s'>", key.name()) - : String.format("<style name='%s' parent='%s'>", key.name(), parent))) + : String.format("<style name='%s' parent='@%s'>", key.name(), parent))) .append(FluentIterable.from(values.entrySet()).transform(ENTRY_TO_ITEM)) .append("</style>")); } @Override + public int serializeTo(Path source, OutputStream output) throws IOException { + SerializeFormat.DataValueXml.Builder xmlValueBuilder = + SerializeFormat.DataValueXml.newBuilder() + .setType(SerializeFormat.DataValueXml.XmlType.STYLE) + .putAllMappedStringValue(values); + if (parent != null && !parent.isEmpty()) { + xmlValueBuilder.setValue(parent); + } + return XmlResourceValues.serializeProtoDataValue( + output, XmlResourceValues.newProtoDataBuilder(source).setXmlValue(xmlValueBuilder)); + } + + @Override public int hashCode() { return Objects.hash(parent, values); } 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 4f8fadc081..e46eaf3065 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 @@ -22,7 +22,11 @@ import com.google.common.collect.Ordering; import com.google.devtools.build.android.AndroidDataWritingVisitor; 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; import java.util.Arrays; import java.util.List; @@ -34,25 +38,25 @@ import javax.annotation.concurrent.Immutable; /** * Represent an Android styleable resource. * - * <p>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; - * <code> + * <p> + * 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; <code> * <declare-styleable name="PieChart"> * <attr name="showText" format="boolean" /> * </declare-styleable> * </code> * - * Can also be seen as: - * <code> + * Can also be seen as: <code> * <attr name="showText" format="boolean" /> * <declare-styleable name="PieChart"> * <attr name="showText"/> * </declare-styleable> * </code> * - * <p>The StyleableXmlValue only contains names of the attributes it holds, not definitions. + * <p> + * The StyleableXmlValue only contains names of the attributes it holds, not definitions. */ @Immutable public class StyleableXmlResourceValue implements XmlResourceValue { @@ -94,6 +98,21 @@ public class StyleableXmlResourceValue implements XmlResourceValue { } @Override + public int serializeTo(Path source, OutputStream output) throws IOException { + return XmlResourceValues.serializeProtoDataValue( + output, + XmlResourceValues.newProtoDataBuilder(source) + .setXmlValue( + SerializeFormat.DataValueXml.newBuilder() + .setType(SerializeFormat.DataValueXml.XmlType.STYLEABLE) + .addAllListValue(attrs))); + } + + public static XmlResourceValue from(SerializeFormat.DataValueXml proto) { + return of(proto.getListValueList()); + } + + @Override public int hashCode() { return attrs.hashCode(); } |