diff options
Diffstat (limited to 'src/tools/android/java/com/google/devtools')
19 files changed, 597 insertions, 202 deletions
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 new file mode 100644 index 0000000000..b954aff3bc --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/AndroidDataWriter.java @@ -0,0 +1,110 @@ +// 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 static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.common.collect.Ordering; + +import java.io.Flushable; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.HashMap; +import java.util.Map; + +/** + * Writer for UnwrittenMergedAndroidData. + */ +// TODO(corysmith): Profile this class on large datasets and look for bottlenecks, as this class +// does all the IO. +public class AndroidDataWriter implements Flushable, AndroidDataWritingVisitor { + private final Path destination; + private final Map<FullyQualifiedName, Iterable<String>> valueFragments = new HashMap<>(); + private Path resourceDirectory; + private Path assetDirectory; + + private AndroidDataWriter(Path destination, Path resourceDirectory, Path assetsDirectory) { + this.destination = destination; + this.resourceDirectory = resourceDirectory; + this.assetDirectory = assetsDirectory; + } + + public static AndroidDataWriter from(Path destination) { + return new AndroidDataWriter( + destination, destination.resolve("res"), destination.resolve("assets")); + } + + @Override + public Path copyManifest(Path sourceManifest) throws IOException { + // aapt won't read any manifest that is not named AndroidManifest.xml, + // so we hard code it here. + Path destinationManifest = destination.resolve("AndroidManifest.xml"); + copy(sourceManifest, destinationManifest); + return destinationManifest; + } + + public Path assetDirectory() { + return assetDirectory; + } + + public Path resourceDirectory() { + return resourceDirectory; + } + + @Override + public void copyAsset(Path source, String relativeDestinationPath) throws IOException { + copy(source, assetDirectory.resolve(relativeDestinationPath)); + } + + @Override + public void copyResource(Path source, String relativeDestinationPath) throws IOException { + copy(source, resourceDirectory.resolve(relativeDestinationPath)); + } + + private void copy(Path sourcePath, Path destinationPath) throws IOException { + Files.createDirectories(destinationPath.getParent()); + Files.copy(sourcePath, destinationPath); + } + + /** + * Finalizes all operations and flushes the buffers. + */ + @Override + 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)) { + channel.write(ByteBuffer.wrap("<resources>".getBytes(UTF_8))); + for (FullyQualifiedName key : Ordering.natural().sortedCopy(valueFragments.keySet())) { + for (String line : valueFragments.get(key)) { + channel.write(ByteBuffer.wrap(line.getBytes(UTF_8))); + } + } + channel.write(ByteBuffer.wrap("</resources>".getBytes(UTF_8))); + } + valueFragments.clear(); + } + + @Override + public void writeToValuesXml(FullyQualifiedName key, Iterable<String> xmlFragment) { + valueFragments.put(key, xmlFragment); + } +} 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 new file mode 100644 index 0000000000..40abe2f184 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/AndroidDataWritingVisitor.java @@ -0,0 +1,51 @@ +// 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 java.io.IOException; +import java.nio.file.Path; + +/** + * An interface for visiting android data for writing. + */ +public interface AndroidDataWritingVisitor { + /** + * Copies the AndroidManifest to the destination directory. + */ + Path copyManifest(Path sourceManifest) throws IOException; + + /** + * 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. + */ + void copyAsset(Path source, String relativeDestinationPath) throws IOException; + + /** + * 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. + */ + void copyResource(Path source, String relativeDestinationPath) throws IOException; + + /** + * Adds a xml string fragment to the values file. + * + * @param key Used to ensure a constant order of the written xml. + * @param xmlFragment the xml fragment as an Iterable<String> which allows lazy generation. + */ + void writeToValuesXml(FullyQualifiedName key, Iterable<String> xmlFragment); +} diff --git a/src/tools/android/java/com/google/devtools/build/android/DataAsset.java b/src/tools/android/java/com/google/devtools/build/android/DataAsset.java index 08dcab4eb8..cbb2f77101 100644 --- a/src/tools/android/java/com/google/devtools/build/android/DataAsset.java +++ b/src/tools/android/java/com/google/devtools/build/android/DataAsset.java @@ -14,17 +14,14 @@ package com.google.devtools.build.android; import java.io.IOException; -import java.nio.file.Path; /** * Represents an Asset created from a binary file. */ public interface DataAsset extends DataValue { - /** - * Writes the asset to the given asset directory. - * @param newAssetDirectory The new directory for this asset. - * @throws IOException if there are issues with writing the asset. + * Write the asset value to mergedDataWriter. */ - void write(Path newAssetDirectory) throws IOException; + void writeAsset(RelativeAssetPath key, AndroidDataWritingVisitor mergedDataWriter) + throws IOException; } diff --git a/src/tools/android/java/com/google/devtools/build/android/DataResource.java b/src/tools/android/java/com/google/devtools/build/android/DataResource.java index 06bd6416af..98fef179ba 100644 --- a/src/tools/android/java/com/google/devtools/build/android/DataResource.java +++ b/src/tools/android/java/com/google/devtools/build/android/DataResource.java @@ -14,16 +14,14 @@ package com.google.devtools.build.android; import java.io.IOException; -import java.nio.file.Path; /** * Represents an Android Resource parsed from an xml or binary file. */ public interface DataResource extends DataValue { /** - * Writes the resource to the given resource directory. - * @param newResourceDirectory The new directory for this resource. - * @throws IOException if there are issues with writing the resource. + * Write as a resource using the supplied {@link MergeDataWriter}. */ - void write(Path newResourceDirectory) throws IOException; + void writeResource(FullyQualifiedName key, AndroidDataWritingVisitor mergedDataWriter) + 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 ac5b392800..21e8c92657 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 @@ -58,7 +58,7 @@ public class DataResourceXml implements DataResource { * @throws FactoryConfigurationError Thrown with the {@link XMLInputFactory} is misconfigured. * @throws IOException Thrown when there is an error reading a file. */ - public static void fromPath( + public static void parse( XMLInputFactory xmlInputFactory, Path path, Factory fqnFactory, @@ -160,8 +160,7 @@ public class DataResourceXml implements DataResource { } @Override - public void write(Path newResourceDirectory) throws IOException { - // TODO(corysmith): Implement write. - throw new UnsupportedOperationException(); + public void writeResource(FullyQualifiedName key, AndroidDataWritingVisitor mergedDataWriter) { + xml.write(key, source, mergedDataWriter); } } 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 4ee0a7d91d..b92002f783 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 @@ -61,8 +61,23 @@ public class DataValueFile implements DataResource, DataAsset { } @Override - public void write(Path newResourceDirectory) throws IOException { - // TODO(corysmith): Implement the copy semantics. - throw new UnsupportedOperationException(); + public void writeAsset(RelativeAssetPath key, AndroidDataWritingVisitor mergedDataWriter) + throws IOException { + mergedDataWriter.copyAsset(source, key.toPathString()); + } + + @Override + public void writeResource(FullyQualifiedName key, AndroidDataWritingVisitor mergedDataWriter) + throws IOException { + mergedDataWriter.copyResource(source, key.toPathString(getSourceExtension())); + } + + private String getSourceExtension() { + String fileName = source.getFileName().toString(); + int extensionStart = fileName.lastIndexOf('.'); + if (extensionStart > 0) { + return fileName.substring(extensionStart); + } + return ""; } } 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 ccdc6617a4..303196cbfa 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 @@ -21,6 +21,7 @@ import com.google.common.collect.Ordering; import com.android.resources.ResourceType; +import java.nio.file.Paths; import java.util.List; import java.util.Objects; import java.util.regex.Matcher; @@ -44,6 +45,32 @@ public class FullyQualifiedName implements DataKey, Comparable<FullyQualifiedNam private final String resourceName; /** + * 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] + * + * @param sourceExtension The extension of the resource represented by the FullyQualifiedName + * @return A string representation of the FullyQualifiedName with the provided extension. + */ + 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()), + resourceName + sourceExtension) + .toString(); + } + + public String name() { + return resourceName; + } + + /** * A factory for parsing an generating FullyQualified names with qualifiers and package. */ public static class Factory { diff --git a/src/tools/android/java/com/google/devtools/build/android/ParsedAndroidData.java b/src/tools/android/java/com/google/devtools/build/android/ParsedAndroidData.java index cc4311a209..50c62e9001 100644 --- a/src/tools/android/java/com/google/devtools/build/android/ParsedAndroidData.java +++ b/src/tools/android/java/com/google/devtools/build/android/ParsedAndroidData.java @@ -18,6 +18,7 @@ import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; import com.android.ide.common.res2.MergingException; @@ -34,6 +35,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; import java.util.Set; @@ -50,8 +52,7 @@ import javax.xml.stream.XMLStreamException; */ @Immutable public class ParsedAndroidData { - - /** A Consumer style interface that will accept a DataKey and DataValue. */ + /** A Consumer style interface that will appendTo a DataKey and DataValue. */ interface KeyValueConsumer<K extends DataKey, V extends DataValue> { void consume(K key, V value); } @@ -253,7 +254,7 @@ public class ParsedAndroidData { try { if (!Files.isDirectory(path)) { if (inValuesSubtree) { - DataResourceXml.fromPath( + DataResourceXml.parse( xmlInputFactory, path, fqnFactory, overwritingConsumer, nonOverwritingConsumer); } else { String rawFqn = deriveRawFullyQualifiedName(path); @@ -412,15 +413,19 @@ public class ParsedAndroidData { return overwritingResources.containsKey(name); } - Iterable<Map.Entry<DataKey, DataResource>> iterateOverwritableEntries() { + Iterable<Entry<DataKey, DataResource>> iterateOverwritableEntries() { return overwritingResources.entrySet(); } + public Iterable<Entry<DataKey, DataResource>> iterateDataResourceEntries() { + return Iterables.concat(overwritingResources.entrySet(), nonOverwritingResources.entrySet()); + } + boolean containsAsset(DataKey name) { return assets.containsKey(name); } - Iterable<Map.Entry<DataKey, DataAsset>> iterateAssetEntries() { + Iterable<Entry<DataKey, DataAsset>> iterateAssetEntries() { return assets.entrySet(); } 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 83b31fd081..609216ff64 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 @@ -29,7 +29,6 @@ import java.util.Objects; * Note: Assets have no qualifiers or packages. */ public class RelativeAssetPath implements DataKey, Comparable<RelativeAssetPath> { - /** * A Factory that creates RelativeAssetsPath objects whose paths are relative to a given path. */ @@ -78,6 +77,10 @@ public class RelativeAssetPath implements DataKey, Comparable<RelativeAssetPath> return Objects.equals(relativeAssetPath, that.relativeAssetPath); } + public String toPathString() { + return this.relativeAssetPath.toString(); + } + @Override public int hashCode() { return relativeAssetPath.hashCode(); 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 30cb43f289..633bd9403a 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 @@ -18,6 +18,7 @@ import com.google.common.base.MoreObjects; import java.io.IOException; import java.nio.file.Path; +import java.util.Map.Entry; import java.util.Objects; /** @@ -26,7 +27,7 @@ import java.util.Objects; public class UnwrittenMergedAndroidData { private final Path manifest; - private final ParsedAndroidData resources; + private final ParsedAndroidData primary; private final ParsedAndroidData deps; public static UnwrittenMergedAndroidData of( @@ -35,28 +36,54 @@ public class UnwrittenMergedAndroidData { } private UnwrittenMergedAndroidData( - Path manifest, ParsedAndroidData resources, ParsedAndroidData deps) { + Path manifest, ParsedAndroidData primary, ParsedAndroidData deps) { this.manifest = manifest; - this.resources = resources; + this.primary = primary; this.deps = deps; } /** - * Writes the android data to directories for consumption by aapt. - * @param newResourceDirectory The new resource directory to write to. + * 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. */ - public MergedAndroidData write(Path newResourceDirectory) throws IOException { - // TODO(corysmith): Implement write. - throw new UnsupportedOperationException(); + public MergedAndroidData write(AndroidDataWriter mergedDataWriter) throws IOException { + try { + writeParsedAndroidData(primary, mergedDataWriter); + writeParsedAndroidData(deps, mergedDataWriter); + return new MergedAndroidData( + mergedDataWriter.resourceDirectory(), + mergedDataWriter.assetDirectory(), + mergedDataWriter.copyManifest(this.manifest)); + } finally { + // Flush to make sure all writing is completed before returning a MergedAndroidData. + // If resources aren't fully written, the MergedAndroidData might be invalid. + mergedDataWriter.flush(); + } + } + + private void writeParsedAndroidData( + ParsedAndroidData resources, AndroidDataWritingVisitor mergedDataWriter) throws IOException { + for (Entry<DataKey, DataAsset> entry : resources.iterateAssetEntries()) { + // TODO(corysmith): Resolve the nit of casting to a RelativeAssetPath by sorting + // out the type structure and generics of DataKey, ParsedAndroidData, AndroidDataMerger and + // MergeConflict. + entry.getValue().writeAsset((RelativeAssetPath) entry.getKey(), mergedDataWriter); + } + for (Entry<DataKey, DataResource> entry : resources.iterateDataResourceEntries()) { + // TODO(corysmith): Resolve the nit of casting to a FullyQualifiedName by sorting + // out the type structure and generics of DataKey, ParsedAndroidData, AndroidDataMerger and + // MergeConflict. + entry.getValue().writeResource((FullyQualifiedName) entry.getKey(), mergedDataWriter); + } } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("manifest", manifest) - .add("resources", resources) + .add("primary", primary) .add("deps", deps) .toString(); } @@ -71,13 +98,13 @@ public class UnwrittenMergedAndroidData { } UnwrittenMergedAndroidData that = (UnwrittenMergedAndroidData) other; return Objects.equals(manifest, that.manifest) - && Objects.equals(resources, that.resources) + && Objects.equals(primary, that.primary) && Objects.equals(deps, that.deps); } @Override public int hashCode() { - return Objects.hash(manifest, resources, deps); + return Objects.hash(manifest, primary, deps); } @VisibleForTesting @@ -86,8 +113,8 @@ public class UnwrittenMergedAndroidData { } @VisibleForTesting - ParsedAndroidData getResources() { - return resources; + ParsedAndroidData getPrimary() { + return primary; } @VisibleForTesting 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 2a22a5aa2f..1a3cfd1f66 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,16 +13,18 @@ // limitations under the License. package com.google.devtools.build.android; -import java.io.Writer; +import java.nio.file.Path; /** - * An XmlValue is a value extracted from an xml resource in the resource 'values' directory. + * An {@link XmlResourceValue} is extracted from xml files in the resource 'values' directory. */ public interface XmlResourceValue { /** - * Each XmlValue is expected to write a valid representation in xml to the supplied buffer. - * @param buffer The buffer for xml output. - * @param name The name of the value being written. + * Each XmlValue is expected to write a valid representation in xml to the writer. + * + * @param key The FullyQualified name for the xml resource being written. + * @param source The source of the value to allow for proper comment annotation. + * @param mergedDataWriter The target writer. */ - void write(Writer buffer, FullyQualifiedName name); + void write(FullyQualifiedName key, Path source, AndroidDataWritingVisitor mergedDataWriter); } 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 c686829385..f2525712ea 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,6 +13,7 @@ // limitations under the License. package com.google.devtools.build.android; +import com.google.common.collect.ImmutableMap; import com.google.devtools.build.android.ParsedAndroidData.KeyValueConsumer; import com.google.devtools.build.android.xml.AttrXmlResourceValue; import com.google.devtools.build.android.xml.IdXmlResourceValue; @@ -37,7 +38,7 @@ import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; /** - * XmlValues provides methods for getting XmlValue derived classes. + * {@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. @@ -58,7 +59,7 @@ public class XmlResourceValues { private static final QName ATTR_TYPE = QName.valueOf("type"); static XmlResourceValue parsePlurals(XMLEventReader eventReader) throws XMLStreamException { - Map<String, String> values = new HashMap<>(); + ImmutableMap.Builder<String, String> values = ImmutableMap.builder(); for (XMLEvent element = eventReader.nextTag(); !isEndTag(element, TAG_PLURALS); element = eventReader.nextTag()) { @@ -68,7 +69,7 @@ public class XmlResourceValues { eventReader.getElementText()); } } - return PluralXmlResourceValue.of(values); + return PluralXmlResourceValue.of(values.build()); } static XmlResourceValue parseStyle(XMLEventReader eventReader, StartElement start) 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 752e803c3e..329ae85405 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 @@ -14,17 +14,23 @@ package com.google.devtools.build.android.xml; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; import com.google.common.base.MoreObjects; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; +import com.google.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 java.io.Writer; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; import javax.xml.namespace.QName; import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLStreamException; @@ -40,20 +46,33 @@ import javax.xml.stream.events.XMLEvent; * .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. */ +@Immutable public class ArrayXmlResourceValue implements XmlResourceValue { private static final QName TAG_INTEGER_ARRAY = QName.valueOf("integer-array"); private static final QName TAG_ARRAY = QName.valueOf("array"); + private static final QName TAG_STRING_ARRAY = QName.valueOf("string-array"); + private static final Function<String, String> ITEM_TO_XML = + new Function<String, String>() { + @Nullable + @Override + public String apply(@Nullable String item) { + return String.format("<item>%s</item>", item); + } + }; /** * Enumerates the different types of array tags. */ public enum ArrayType { INTEGER_ARRAY(TAG_INTEGER_ARRAY), - ARRAY(TAG_ARRAY); + ARRAY(TAG_ARRAY), + STRING_ARRAY(TAG_STRING_ARRAY); public QName tagName; @@ -71,12 +90,20 @@ public class ArrayXmlResourceValue implements XmlResourceValue { throw new IllegalArgumentException( String.format("%s not found in %s", tagQName, Arrays.toString(values()))); } + + String openTag(FullyQualifiedName key) { + return String.format("<%s name='%s'>", tagName.getLocalPart(), key.name()); + } + + String closeTag() { + return String.format("</%s>", tagName.getLocalPart()); + } } - private final List<String> values; - private ArrayType arrayType; + private final ImmutableList<String> values; + private final ArrayType arrayType; - private ArrayXmlResourceValue(ArrayType arrayType, List<String> values) { + private ArrayXmlResourceValue(ArrayType arrayType, ImmutableList<String> values) { this.arrayType = arrayType; this.values = values; } @@ -87,13 +114,17 @@ public class ArrayXmlResourceValue implements XmlResourceValue { } public static XmlResourceValue of(ArrayType arrayType, List<String> values) { - return new ArrayXmlResourceValue(arrayType, values); + return new ArrayXmlResourceValue(arrayType, ImmutableList.copyOf(values)); } @Override - public void write(Writer buffer, FullyQualifiedName name) { - // TODO(corysmith): Implement writing xml. - throw new UnsupportedOperationException(); + public void write( + FullyQualifiedName key, Path source, AndroidDataWritingVisitor mergedDataWriter) { + mergedDataWriter.writeToValuesXml( + key, + FluentIterable.of(String.format("<!-- %s -->", source), arrayType.openTag(key)) + .append(FluentIterable.from(values).transform(ITEM_TO_XML)) + .append(arrayType.closeTag())); } @Override 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 52f44dbc6e..61dfdefd6a 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 @@ -14,24 +14,32 @@ package com.google.devtools.build.android.xml; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.base.Joiner; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap.Builder; +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 java.io.Writer; +import java.nio.file.Path; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; 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; import javax.xml.namespace.QName; import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLStreamException; @@ -55,9 +63,10 @@ import javax.xml.stream.events.XMLEvent; * 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 mutiple types of attributes is actually a composite class that - * contains multiple XmlValue 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 { private static final String FRACTION = "fraction"; @@ -72,19 +81,19 @@ public class AttrXmlResourceValue implements XmlResourceValue { private static final String FLAG = "flag"; private static final QName TAG_ENUM = QName.valueOf(ENUM); private static final QName TAG_FLAG = QName.valueOf(FLAG); - private final Map<String, XmlResourceValue> formats; + private final ImmutableMap<String, ResourceXmlAttrValue> formats; - private AttrXmlResourceValue(Map<String, XmlResourceValue> formats) { + private AttrXmlResourceValue(ImmutableMap<String, ResourceXmlAttrValue> formats) { this.formats = formats; } private static Map<String, String> readSubValues(XMLEventReader reader, QName subTagType) throws XMLStreamException { - Map<String, String> enumValue = new HashMap<>(); + Builder<String, String> builder = ImmutableMap.builder(); while (reader.hasNext() && XmlResourceValues.isTag(XmlResourceValues.peekNextTag(reader), subTagType)) { StartElement element = reader.nextEvent().asStartElement(); - enumValue.put( + builder.put( XmlResourceValues.getElementName(element), XmlResourceValues.getElementValue(element)); XMLEvent endTag = reader.nextEvent(); if (!XmlResourceValues.isEndTag(endTag, subTagType)) { @@ -92,7 +101,7 @@ public class AttrXmlResourceValue implements XmlResourceValue { String.format("Unexpected [%s]; Expected %s", endTag, "</enum>"), endTag.getLocation()); } } - return enumValue; + return builder.build(); } private static void endAttrElement(XMLEventReader reader) throws XMLStreamException { @@ -103,11 +112,11 @@ public class AttrXmlResourceValue implements XmlResourceValue { } @VisibleForTesting - private static final class BuilderEntry implements Map.Entry<String, XmlResourceValue> { + private static final class BuilderEntry implements Entry<String, ResourceXmlAttrValue> { private final String name; - private final XmlResourceValue value; + private final ResourceXmlAttrValue value; - public BuilderEntry(String name, XmlResourceValue value) { + BuilderEntry(String name, ResourceXmlAttrValue value) { this.name = name; this.value = value; } @@ -118,19 +127,19 @@ public class AttrXmlResourceValue implements XmlResourceValue { } @Override - public XmlResourceValue getValue() { + public ResourceXmlAttrValue getValue() { return value; } @Override - public XmlResourceValue setValue(XmlResourceValue value) { + public ResourceXmlAttrValue setValue(ResourceXmlAttrValue value) { throw new UnsupportedOperationException(); } } @SafeVarargs @VisibleForTesting - public static XmlResourceValue fromFormatEntries(Map.Entry<String, XmlResourceValue>... entries) { + public static XmlResourceValue fromFormatEntries(Entry<String, ResourceXmlAttrValue>... entries) { return of(ImmutableMap.copyOf(Arrays.asList(entries))); } @@ -146,52 +155,52 @@ public class AttrXmlResourceValue implements XmlResourceValue { formatNames.add(nextTag.asStartElement().getName().getLocalPart().toLowerCase()); } - Map<String, XmlResourceValue> formats = new HashMap<>(); + Builder<String, ResourceXmlAttrValue> formats = ImmutableMap.builder(); for (String formatName : formatNames) { switch (formatName) { case FLAG: Map<String, String> flags = readSubValues(eventReader, TAG_FLAG); endAttrElement(eventReader); - formats.put(formatName, AttrFlagXmlValue.of(flags)); + formats.put(formatName, FlagResourceXmlAttrValue.of(flags)); break; case ENUM: Map<String, String> enums = readSubValues(eventReader, TAG_ENUM); endAttrElement(eventReader); - formats.put(formatName, AttrEnumXmlValue.of(enums)); + formats.put(formatName, EnumResourceXmlAttrValue.of(enums)); break; case REFERENCE: - formats.put(formatName, AttrReferenceXmlValue.of()); + formats.put(formatName, ReferenceResourceXmlAttrValue.of()); break; case COLOR: - formats.put(formatName, AttrColorXmlValue.of()); + formats.put(formatName, ColorResourceXmlAttrValue.of()); break; case BOOLEAN: - formats.put(formatName, AttrBooleanXmlValue.of()); + formats.put(formatName, BooleanResourceXmlAttrValue.of()); break; case DIMENSION: - formats.put(formatName, AttrDimensionXmlValue.of()); + formats.put(formatName, DimensionResourceXmlAttrValue.of()); break; case FLOAT: - formats.put(formatName, AttrFloatXmlValue.of()); + formats.put(formatName, FloatResourceXmlAttrValue.of()); break; case INTEGER: - formats.put(formatName, AttrIntegerXmlValue.of()); + formats.put(formatName, IntegerResourceXmlAttrValue.of()); break; case STRING: - formats.put(formatName, AttrStringXmlValue.of()); + formats.put(formatName, StringResourceXmlAttrValue.of()); break; case FRACTION: - formats.put(formatName, AttrFractionXmlValue.of()); + formats.put(formatName, FractionResourceXmlAttrValue.of()); break; default: throw new XMLStreamException( String.format("Unexpected attr format: %S", formatName), attr.getLocation()); } } - return of(formats); + return of(formats.build()); } - public static XmlResourceValue of(Map<String, XmlResourceValue> formats) { + public static XmlResourceValue of(ImmutableMap<String, ResourceXmlAttrValue> formats) { return new AttrXmlResourceValue(formats); } @@ -218,40 +227,58 @@ public class AttrXmlResourceValue implements XmlResourceValue { } @Override - public void write(Writer buffer, FullyQualifiedName name) { - // TODO(corysmith): Implement write. - throw new UnsupportedOperationException(); + public void write( + FullyQualifiedName key, Path source, AndroidDataWritingVisitor mergedDataWriter) { + ImmutableList<String> formatKeys = Ordering.natural().immutableSortedCopy(formats.keySet()); + FluentIterable<String> iterable = + FluentIterable.of( + String.format("<!-- %s -->", source), + String.format( + "<attr name='%s' format='%s'>", key.name(), Joiner.on('|').join(formatKeys))); + for (String formatKey : formatKeys) { + iterable = formats.get(formatKey).appendTo(iterable); + } + mergedDataWriter.writeToValuesXml(key, iterable.append("</attr>")); } + @CheckReturnValue + interface ResourceXmlAttrValue { + FluentIterable<String> appendTo(FluentIterable<String> iterable); + } + + // TODO(corysmith): The ResourceXmlAttrValue implementors, other than enum and flag, share a + // lot of boilerplate. Determine how to reduce it. /** Represents an Android Enum Attribute resource. */ @VisibleForTesting - public static class AttrEnumXmlValue implements XmlResourceValue { - + public static class EnumResourceXmlAttrValue implements ResourceXmlAttrValue { + + private static final Function<Entry<String, String>, String> MAP_TO_ENUM = + new Function<Entry<String, String>, String>() { + @Nullable + @Override + public String apply(Entry<String, String> entry) { + return String.format("<enum name='%s' value='%s'/>", entry.getKey(), entry.getValue()); + } + }; private Map<String, String> values; - private AttrEnumXmlValue(Map<String, String> values) { + private EnumResourceXmlAttrValue(Map<String, String> values) { this.values = values; } @VisibleForTesting - public static Map.Entry<String, XmlResourceValue> asEntryOf(String... keyThenValue) { + public static Entry<String, ResourceXmlAttrValue> asEntryOf(String... keyThenValue) { Preconditions.checkArgument(keyThenValue.length > 0); Preconditions.checkArgument(keyThenValue.length % 2 == 0); - Builder<String, String> builder = ImmutableMap.<String, String>builder(); + Builder<String, String> builder = ImmutableMap.builder(); for (int i = 0; i < keyThenValue.length; i += 2) { builder.put(keyThenValue[i], keyThenValue[i + 1]); } return new BuilderEntry(ENUM, of(builder.build())); } - public static XmlResourceValue of(Map<String, String> values) { - return new AttrEnumXmlValue(values); - } - - @Override - public void write(Writer buffer, FullyQualifiedName name) { - // TODO(corysmith): Implement write. - throw new UnsupportedOperationException(); + public static ResourceXmlAttrValue of(Map<String, String> values) { + return new EnumResourceXmlAttrValue(values); } @Override @@ -261,10 +288,10 @@ public class AttrXmlResourceValue implements XmlResourceValue { @Override public boolean equals(Object obj) { - if (!(obj instanceof AttrEnumXmlValue)) { + if (!(obj instanceof EnumResourceXmlAttrValue)) { return false; } - AttrEnumXmlValue other = (AttrEnumXmlValue) obj; + EnumResourceXmlAttrValue other = (EnumResourceXmlAttrValue) obj; return Objects.equals(values, other.values); } @@ -272,25 +299,31 @@ public class AttrXmlResourceValue implements XmlResourceValue { public String toString() { return MoreObjects.toStringHelper(getClass()).add("values", values).toString(); } + + @Override + public FluentIterable<String> appendTo(FluentIterable<String> iterable) { + return iterable.append(FluentIterable.from(values.entrySet()).transform(MAP_TO_ENUM)); + } } /** Represents an Android Flag Attribute resource. */ @VisibleForTesting - public static class AttrFlagXmlValue implements XmlResourceValue { + public static class FlagResourceXmlAttrValue implements ResourceXmlAttrValue { private Map<String, String> values; - private AttrFlagXmlValue(Map<String, String> values) { + private FlagResourceXmlAttrValue(Map<String, String> values) { this.values = values; } - public static XmlResourceValue of(Map<String, String> values) { - return new AttrFlagXmlValue(values); + public static ResourceXmlAttrValue of(Map<String, String> values) { + // ImmutableMap guarantees a stable order. + return new FlagResourceXmlAttrValue(ImmutableMap.copyOf(values)); } @VisibleForTesting - public static Map.Entry<String, XmlResourceValue> asEntryOf(String... keyThenValue) { - Builder<String, String> builder = ImmutableMap.<String, String>builder(); + public static Entry<String, ResourceXmlAttrValue> asEntryOf(String... keyThenValue) { + Builder<String, String> builder = ImmutableMap.builder(); Preconditions.checkArgument(keyThenValue.length > 0); Preconditions.checkArgument(keyThenValue.length % 2 == 0); for (int i = 0; i < keyThenValue.length; i += 2) { @@ -300,22 +333,16 @@ public class AttrXmlResourceValue implements XmlResourceValue { } @Override - public void write(Writer buffer, FullyQualifiedName name) { - // TODO(corysmith): Implement write. - throw new UnsupportedOperationException(); - } - - @Override public int hashCode() { return values.hashCode(); } @Override public boolean equals(Object obj) { - if (!(obj instanceof AttrFlagXmlValue)) { + if (!(obj instanceof FlagResourceXmlAttrValue)) { return false; } - AttrFlagXmlValue other = (AttrFlagXmlValue) obj; + FlagResourceXmlAttrValue other = (FlagResourceXmlAttrValue) obj; return Objects.equals(values, other.values); } @@ -323,14 +350,30 @@ public class AttrXmlResourceValue implements XmlResourceValue { public String toString() { return MoreObjects.toStringHelper(getClass()).add("values", values).toString(); } + + @Override + public FluentIterable<String> appendTo(FluentIterable<String> iterable) { + return iterable.append( + FluentIterable.from(values.entrySet()) + .transform( + new Function<Entry<String, String>, String>() { + @Nullable + @Override + public String apply(Entry<String, String> input) { + return String.format( + "<flag name='%s' value='%s'/>", input.getKey(), input.getValue()); + } + })); + } } /** Represents an Android Reference Attribute resource. */ @VisibleForTesting - public static class AttrReferenceXmlValue implements XmlResourceValue { - private static final AttrReferenceXmlValue INSTANCE = new AttrReferenceXmlValue(); + public static class ReferenceResourceXmlAttrValue implements ResourceXmlAttrValue { + private static final ReferenceResourceXmlAttrValue INSTANCE = + new ReferenceResourceXmlAttrValue(); - public static XmlResourceValue of() { + public static ResourceXmlAttrValue of() { return INSTANCE; } @@ -340,18 +383,17 @@ public class AttrXmlResourceValue implements XmlResourceValue { } @Override - public void write(Writer buffer, FullyQualifiedName name) { - // TODO(corysmith): Implement write. - throw new UnsupportedOperationException(); + public FluentIterable<String> appendTo(FluentIterable<String> iterable) { + return iterable; } } /** Represents an Android Color Attribute resource. */ @VisibleForTesting - public static class AttrColorXmlValue implements XmlResourceValue { - private static final AttrColorXmlValue INSTANCE = new AttrColorXmlValue(); + public static class ColorResourceXmlAttrValue implements ResourceXmlAttrValue { + private static final ColorResourceXmlAttrValue INSTANCE = new ColorResourceXmlAttrValue(); - public static XmlResourceValue of() { + public static ResourceXmlAttrValue of() { return INSTANCE; } @@ -361,18 +403,17 @@ public class AttrXmlResourceValue implements XmlResourceValue { } @Override - public void write(Writer buffer, FullyQualifiedName name) { - // TODO(corysmith): Implement write. - throw new UnsupportedOperationException(); + public FluentIterable<String> appendTo(FluentIterable<String> iterable) { + return iterable; } } /** Represents an Android Boolean Attribute resource. */ @VisibleForTesting - public static class AttrBooleanXmlValue implements XmlResourceValue { - private static final AttrBooleanXmlValue INSTANCE = new AttrBooleanXmlValue(); + public static class BooleanResourceXmlAttrValue implements ResourceXmlAttrValue { + private static final BooleanResourceXmlAttrValue INSTANCE = new BooleanResourceXmlAttrValue(); - public static XmlResourceValue of() { + public static ResourceXmlAttrValue of() { return INSTANCE; } @@ -382,18 +423,17 @@ public class AttrXmlResourceValue implements XmlResourceValue { } @Override - public void write(Writer buffer, FullyQualifiedName name) { - // TODO(corysmith): Implement write. - throw new UnsupportedOperationException(); + public FluentIterable<String> appendTo(FluentIterable<String> iterable) { + return iterable; } } /** Represents an Android Float Attribute resource. */ @VisibleForTesting - public static class AttrFloatXmlValue implements XmlResourceValue { - private static final AttrFloatXmlValue INSTANCE = new AttrFloatXmlValue(); + public static class FloatResourceXmlAttrValue implements ResourceXmlAttrValue { + private static final FloatResourceXmlAttrValue INSTANCE = new FloatResourceXmlAttrValue(); - public static XmlResourceValue of() { + public static ResourceXmlAttrValue of() { return INSTANCE; } @@ -403,39 +443,38 @@ public class AttrXmlResourceValue implements XmlResourceValue { } @Override - public void write(Writer buffer, FullyQualifiedName name) { - // TODO(corysmith): Implement write. - throw new UnsupportedOperationException(); + public FluentIterable<String> appendTo(FluentIterable<String> iterable) { + return iterable; } } /** Represents an Android Dimension Attribute resource. */ @VisibleForTesting - public static class AttrDimensionXmlValue implements XmlResourceValue { - private static final AttrDimensionXmlValue INSTANCE = new AttrDimensionXmlValue(); + public static class DimensionResourceXmlAttrValue implements ResourceXmlAttrValue { + private static final DimensionResourceXmlAttrValue INSTANCE = + new DimensionResourceXmlAttrValue(); + + public static ResourceXmlAttrValue of() { + return INSTANCE; + } @VisibleForTesting public static BuilderEntry asEntry() { return new BuilderEntry(DIMENSION, of()); } - public static XmlResourceValue of() { - return INSTANCE; - } - @Override - public void write(Writer buffer, FullyQualifiedName name) { - // TODO(corysmith): Implement write. - throw new UnsupportedOperationException(); + public FluentIterable<String> appendTo(FluentIterable<String> iterable) { + return iterable; } } /** Represents an Android Integer Attribute resource. */ @VisibleForTesting - public static class AttrIntegerXmlValue implements XmlResourceValue { - private static final AttrIntegerXmlValue INSTANCE = new AttrIntegerXmlValue(); + public static class IntegerResourceXmlAttrValue implements ResourceXmlAttrValue { + private static final IntegerResourceXmlAttrValue INSTANCE = new IntegerResourceXmlAttrValue(); - public static XmlResourceValue of() { + public static ResourceXmlAttrValue of() { return INSTANCE; } @@ -445,18 +484,17 @@ public class AttrXmlResourceValue implements XmlResourceValue { } @Override - public void write(Writer buffer, FullyQualifiedName name) { - // TODO(corysmith): Implement write. - throw new UnsupportedOperationException(); + public FluentIterable<String> appendTo(FluentIterable<String> iterable) { + return iterable; } } /** Represents an Android String Attribute resource. */ @VisibleForTesting - public static class AttrStringXmlValue implements XmlResourceValue { - private static final AttrStringXmlValue INSTANCE = new AttrStringXmlValue(); + public static class StringResourceXmlAttrValue implements ResourceXmlAttrValue { + private static final StringResourceXmlAttrValue INSTANCE = new StringResourceXmlAttrValue(); - public static XmlResourceValue of() { + public static ResourceXmlAttrValue of() { return INSTANCE; } @@ -466,18 +504,17 @@ public class AttrXmlResourceValue implements XmlResourceValue { } @Override - public void write(Writer buffer, FullyQualifiedName name) { - // TODO(corysmith): Implement write. - throw new UnsupportedOperationException(); + public FluentIterable<String> appendTo(FluentIterable<String> iterable) { + return iterable; } } /** Represents an Android Fraction Attribute resource. */ @VisibleForTesting - public static class AttrFractionXmlValue implements XmlResourceValue { - private static final AttrFractionXmlValue INSTANCE = new AttrFractionXmlValue(); + public static class FractionResourceXmlAttrValue implements ResourceXmlAttrValue { + private static final FractionResourceXmlAttrValue INSTANCE = new FractionResourceXmlAttrValue(); - public static XmlResourceValue of() { + public static ResourceXmlAttrValue of() { return INSTANCE; } @@ -487,9 +524,8 @@ public class AttrXmlResourceValue implements XmlResourceValue { } @Override - public void write(Writer buffer, FullyQualifiedName name) { - // TODO(corysmith): Implement write. - throw new UnsupportedOperationException(); + public FluentIterable<String> appendTo(FluentIterable<String> iterable) { + return iterable; } } } 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 f8221ed332..309971005f 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 @@ -14,10 +14,14 @@ package com.google.devtools.build.android.xml; import com.google.common.base.MoreObjects; +import com.google.common.collect.FluentIterable; +import com.google.devtools.build.android.AndroidDataWritingVisitor; import com.google.devtools.build.android.FullyQualifiedName; import com.google.devtools.build.android.XmlResourceValue; -import java.io.Writer; +import java.nio.file.Path; + +import javax.annotation.concurrent.Immutable; /** * Represents an Android Resource id. @@ -28,6 +32,7 @@ import java.io.Writer; * 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 { static final IdXmlResourceValue SINGLETON = new IdXmlResourceValue(); @@ -37,9 +42,13 @@ public class IdXmlResourceValue implements XmlResourceValue { } @Override - public void write(Writer buffer, FullyQualifiedName name) { - // TODO(corysmith): Implement write. - throw new UnsupportedOperationException(); + public void write( + FullyQualifiedName key, Path source, AndroidDataWritingVisitor mergedDataWriter) { + mergedDataWriter.writeToValuesXml( + key, + FluentIterable.of( + String.format("<!-- %s -->", source), + String.format("<item type='id' name='%s'/>", key.name()))); } @Override 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 36c18c0a9e..8df051797a 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 @@ -13,20 +13,27 @@ // limitations under the License. package com.google.devtools.build.android.xml; +import com.google.common.base.Function; import com.google.common.base.MoreObjects; +import com.google.common.collect.FluentIterable; +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 java.io.Writer; -import java.util.Map; +import java.nio.file.Path; +import java.util.Map.Entry; import java.util.Objects; +import javax.annotation.Nullable; +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: + * 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"]> @@ -35,22 +42,37 @@ import java.util.Objects; * </plurals> * </code> */ +@Immutable public class PluralXmlResourceValue implements XmlResourceValue { - private Map<String, String> values; + public static final Function<Entry<String, String>, String> ENTRY_TO_PLURAL = + new Function<Entry<String, String>, String>() { + @Nullable + @Override + public String apply(Entry<String, String> input) { + return String.format("<item quantity='%s'>%s</item>", input.getKey(), input.getValue()); + } + }; + private final ImmutableMap<String, String> values; - private PluralXmlResourceValue(Map<String, String> values) { + private PluralXmlResourceValue(ImmutableMap<String, String> values) { this.values = values; } - public static XmlResourceValue of(Map<String, String> values) { + public static XmlResourceValue of(ImmutableMap<String, String> values) { return new PluralXmlResourceValue(values); } @Override - public void write(Writer buffer, FullyQualifiedName name) { - // TODO(corysmith): Implement write. - throw new UnsupportedOperationException(); + public void write( + FullyQualifiedName key, Path source, AndroidDataWritingVisitor mergedDataWriter) { + mergedDataWriter.writeToValuesXml( + key, + FluentIterable.of( + String.format("<!-- %s -->", source), + String.format("<plurals name='%s'>", key.name())) + .append(FluentIterable.from(values.entrySet()).transform(ENTRY_TO_PLURAL)) + .append("</plurals>")); } @Override 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 ce7684b764..4df442f35f 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 @@ -14,15 +14,18 @@ package com.google.devtools.build.android.xml; import com.google.common.base.MoreObjects; +import com.google.common.collect.FluentIterable; +import com.google.devtools.build.android.AndroidDataWritingVisitor; import com.google.devtools.build.android.FullyQualifiedName; import com.google.devtools.build.android.XmlResourceValue; import com.android.resources.ResourceType; -import java.io.Writer; +import java.nio.file.Path; import java.util.Arrays; import java.util.Objects; +import javax.annotation.concurrent.Immutable; import javax.xml.namespace.QName; /** @@ -37,6 +40,7 @@ import javax.xml.namespace.QName; * 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 { static final QName TAG_STRING = QName.valueOf("string"); static final QName TAG_BOOL = QName.valueOf("bool"); @@ -94,8 +98,8 @@ public class SimpleXmlResourceValue implements XmlResourceValue { } } - private String value; - private Type valueType; + private final String value; + private final Type valueType; public static XmlResourceValue of(Type valueType, String value) { return new SimpleXmlResourceValue(valueType, value); @@ -107,9 +111,18 @@ public class SimpleXmlResourceValue implements XmlResourceValue { } @Override - public void write(Writer buffer, FullyQualifiedName name) { - // TODO(corysmith): Implement write. - throw new UnsupportedOperationException(); + public void write( + FullyQualifiedName key, Path source, AndroidDataWritingVisitor mergedDataWriter) { + mergedDataWriter.writeToValuesXml( + key, + FluentIterable.of( + String.format("<!-- %s -->", source), + String.format( + "<%s name='%s'>%s</%s>", + valueType.tagName.getLocalPart(), + key.name(), + value, + valueType.tagName.getLocalPart()))); } @Override 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 3055089ea0..2067a01f77 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 @@ -13,14 +13,22 @@ // limitations under the License. package com.google.devtools.build.android.xml; +import com.google.common.base.Function; import com.google.common.base.MoreObjects; +import com.google.common.collect.FluentIterable; +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 java.io.Writer; +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; + /** * Represents an Android Style Resource. * @@ -38,23 +46,40 @@ import java.util.Objects; * </resources> * </code> */ +@Immutable public class StyleXmlResourceValue implements XmlResourceValue { - private String parent; - private Map<String, String> values; + public static final Function<Entry<String, String>, String> ENTRY_TO_ITEM = + new Function<Entry<String, String>, String>() { + @Nullable + @Override + public String apply(Entry<String, String> input) { + return String.format("<item name='%s'>%s</item>", input.getKey(), input.getValue()); + } + }; + private final String parent; + private final ImmutableMap<String, String> values; public static StyleXmlResourceValue of(String parent, Map<String, String> values) { - return new StyleXmlResourceValue(parent, values); + return new StyleXmlResourceValue(parent, ImmutableMap.copyOf(values)); } - private StyleXmlResourceValue(String parent, Map<String, String> values) { + private StyleXmlResourceValue(String parent, ImmutableMap<String, String> values) { this.parent = parent; this.values = values; } @Override - public void write(Writer buffer, FullyQualifiedName name) { - // TODO(corysmith): Implement write. - throw new UnsupportedOperationException(); + public void write( + FullyQualifiedName key, Path source, AndroidDataWritingVisitor mergedDataWriter) { + mergedDataWriter.writeToValuesXml( + key, + FluentIterable.of( + 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)) + .append(FluentIterable.from(values.entrySet()).transform(ENTRY_TO_ITEM)) + .append("</style>")); } @Override 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 f1a0486307..76011237f7 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 @@ -14,15 +14,23 @@ package com.google.devtools.build.android.xml; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; import com.google.common.base.MoreObjects; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Ordering; +import com.google.devtools.build.android.AndroidDataWritingVisitor; import com.google.devtools.build.android.FullyQualifiedName; import com.google.devtools.build.android.XmlResourceValue; -import java.io.Writer; +import java.nio.file.Path; import java.util.Arrays; import java.util.List; import java.util.Objects; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + /** * Represent an Android styleable resource. * @@ -46,26 +54,42 @@ import java.util.Objects; * * <p>The StyleableXmlValue only contains names of the attributes it holds, not definitions. */ +@Immutable public class StyleableXmlResourceValue implements XmlResourceValue { - private final List<String> attrs; + public static final Function<String, String> ITEM_TO_ATTR = + new Function<String, String>() { + @Nullable + @Override + public String apply(@Nullable String input) { + return String.format("<attr name='%s'/>", input); + } + }; + private final ImmutableList<String> attrs; - private StyleableXmlResourceValue(List<String> attrs) { + private StyleableXmlResourceValue(ImmutableList<String> attrs) { this.attrs = attrs; } public static XmlResourceValue of(List<String> attrs) { - return new StyleableXmlResourceValue(attrs); + return new StyleableXmlResourceValue(ImmutableList.copyOf(attrs)); } @VisibleForTesting public static XmlResourceValue of(String... attrs) { - return new StyleableXmlResourceValue(Arrays.asList(attrs)); + return new StyleableXmlResourceValue( + Ordering.natural().immutableSortedCopy(Arrays.asList(attrs))); } @Override - public void write(Writer buffer, FullyQualifiedName name) { - // TODO(corysmith): Implement write. - throw new UnsupportedOperationException(); + public void write( + FullyQualifiedName key, Path source, AndroidDataWritingVisitor mergedDataWriter) { + mergedDataWriter.writeToValuesXml( + key, + FluentIterable.of( + String.format("<!-- %s -->", source), + String.format("<declare-styleable name='%s'>", key.name())) + .append(FluentIterable.from(attrs).transform(ITEM_TO_ATTR)) + .append("</declare-styleable>")); } @Override |