diff options
Diffstat (limited to 'src/tools/android')
38 files changed, 783 insertions, 682 deletions
diff --git a/src/tools/android/java/com/google/devtools/build/android/Aapt2ResourcePackagingAction.java b/src/tools/android/java/com/google/devtools/build/android/Aapt2ResourcePackagingAction.java index 4ef23e76e2..83b628cd9e 100644 --- a/src/tools/android/java/com/google/devtools/build/android/Aapt2ResourcePackagingAction.java +++ b/src/tools/android/java/com/google/devtools/build/android/Aapt2ResourcePackagingAction.java @@ -125,82 +125,83 @@ public class Aapt2ResourcePackagingAction { profiler.recordEndOf("merging"); - profiler.startTask("compile"); - final ResourceCompiler compiler = - ResourceCompiler.create( - executorService, - compiledResources, - aaptConfigOptions.aapt2, - aaptConfigOptions.buildToolsVersion); - - CompiledResources compiled = - options - .primaryData - .processDataBindings( - options.dataBindingInfoOut, options.packageForR, databindingResourcesRoot) - .compile(compiler, compiledResources) - .processManifest( - manifest -> - AndroidManifestProcessor.with(STD_LOGGER) - .processManifest( - options.applicationId, - options.versionCode, - options.versionName, - manifest, - processedManifest)) - .processManifest( - manifest -> - new DensitySpecificManifestProcessor(densities, densityManifest) - .process(manifest)); - profiler.recordEndOf("compile").startTask("link"); - // Write manifestOutput now before the dummy manifest is created. - if (options.manifestOutput != null) { - AndroidResourceOutputs.copyManifestToOutput(compiled, options.manifestOutput); - } - - List<CompiledResources> compiledResourceDeps = - // Last defined dependencies will overwrite previous one, so always place direct - // after transitive. - concat(options.transitiveData.stream(), options.directData.stream()) - .map(DependencyAndroidData::getCompiledSymbols) - .collect(toList()); - - List<Path> assetDirs = - concat(options.transitiveData.stream(), options.directData.stream()) - .flatMap(dep -> dep.assetDirs.stream()) - .collect(toList()); - assetDirs.addAll(options.primaryData.assetDirs); - - final PackagedResources packagedResources = - ResourceLinker.create(aaptConfigOptions.aapt2, linkedOut) - .profileUsing(profiler) - .customPackage(options.packageForR) - .outputAsProto(aaptConfigOptions.resourceTableAsProto) - .dependencies(ImmutableList.of(StaticLibrary.from(aaptConfigOptions.androidJar))) - .include(compiledResourceDeps) - .withAssets(assetDirs) - .buildVersion(aaptConfigOptions.buildToolsVersion) - .conditionalKeepRules(aaptConfigOptions.conditionalKeepRules == TriState.YES) - .filterToDensity(densities) - .includeOnlyConfigs(aaptConfigOptions.resourceConfigs) - .link(compiled) - .copyPackageTo(options.packagePath) - .copyProguardTo(options.proguardOutput) - .copyMainDexProguardTo(options.mainDexProguardOutput) - .createSourceJar(options.srcJarOutput) - .copyRTxtTo(options.rOutput); - profiler.recordEndOf("link"); - if (options.resourcesOutput != null) { - profiler.startTask("package"); - // The compiled resources and the merged resources should be the same. - // TODO(corysmith): Decompile or otherwise provide the exact resources in the apk. - ResourcesZip.fromApk( - mergedAndroidData.getResourceDir(), - packagedResources.getApk(), - packagedResources.getResourceIds()) - .writeTo(options.resourcesOutput, false /* compress */); - profiler.recordEndOf("package"); + + profiler.startTask("compile"); + final ResourceCompiler compiler = + ResourceCompiler.create( + executorService, + compiledResources, + aaptConfigOptions.aapt2, + aaptConfigOptions.buildToolsVersion); + + CompiledResources compiled = + options + .primaryData + .processDataBindings( + options.dataBindingInfoOut, options.packageForR, databindingResourcesRoot) + .compile(compiler, compiledResources) + .processManifest( + manifest -> + AndroidManifestProcessor.with(STD_LOGGER) + .processManifest( + options.applicationId, + options.versionCode, + options.versionName, + manifest, + processedManifest)) + .processManifest( + manifest -> + new DensitySpecificManifestProcessor(densities, densityManifest) + .process(manifest)); + profiler.recordEndOf("compile").startTask("link"); + // Write manifestOutput now before the dummy manifest is created. + if (options.manifestOutput != null) { + AndroidResourceOutputs.copyManifestToOutput(compiled, options.manifestOutput); + } + + List<CompiledResources> compiledResourceDeps = + // Last defined dependencies will overwrite previous one, so always place direct + // after transitive. + concat(options.transitiveData.stream(), options.directData.stream()) + .map(DependencyAndroidData::getCompiledSymbols) + .collect(toList()); + + List<Path> assetDirs = + concat(options.transitiveData.stream(), options.directData.stream()) + .flatMap(dep -> dep.assetDirs.stream()) + .collect(toList()); + assetDirs.addAll(options.primaryData.assetDirs); + + final PackagedResources packagedResources = + ResourceLinker.create(aaptConfigOptions.aapt2, linkedOut) + .profileUsing(profiler) + .customPackage(options.packageForR) + .outputAsProto(aaptConfigOptions.resourceTableAsProto) + .dependencies(ImmutableList.of(StaticLibrary.from(aaptConfigOptions.androidJar))) + .include(compiledResourceDeps) + .withAssets(assetDirs) + .buildVersion(aaptConfigOptions.buildToolsVersion) + .conditionalKeepRules(aaptConfigOptions.conditionalKeepRules == TriState.YES) + .filterToDensity(densities) + .includeOnlyConfigs(aaptConfigOptions.resourceConfigs) + .link(compiled) + .copyPackageTo(options.packagePath) + .copyProguardTo(options.proguardOutput) + .copyMainDexProguardTo(options.mainDexProguardOutput) + .createSourceJar(options.srcJarOutput) + .copyRTxtTo(options.rOutput); + profiler.recordEndOf("link"); + if (options.resourcesOutput != null) { + profiler.startTask("package"); + // The compiled resources and the merged resources should be the same. + // TODO(corysmith): Decompile or otherwise provide the exact resources in the apk. + ResourcesZip.fromApk( + mergedAndroidData.getResourceDir(), + packagedResources.getApk(), + packagedResources.getResourceIds()) + .writeTo(options.resourcesOutput, false /* compress */); + profiler.recordEndOf("package"); + } } } } -} diff --git a/src/tools/android/java/com/google/devtools/build/android/AarGeneratorAction.java b/src/tools/android/java/com/google/devtools/build/android/AarGeneratorAction.java index 3f3c35c029..20e9e6c9d7 100644 --- a/src/tools/android/java/com/google/devtools/build/android/AarGeneratorAction.java +++ b/src/tools/android/java/com/google/devtools/build/android/AarGeneratorAction.java @@ -169,7 +169,7 @@ public class AarGeneratorAction { null, VariantType.LIBRARY, null, - /* filteredResources= */ ImmutableList.of(), + /* filteredResources= */ ImmutableList.<String>of(), options.throwOnResourceConflict); logger.fine(String.format("Merging finished at %dms", timer.elapsed(TimeUnit.MILLISECONDS))); diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidCompiledDataDeserializer.java b/src/tools/android/java/com/google/devtools/build/android/AndroidCompiledDataDeserializer.java index 0e6209b2c9..fb4d93dcc6 100644 --- a/src/tools/android/java/com/google/devtools/build/android/AndroidCompiledDataDeserializer.java +++ b/src/tools/android/java/com/google/devtools/build/android/AndroidCompiledDataDeserializer.java @@ -28,6 +28,9 @@ import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableSet; import com.google.common.io.LittleEndianDataInputStream; import com.google.devtools.build.android.FullyQualifiedName.Factory; +import com.google.devtools.build.android.proto.SerializeFormat; +import com.google.devtools.build.android.proto.SerializeFormat.Header; +import com.google.devtools.build.android.xml.ResourcesAttribute.AttributeType; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; @@ -77,8 +80,7 @@ public class AndroidCompiledDataDeserializer implements AndroidDataDeserializer private void readResourceTable( LittleEndianDataInputStream resourceTableStream, KeyValueConsumers consumers, - Factory fqnFactory) - throws IOException { + Factory fqnFactory) throws IOException { long alignedSize = resourceTableStream.readLong(); Preconditions.checkArgument(alignedSize <= Integer.MAX_VALUE); @@ -111,27 +113,34 @@ public class AndroidCompiledDataDeserializer implements AndroidDataDeserializer new SimpleEntry<FullyQualifiedName, Boolean>(fqn, packageName.isEmpty())); List<ConfigValue> configValues = resource.getConfigValueList(); - if (configValues.isEmpty() && resource.getVisibility().getLevel() == Level.PUBLIC) { + if (configValues.isEmpty() + && resource.getVisibility().getLevel() == Level.PUBLIC) { int sourceIndex = resource.getVisibility().getSource().getPathIdx(); String source = sourcePool.get(sourceIndex); DataSource dataSource = DataSource.of(Paths.get(source)); - DataResourceXml dataResourceXml = - DataResourceXml.fromPublic(dataSource, resourceType, resource.getEntryId().getId()); + DataResourceXml dataResourceXml = DataResourceXml + .fromPublic(dataSource, resourceType, resource.getEntryId().getId()); consumers.combiningConsumer.accept(fqn, dataResourceXml); - } else if (packageName.isEmpty()) { // This means this resource is not in the android sdk + } else if (packageName.isEmpty()) {// This means this resource is not in the android sdk Preconditions.checkArgument(configValues.size() == 1); - int sourceIndex = configValues.get(0).getValue().getSource().getPathIdx(); + int sourceIndex = + configValues.get(0) + .getValue() + .getSource() + .getPathIdx(); String source = sourcePool.get(sourceIndex); DataSource dataSource = DataSource.of(Paths.get(source)); Value resourceValue = resource.getConfigValue(0).getValue(); DataResourceXml dataResourceXml = - DataResourceXml.from(resourceValue, dataSource, resourceType, fullyQualifiedNames); + DataResourceXml + .from(resourceValue, dataSource, resourceType, fullyQualifiedNames); - if (resourceType == ResourceType.ID || resourceType == ResourceType.STYLEABLE) { + if (resourceType == ResourceType.ID + || resourceType == ResourceType.STYLEABLE) { consumers.combiningConsumer.accept(fqn, dataResourceXml); } else { consumers.overwritingConsumer.accept(fqn, dataResourceXml); @@ -156,14 +165,13 @@ public class AndroidCompiledDataDeserializer implements AndroidDataDeserializer private void readCompiledFile( LittleEndianDataInputStream compiledFileStream, KeyValueConsumers consumers, - Factory fqnFactory) - throws IOException { - // Skip aligned size. We don't need it here. + Factory fqnFactory) throws IOException { + //Skip aligned size. We don't need it here. Preconditions.checkArgument(compiledFileStream.skipBytes(8) == 8); int resFileHeaderSize = compiledFileStream.readInt(); - // Skip data payload size. We don't need it here. + //Skip data payload size. We don't need it here. Preconditions.checkArgument(compiledFileStream.skipBytes(8) == 8); byte[] file = new byte[resFileHeaderSize]; @@ -193,10 +201,35 @@ public class AndroidCompiledDataDeserializer implements AndroidDataDeserializer } private void readAttributesFile( - InputStream resourceFileStream, FileSystem fileSystem, KeyValueConsumers consumers) - throws IOException { - AndroidParsedDataDeserializer.deserializeEntries( - consumers, resourceFileStream, fileSystem, ImmutableSet.of()); + InputStream resourceFileStream, + FileSystem fileSystem, + KeyValueConsumers consumers) throws IOException { + + Header header = Header.parseDelimitedFrom(resourceFileStream); + List<DataKey> fullyQualifiedNames = new ArrayList<>(); + for (int i = 0; i < header.getEntryCount(); i++) { + SerializeFormat.DataKey protoKey = + SerializeFormat.DataKey.parseDelimitedFrom(resourceFileStream); + fullyQualifiedNames.add(FullyQualifiedName.fromProto(protoKey)); + } + + DataSourceTable sourceTable = DataSourceTable.read(resourceFileStream, fileSystem, header); + + for (DataKey fullyQualifiedName : fullyQualifiedNames) { + SerializeFormat.DataValue protoValue = + SerializeFormat.DataValue.parseDelimitedFrom(resourceFileStream); + DataSource source = sourceTable.sourceFromId(protoValue.getSourceId()); + DataResourceXml dataResourceXml = + (DataResourceXml) DataResourceXml.from(protoValue, source); + AttributeType attributeType = + AttributeType.valueOf(protoValue.getXmlValue().getValueType()); + + if (attributeType.isCombining()) { + consumers.combiningConsumer.accept(fullyQualifiedName, dataResourceXml); + } else { + consumers.overwritingConsumer.accept(fullyQualifiedName, dataResourceXml); + } + } } @Override 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 index 9a1c3a07ac..2304f3cb67 100644 --- a/src/tools/android/java/com/google/devtools/build/android/AndroidDataSerializer.java +++ b/src/tools/android/java/com/google/devtools/build/android/AndroidDataSerializer.java @@ -13,23 +13,17 @@ // limitations under the License. package com.google.devtools.build.android; -import com.google.common.base.MoreObjects; -import com.google.common.base.Preconditions; import com.google.common.base.Stopwatch; -import com.google.common.collect.BiMap; -import com.google.common.collect.HashBiMap; 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.Header; -import com.google.devtools.build.android.xml.Namespaces; import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; -import java.util.ArrayList; -import java.util.List; +import java.util.Map; import java.util.Map.Entry; import java.util.NavigableMap; import java.util.Set; @@ -39,128 +33,6 @@ import java.util.logging.Logger; /** Serializes {@link DataKey},{@link DataValue} entries to a binary file. */ public class AndroidDataSerializer { - - /** A visitor to accumulate the necessary state to write a resource entry. */ - public interface SerializeEntryVisitor extends Writeable { - SerializeEntryVisitor setSource(DataSource dataSource); - - SerializeEntryVisitor setKey(DataKey key); - - SerializeEntryVisitor overwrite(Set<DataSource> dataSource); - - SerializeEntryVisitor setXml(XmlResourceValue value); - - SerializeEntryVisitor setNamespaces(Namespaces namespaces); - } - - private static class DataValueBuilder implements SerializeEntryVisitor { - - private final Builder builder; - private final WritablePool<DataKey> keys; - private final WritablePool<DataSource> sources; - private final WritablePool<XmlResourceValue> xml; - private final WritablePool<Namespaces> namespaces; - - public DataValueBuilder( - Builder builder, - WritablePool<DataKey> keys, - WritablePool<DataSource> sources, - WritablePool<XmlResourceValue> xml, - WritablePool<Namespaces> namespaces) { - this.builder = Preconditions.checkNotNull(builder); - this.keys = Preconditions.checkNotNull(keys); - this.sources = sources; - this.xml = xml; - this.namespaces = namespaces; - } - - static DataValueBuilder create( - WritablePool<DataKey> keys, - WritablePool<DataSource> sources, - WritablePool<XmlResourceValue> xml, - WritablePool<Namespaces> namespaces) { - return new DataValueBuilder( - SerializeFormat.DataValue.newBuilder(), keys, sources, xml, namespaces); - } - - @Override - public SerializeEntryVisitor setSource(DataSource dataSource) { - builder.setSourceId(sources.queue(dataSource)); - overwrite(dataSource.overrides()); - return this; - } - - @Override - public SerializeEntryVisitor setKey(DataKey key) { - builder.setKeyId(keys.queue(key)); - return this; - } - - @Override - public SerializeEntryVisitor overwrite(Set<DataSource> dataSource) { - for (DataSource source : dataSource) { - builder.addOverwrittenSourceId(sources.queue(source)); - } - return this; - } - - @Override - public SerializeEntryVisitor setXml(XmlResourceValue value) { - builder.setXmlId(xml.queue(value)); - return this; - } - - @Override - public SerializeEntryVisitor setNamespaces(Namespaces namespaces) { - builder.setNamespaceId(this.namespaces.queue(namespaces)); - return this; - } - - @Override - public void writeTo(OutputStream out) throws IOException { - builder.build().writeDelimitedTo(out); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("builder", builder.build()) - .add("keys", keys) - .add("sources", sources) - .add("xml", xml) - .add("namespaces", namespaces) - .toString(); - } - } - - private static class WritablePool<T extends Writeable> implements Writeable { - BiMap<T, Integer> lookup = HashBiMap.create(); - Integer lastIndex = 0; - - Integer queue(T value) { - if (!lookup.containsKey(value)) { - lookup.put(value, lastIndex++); - } - return lookup.get(value); - } - - @Override - public void writeTo(OutputStream out) throws IOException { - final BiMap<Integer, T> indexed = lookup.inverse(); - for (int idx = 0; idx < lastIndex; idx++) { - indexed.get(idx).writeTo(out); - } - } - - public int size() { - return lookup.size(); - } - - public static <T extends Writeable> WritablePool<T> create() { - return new WritablePool<>(); - } - } - private static final Logger logger = Logger.getLogger(AndroidDataSerializer.class.getName()); private final NavigableMap<DataKey, DataValue> entries = new TreeMap<>(); @@ -191,61 +63,54 @@ public class AndroidDataSerializer { if (out.getParent() != null) { Files.createDirectories(out.getParent()); } - WritablePool<DataKey> keys = WritablePool.create(); - WritablePool<DataSource> sources = WritablePool.create(); - WritablePool<XmlResourceValue> xml = WritablePool.create(); - WritablePool<Namespaces> namespaces = WritablePool.create(); - try (OutputStream outStream = new BufferedOutputStream( Files.newOutputStream(out, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE))) { // Set the header for the deserialization process. + SerializeFormat.Header.Builder headerBuilder = + Header.newBuilder().setEntryCount(entries.size()); - List<SerializeEntryVisitor> values = new ArrayList<>(entries.size()); - for (Entry<DataKey, DataValue> entry : entries.entrySet()) { - values.add( - entry - .getValue() - .serializeTo( - DataValueBuilder.create(keys, sources, xml, namespaces) - .setKey(entry.getKey()))); - } - - Header.newBuilder() - .setKeyCount(keys.size()) - .setSourceCount(sources.size()) - .setValueCount(values.size()) - .setXmlCount(xml.size()) - .setNamespacesCount(namespaces.size()) - .build() - .writeDelimitedTo(outStream); - - /* - Serialization order: - keys - sources - values - xml - namespaces + // Create table of source paths to allow references in the serialization format via an index. + ByteArrayOutputStream sourceTableOutputStream = new ByteArrayOutputStream(2048); + DataSourceTable sourceTable = + DataSourceTable.createAndWrite(entries, sourceTableOutputStream, headerBuilder); - This allows deserializing keys for R generation, as well as sources and entries for - lightweight merging. - */ + headerBuilder.build().writeDelimitedTo(outStream); - keys.writeTo(outStream); - sources.writeTo(outStream); - for (SerializeEntryVisitor value : values) { - value.writeTo(outStream); - } - xml.writeTo(outStream); - namespaces.writeTo(outStream); - - outStream.flush(); + writeKeyValuesTo(entries, outStream, sourceTable, sourceTableOutputStream.toByteArray()); } logger.fine(String.format("Serialized merged in %sms", timer.elapsed(TimeUnit.MILLISECONDS))); } + private void writeKeyValuesTo( + NavigableMap<DataKey, DataValue> map, + OutputStream outStream, + DataSourceTable sourceTable, + byte[] sourceTableBytes) + throws IOException { + Set<Entry<DataKey, DataValue>> entries = map.entrySet(); + int[] orderedValueSizes = new int[entries.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 (Map.Entry<DataKey, DataValue> entry : entries) { + orderedValueSizes[valueSizeIndex++] = + entry.getValue().serializeTo(sourceTable, valuesOutputStream); + } + // Serialize all the keys in sorted order + valueSizeIndex = 0; + for (Map.Entry<DataKey, DataValue> entry : entries) { + entry.getKey().serializeTo(outStream, orderedValueSizes[valueSizeIndex++]); + } + // write the source table + outStream.write(sourceTableBytes); + // write the values to the output stream. + outStream.write(valuesOutputStream.toByteArray()); + } + /** Queues the key and value for serialization as a entries entry. */ public void queueForSerialization(DataKey key, DataValue value) { entries.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 a9770f76cd..38d38f3aa0 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 @@ -654,7 +654,7 @@ public class AndroidDataWriter implements AndroidDataWritingVisitor { } /** Base interface for writing information to a {@link Writer}. */ - private interface Segment { + private static interface Segment { /** * Writes the segment contents to a Writer * diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidParsedDataDeserializer.java b/src/tools/android/java/com/google/devtools/build/android/AndroidParsedDataDeserializer.java index 3ded0da24f..e704c06552 100644 --- a/src/tools/android/java/com/google/devtools/build/android/AndroidParsedDataDeserializer.java +++ b/src/tools/android/java/com/google/devtools/build/android/AndroidParsedDataDeserializer.java @@ -15,6 +15,7 @@ package com.google.devtools.build.android; import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; @@ -23,9 +24,6 @@ import com.google.devtools.build.android.ParsedAndroidData.Builder; 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.devtools.build.android.proto.SerializeFormat.XmlNamespaces; -import com.google.devtools.build.android.xml.Namespaces; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; @@ -36,11 +34,12 @@ import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.concurrent.Callable; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; -import java.util.stream.Collectors; /** Deserializes {@link DataKey}, {@link DataValue} entries from a binary file. */ public class AndroidParsedDataDeserializer implements AndroidDataDeserializer { @@ -88,7 +87,7 @@ public class AndroidParsedDataDeserializer implements AndroidDataDeserializer { } public static AndroidParsedDataDeserializer create() { - return new AndroidParsedDataDeserializer(ImmutableSet.of()); + return new AndroidParsedDataDeserializer(ImmutableSet.<String>of()); } private AndroidParsedDataDeserializer(ImmutableSet<String> filteredResources) { @@ -109,8 +108,12 @@ public class AndroidParsedDataDeserializer implements AndroidDataDeserializer { Stopwatch timer = Stopwatch.createStarted(); try (InputStream in = Files.newInputStream(inPath, StandardOpenOption.READ)) { FileSystem currentFileSystem = inPath.getFileSystem(); - deserializeEntries(consumers, in, currentFileSystem, filteredResources); - } catch (Throwable e) { + Header header = Header.parseDelimitedFrom(in); + if (header == null) { + throw new DeserializationException("No Header found in " + inPath); + } + readEntriesSegment(consumers, in, currentFileSystem, header); + } catch (IOException e) { throw new DeserializationException("Error deserializing " + inPath, e); } finally { logger.fine( @@ -118,103 +121,59 @@ public class AndroidParsedDataDeserializer implements AndroidDataDeserializer { } } - public static void deserializeEntries( - KeyValueConsumers consumers, - InputStream in, - FileSystem currentFileSystem, - ImmutableSet<String> filteredResources) + private void readEntriesSegment( + KeyValueConsumers consumers, InputStream in, FileSystem currentFileSystem, Header header) throws IOException { - - /* - Serialization order: - header - keys - sources - values - xml - namespaces - */ - - Header header = Header.parseDelimitedFrom(in); - if (header == null) { - throw new DeserializationException("Invalid Format: no header"); - } - - DataKey[] keys = new DataKey[header.getKeyCount()]; - for (int i = 0; i < header.getKeyCount(); i++) { - final SerializeFormat.DataKey proto = SerializeFormat.DataKey.parseDelimitedFrom(in); - keys[i] = - proto.hasResourceType() - ? FullyQualifiedName.fromProto(proto) - : RelativeAssetPath.fromProto(proto, currentFileSystem); - } - - DataSource[] sources = new DataSource[header.getSourceCount()]; - for (int i = 0; i < header.getSourceCount(); i++) { - sources[i] = DataSource.from(ProtoSource.parseDelimitedFrom(in), currentFileSystem); + int numberOfEntries = header.getEntryCount(); + Map<DataKey, KeyValueConsumer<DataKey, ? extends DataValue>> keys = + Maps.newLinkedHashMapWithExpectedSize(numberOfEntries); + 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, + resourceName.isOverwritable() + ? consumers.overwritingConsumer + : consumers.combiningConsumer); + } else { + keys.put(RelativeAssetPath.fromProto(protoKey, currentFileSystem), consumers.assetConsumer); + } } - List<SerializeFormat.DataValue> values = new ArrayList<>(header.getValueCount()); - for (int i = 0; i < header.getValueCount(); i++) { - final SerializeFormat.DataValue protoValue = SerializeFormat.DataValue.parseDelimitedFrom(in); - if (sources[protoValue.getSourceId()].shouldFilter(filteredResources)) { + // Read back the sources table. + DataSourceTable sourceTable = DataSourceTable.read(in, currentFileSystem, header); + + // 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); + DataSource source = sourceTable.sourceFromId(protoValue.getSourceId()); + // Compose the `shortPath` manually to ensure it uses a forward slash. + // Using Path.subpath would return a backslash-using path on Windows. + String shortPath = + source.getPath().getParent().getFileName() + "/" + source.getPath().getFileName(); + if (filteredResources.contains(shortPath) && !Files.exists(source.getPath())) { + // Skip files that were filtered out during analysis. + // TODO(asteinb): Properly filter out these files from android_library symbol files during + // analysis instead, and remove this list. continue; } - values.add(protoValue); - } - - XmlResourceValue[] xml = new XmlResourceValue[header.getXmlCount()]; - for (int i = 0; i < header.getXmlCount(); i++) { - xml[i] = - XmlResourceValues.valueFromProto(SerializeFormat.DataValueXml.parseDelimitedFrom(in)); - } - - Namespaces[] namespaces = new Namespaces[header.getNamespacesCount()]; - for (int i = 0; i < header.getNamespacesCount(); i++) { - final XmlNamespaces proto = XmlNamespaces.parseDelimitedFrom(in); - namespaces[i] = Namespaces.fromProto(proto); - } - - for (SerializeFormat.DataValue protoValue : values) { - final DataSource dataSource = - sources[protoValue.getSourceId()].overwrite( - protoValue - .getOverwrittenSourceIdList() - .stream() - .map(sourceId -> sources[sourceId]) - .collect(Collectors.toList()) - .toArray(new DataSource[0])); - - final DataKey dataKey = keys[protoValue.getKeyId()]; - - switch (dataKey.getKeyType()) { - case ASSET_PATH: - consumers.assetConsumer.accept(dataKey, DataValueFile.of(dataSource)); - break; - case FULL_QUALIFIED_NAME: - final FullyQualifiedName fullyQualifiedName = (FullyQualifiedName) dataKey; - - KeyValueConsumer<DataKey, DataResource> consumer = - fullyQualifiedName.isOverwritable() - ? consumers.overwritingConsumer - : consumers.combiningConsumer; - - if (!protoValue.hasXmlId()) { - consumer.accept(dataKey, DataValueFile.of(dataSource)); - } else { - consumer.accept( - fullyQualifiedName, - protoValue.hasNamespaceId() - ? DataResourceXml.createWithNamespaces( - dataSource, - xml[protoValue.getXmlId()], - namespaces[protoValue.getNamespaceId()]) - : DataResourceXml.createWithNoNamespace( - dataSource, xml[protoValue.getXmlId()])); - } - break; - default: - throw new DeserializationException("Unknown key type " + dataKey); + 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 accept. + // 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.accept(entry.getKey(), DataResourceXml.from(protoValue, source)); + } else { + @SuppressWarnings("unchecked") + KeyValueConsumer<DataKey, DataValue> value = + (KeyValueConsumer<DataKey, DataValue>) entry.getValue(); + value.accept(entry.getKey(), DataValueFile.of(source)); } } } diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceParsingAction.java b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceParsingAction.java index ce5cb77147..e2a98e4f13 100644 --- a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceParsingAction.java +++ b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceParsingAction.java @@ -83,7 +83,7 @@ public class AndroidResourceParsingAction { logger.fine(String.format("Walked XML tree at %dms", timer.elapsed(TimeUnit.MILLISECONDS))); UnwrittenMergedAndroidData unwrittenData = UnwrittenMergedAndroidData.of( - null, parsedPrimary, ParsedAndroidData.from(ImmutableList.of())); + null, parsedPrimary, ParsedAndroidData.from(ImmutableList.<DependencyAndroidData>of())); AndroidDataSerializer serializer = AndroidDataSerializer.create(); unwrittenData.serializeTo(serializer); serializer.flushTo(options.output); diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceValidatorAction.java b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceValidatorAction.java index 970c7bc512..23f1671a07 100644 --- a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceValidatorAction.java +++ b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceValidatorAction.java @@ -162,7 +162,7 @@ public class AndroidResourceValidatorAction { options.packageForR, new FlagAaptOptions(aaptConfigOptions), aaptConfigOptions.resourceConfigs, - ImmutableList.of(), + ImmutableList.<String>of(), dummyManifest, resources, assets, diff --git a/src/tools/android/java/com/google/devtools/build/android/Converters.java b/src/tools/android/java/com/google/devtools/build/android/Converters.java index 75c5ead21e..e58dd2d29b 100644 --- a/src/tools/android/java/com/google/devtools/build/android/Converters.java +++ b/src/tools/android/java/com/google/devtools/build/android/Converters.java @@ -218,7 +218,7 @@ public final class Converters { @Override public List<DependencySymbolFileProvider> convert(String input) throws OptionsParsingException { if (input.isEmpty()) { - return ImmutableList.of(); + return ImmutableList.<DependencySymbolFileProvider>of(); } try { ImmutableList.Builder<DependencySymbolFileProvider> builder = ImmutableList.builder(); @@ -473,7 +473,7 @@ public final class Converters { @Override public List<StaticLibrary> convert(String input) throws OptionsParsingException { - final Builder<StaticLibrary> builder = ImmutableList.builder(); + final Builder<StaticLibrary> builder = ImmutableList.<StaticLibrary>builder(); for (String path : SPLITTER.splitToList(input)) { builder.add(libraryConverter.convert(path)); } 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 f8a76a5768..02f193c0f7 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,6 +13,9 @@ // limitations under the License. package com.google.devtools.build.android; +import java.io.IOException; +import java.io.OutputStream; + /** * A general interface for resource and asset keys. * @@ -23,7 +26,16 @@ package com.google.devtools.build.android; * * <p>For Assets, it is the asset path from the assets directory. */ -public interface DataKey extends Comparable<DataKey>, Writeable { +public interface DataKey extends Comparable<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; /** Returns a human readable string representation of the key. */ String toPrettyString(); 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 eade76072a..2b39c449e6 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 @@ -20,11 +20,13 @@ import static com.android.resources.ResourceType.PUBLIC; import com.android.aapt.Resources.Value; import com.android.resources.ResourceType; import com.google.common.base.MoreObjects; +import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; -import com.google.devtools.build.android.AndroidDataSerializer.SerializeEntryVisitor; import com.google.devtools.build.android.FullyQualifiedName.Factory; import com.google.devtools.build.android.FullyQualifiedName.VirtualType; import com.google.devtools.build.android.ParsedAndroidData.KeyValueConsumer; +import com.google.devtools.build.android.proto.SerializeFormat; +import com.google.devtools.build.android.proto.SerializeFormat.DataValueXml; import com.google.devtools.build.android.xml.ArrayXmlResourceValue; import com.google.devtools.build.android.xml.AttrXmlResourceValue; import com.google.devtools.build.android.xml.IdXmlResourceValue; @@ -38,6 +40,7 @@ import com.google.devtools.build.android.xml.StyleableXmlResourceValue; import com.google.protobuf.InvalidProtocolBufferException; import java.io.BufferedInputStream; import java.io.IOException; +import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -158,6 +161,15 @@ public class DataResourceXml implements DataResource { } } + @SuppressWarnings("deprecation") + // TODO(corysmith): Update proto to use get<>Map + public static DataValue from(SerializeFormat.DataValue protoValue, DataSource source) + throws InvalidProtocolBufferException { + DataValueXml xmlValue = protoValue.getXmlValue(); + return createWithNamespaces( + source, valueFromProto(xmlValue), Namespaces.from(xmlValue.getNamespace())); + } + public static DataResourceXml from( Value protoValue, DataSource source, @@ -172,6 +184,33 @@ public class DataResourceXml implements DataResource { return dataResourceXml; } + 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 PUBLIC: + return PublicXmlResourceValue.from(proto); + case STYLE: + return StyleXmlResourceValue.from(proto); + case STYLEABLE: + return StyleableXmlResourceValue.from(proto); + case RESOURCES_ATTRIBUTE: + return ResourcesAttribute.from(proto); + default: + throw new IllegalArgumentException(); + } + } + private static XmlResourceValue valueFromProto( Value proto, ResourceType resourceType, @@ -288,7 +327,7 @@ public class DataResourceXml implements DataResource { } public static DataResourceXml createWithNoNamespace(Path sourcePath, XmlResourceValue xml) { - return createWithNamespaces(sourcePath, xml, ImmutableMap.of()); + return createWithNamespaces(sourcePath, xml, ImmutableMap.<String, String>of()); } public static DataResourceXml createWithNoNamespace(DataSource source, XmlResourceValue xml) { @@ -352,8 +391,9 @@ public class DataResourceXml implements DataResource { } @Override - public SerializeEntryVisitor serializeTo(SerializeEntryVisitor visitor) { - return visitor.setXml(xml).setSource(source).setNamespaces(namespaces); + public int serializeTo(DataSourceTable sourceTable, OutputStream outStream) + throws IOException { + return xml.serializeTo(sourceTable.getSourceId(source), namespaces, outStream); } // TODO(corysmith): Clean up all the casting. The type structure is unclean. diff --git a/src/tools/android/java/com/google/devtools/build/android/DataSource.java b/src/tools/android/java/com/google/devtools/build/android/DataSource.java index 994935484a..16c79d7f81 100644 --- a/src/tools/android/java/com/google/devtools/build/android/DataSource.java +++ b/src/tools/android/java/com/google/devtools/build/android/DataSource.java @@ -17,20 +17,17 @@ import com.android.SdkConstants; import com.google.common.base.MoreObjects; import com.google.common.base.Objects; import com.google.common.collect.ImmutableSet; -import com.google.devtools.build.android.proto.SerializeFormat; import com.google.devtools.build.android.proto.SerializeFormat.ProtoSource; import java.io.BufferedInputStream; 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.attribute.BasicFileAttributeView; -import java.util.Set; /** Represents where the DataValue was derived from. */ -public class DataSource implements Comparable<DataSource>, Writeable { +public class DataSource implements Comparable<DataSource> { public static DataSource from(ProtoSource protoSource, FileSystem currentFileSystem) { Path path = currentFileSystem.getPath(protoSource.getFilename()); @@ -38,7 +35,7 @@ public class DataSource implements Comparable<DataSource>, Writeable { } public static DataSource of(Path sourcePath) { - return new DataSource(sourcePath, ImmutableSet.of()); + return new DataSource(sourcePath, ImmutableSet.<DataSource>of()); } private final Path path; @@ -101,9 +98,6 @@ public class DataSource implements Comparable<DataSource>, Writeable { } public DataSource overwrite(DataSource... sources) { - if (sources.length == 0) { - return this; - } ImmutableSet.Builder<DataSource> overridesBuilder = ImmutableSet.<DataSource>builder().addAll(this.overrides); for (DataSource dataSource : sources) { @@ -143,19 +137,4 @@ public class DataSource implements Comparable<DataSource>, Writeable { public String asConflictString() { return path.toString(); } - - @Override - public void writeTo(OutputStream out) throws IOException { - SerializeFormat.ProtoSource.newBuilder() - .setFilename(path.toString()) - .build() - .writeDelimitedTo(out); - } - - public boolean shouldFilter(Set<String> filteredResources) { - return filteredResources.contains(path.getParent().getFileName() + "/" + path.getFileName()) - // Since the filtered resources short path could match multiple sources, check to make sure - // the source doesn't exist. - && !Files.exists(path); - } } diff --git a/src/tools/android/java/com/google/devtools/build/android/DataSourceTable.java b/src/tools/android/java/com/google/devtools/build/android/DataSourceTable.java new file mode 100644 index 0000000000..ac953da711 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/DataSourceTable.java @@ -0,0 +1,131 @@ +// 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.Function; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +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 java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.FileSystem; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.NavigableMap; +import java.util.stream.Collectors; + +/** + * Tracks mappings from resource source paths (/foo/bar/res/values/colors.xml) to an ID for a more + * compact serialization format. + */ +class DataSourceTable { + + private static final Function<DataValue, DataSource> VALUE_TO_SOURCE = DataValue::source; + private final Map<DataSource, Integer> sourceTable = new LinkedHashMap<>(); + private DataSource[] idToSource; + + /** + * Creates a DataSourceTable and serialize to the given outstream. Assigns each resource source + * path a number to enable {@link #getSourceId(DataSource)} queries. + * + * @param map the final map of resources + * @param outStream stream to serialize the source table + * @param headerBuilder the header to serialize + * @throws IOException if this fails to serialize the table to the outStream + */ + public static DataSourceTable createAndWrite( + NavigableMap<DataKey, DataValue> map, OutputStream outStream, Header.Builder headerBuilder) + throws IOException { + DataSourceTable sourceTable = new DataSourceTable(); + sourceTable.writeSourceInfo(map, outStream); + sourceTable.setHeader(headerBuilder); + return sourceTable; + } + + /** Convert the absolute source path to the source table index */ + public int getSourceId(DataSource source) { + return sourceTable.get(source); + } + + private void writeSourceInfo(NavigableMap<DataKey, DataValue> map, OutputStream outStream) + throws IOException { + int sourceNumber = 0; + LinkedList<DataSource> sourceQueue = + map.values() + .stream() + .map(VALUE_TO_SOURCE) + .collect(Collectors.toCollection(LinkedList::new)); + while (!sourceQueue.isEmpty()) { + DataSource source = sourceQueue.pop(); + if (!sourceTable.containsKey(source)) { + sourceTable.put(source, sourceNumber); + ++sourceNumber; + sourceQueue.addAll(source.overrides()); + } + } + for (DataSource dataSource : sourceTable.keySet()) { + ProtoSource.newBuilder() + .setFilename(dataSource.getPath().toString()) + .addAllOverwritten(sourcesToIds(dataSource.overrides())) + .build() + .writeDelimitedTo(outStream); + } + } + + private List<Integer> sourcesToIds(ImmutableSet<DataSource> overrides) { + ImmutableList.Builder<Integer> idsBuilder = ImmutableList.builder(); + for (DataSource dataSource : overrides) { + if (!sourceTable.containsKey(dataSource)) { + throw new IllegalArgumentException( + "Cannot find data source: " + dataSource.toString() + " in " + sourceTable.keySet()); + } + idsBuilder.add(sourceTable.get(dataSource)); + } + return idsBuilder.build(); + } + + /** Fill in the serialize format header information required to deserialize */ + private Header.Builder setHeader(Header.Builder headerBuilder) { + return headerBuilder.setSourceCount(sourceTable.size()); + } + + /** Deserialize the source table and allow {@link #sourceFromId(int)} queries. */ + public static DataSourceTable read(InputStream in, FileSystem currentFileSystem, Header header) + throws IOException { + DataSourceTable sourceTable = new DataSourceTable(); + sourceTable.readSourceInfo(in, currentFileSystem, header); + return sourceTable; + } + + /** Convert the source ID to full Path */ + public DataSource sourceFromId(int sourceId) { + return idToSource[sourceId]; + } + + private void readSourceInfo(InputStream in, FileSystem currentFileSystem, Header header) + throws IOException { + int numberOfSources = header.getSourceCount(); + // Read back the sources. + idToSource = new DataSource[numberOfSources]; + for (int i = 0; i < numberOfSources; i++) { + ProtoSource protoSource = SerializeFormat.ProtoSource.parseDelimitedFrom(in); + idToSource[i] = DataSource.from(protoSource, currentFileSystem); + } + } +} 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 2a2bc51826..de9574508b 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,8 +13,8 @@ // limitations under the License. package com.google.devtools.build.android; -import com.google.devtools.build.android.AndroidDataSerializer.SerializeEntryVisitor; import java.io.IOException; +import java.io.OutputStream; /** * Represents the value associated with DataKey interface for resource and asset values. @@ -28,8 +28,9 @@ public interface DataValue { */ DataSource source(); - /** Serializes the value to the entry visitor. */ - SerializeEntryVisitor serializeTo(SerializeEntryVisitor visitor) throws IOException; + /** Serializes to a supplied stream and returns the number of bytes written. */ + int serializeTo( + DataSourceTable sourceTable, OutputStream output) throws IOException; DataValue update(DataSource source); 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 9a1792f447..3270e3ced0 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,10 +14,11 @@ package com.google.devtools.build.android; import com.google.common.base.MoreObjects; -import com.google.devtools.build.android.AndroidDataSerializer.SerializeEntryVisitor; import com.google.devtools.build.android.AndroidResourceMerger.MergingException; 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 java.util.Objects; @@ -84,8 +85,13 @@ public class DataValueFile implements DataResource, DataAsset { } @Override - public SerializeEntryVisitor serializeTo(SerializeEntryVisitor visitor) { - return visitor.setSource(source); + public int serializeTo(DataSourceTable sourceTable, OutputStream output) + throws IOException { + SerializeFormat.DataValue.Builder builder = SerializeFormat.DataValue.newBuilder(); + SerializeFormat.DataValue value = builder.setSourceId(sourceTable.getSourceId(source)).build(); + value.writeDelimitedTo(output); + return CodedOutputStream.computeUInt32SizeNoTag(value.getSerializedSize()) + + value.getSerializedSize(); } @Override diff --git a/src/tools/android/java/com/google/devtools/build/android/DependencyAndroidData.java b/src/tools/android/java/com/google/devtools/build/android/DependencyAndroidData.java index 2a770464be..b4d4b99b3f 100644 --- a/src/tools/android/java/com/google/devtools/build/android/DependencyAndroidData.java +++ b/src/tools/android/java/com/google/devtools/build/android/DependencyAndroidData.java @@ -54,7 +54,7 @@ class DependencyAndroidData extends SerializedAndroidData { // The local symbols.bin is optional -- if it is missing, we'll use the full R.txt Path rTxt = exists(fileSystem.getPath(parts[3])); ImmutableList<Path> assetDirs = - parts[1].length() == 0 ? ImmutableList.of() : splitPaths(parts[1], fileSystem); + parts[1].length() == 0 ? ImmutableList.<Path>of() : splitPaths(parts[1], fileSystem); CompiledResources compiledSymbols = null; Path symbolsBin = null; diff --git a/src/tools/android/java/com/google/devtools/build/android/DeserializationException.java b/src/tools/android/java/com/google/devtools/build/android/DeserializationException.java index 098f4f5316..8c6e5990c9 100644 --- a/src/tools/android/java/com/google/devtools/build/android/DeserializationException.java +++ b/src/tools/android/java/com/google/devtools/build/android/DeserializationException.java @@ -13,6 +13,7 @@ // limitations under the License. package com.google.devtools.build.android; +import java.io.IOException; /** Thrown when there is an error during deserialization. */ public class DeserializationException extends RuntimeException { @@ -29,7 +30,7 @@ public class DeserializationException extends RuntimeException { this.isLegacy = false; } - public DeserializationException(Throwable e) { + public DeserializationException(IOException e) { super(e); this.isLegacy = false; } 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 d0a5545b4f..9344412390 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 @@ -264,6 +264,11 @@ public class FullyQualifiedName implements DataKey { return KeyType.FULL_QUALIFIED_NAME; } + @Override + public void serializeTo(OutputStream out, int valueSize) throws IOException { + toSerializedBuilder().setValueSize(valueSize).build().writeDelimitedTo(out); + } + public SerializeFormat.DataKey.Builder toSerializedBuilder() { return SerializeFormat.DataKey.newBuilder() .setKeyPackage(pkg) @@ -272,11 +277,6 @@ public class FullyQualifiedName implements DataKey { .setKeyValue(name); } - @Override - public void writeTo(OutputStream out) throws IOException { - toSerializedBuilder().build().writeDelimitedTo(out); - } - /** The non-resource {@link Type}s of a {@link FullyQualifiedName}. */ public enum VirtualType implements Type { RESOURCES_ATTRIBUTE("<resources>", "Resources Attribute"); @@ -284,7 +284,7 @@ public class FullyQualifiedName implements DataKey { private final String name; private final String displayName; - VirtualType(String name, String displayName) { + private VirtualType(String name, String displayName) { this.name = name; this.displayName = displayName; } @@ -349,31 +349,31 @@ public class FullyQualifiedName implements DataKey { /** Represents the type of a {@link FullyQualifiedName}. */ public interface Type { - String getName(); + public String getName(); - ConcreteType getType(); + public ConcreteType getType(); - boolean isOverwritable(FullyQualifiedName fqn); + public boolean isOverwritable(FullyQualifiedName fqn); - int compareTo(Type other); + public int compareTo(Type other); @Override - boolean equals(Object obj); + public boolean equals(Object obj); @Override - int hashCode(); + public int hashCode(); @Override - String toString(); + public String toString(); /** * The category of type that a {@link Type} can be. * * <p><em>Note:</em> used for strict ordering of {@link FullyQualifiedName}s. */ - enum ConcreteType { + public enum ConcreteType { RESOURCE_TYPE, - VIRTUAL_TYPE + VIRTUAL_TYPE; } } @@ -511,7 +511,7 @@ public class FullyQualifiedName implements DataKey { // This is fragile but better than the Gradle scheme of just dropping // entire subtrees. - Builder<String> builder = ImmutableList.builder(); + Builder<String> builder = ImmutableList.<String>builder(); addIfNotNull(config.getCountryCodeQualifier(), builder); addIfNotNull(config.getNetworkCodeQualifier(), builder); if (transformedLocaleQualifiers.isEmpty()) { diff --git a/src/tools/android/java/com/google/devtools/build/android/ManifestMergerAction.java b/src/tools/android/java/com/google/devtools/build/android/ManifestMergerAction.java index b8249dcda6..a7301f1030 100644 --- a/src/tools/android/java/com/google/devtools/build/android/ManifestMergerAction.java +++ b/src/tools/android/java/com/google/devtools/build/android/ManifestMergerAction.java @@ -41,6 +41,7 @@ import java.util.logging.Logger; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerFactoryConfigurationError; @@ -161,8 +162,8 @@ public class ManifestMergerAction { private static Options options; private static Path removePermissions(Path manifest, Path outputDir) - throws IOException, ParserConfigurationException, TransformerException, - TransformerFactoryConfigurationError, SAXException { + throws IOException, ParserConfigurationException, TransformerConfigurationException, + TransformerException, TransformerFactoryConfigurationError, SAXException { DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); Document doc = docBuilder.parse(manifest.toFile()); for (String tag : PERMISSION_TAGS) { diff --git a/src/tools/android/java/com/google/devtools/build/android/PlaceholderIdFieldInitializerBuilder.java b/src/tools/android/java/com/google/devtools/build/android/PlaceholderIdFieldInitializerBuilder.java index ee1663356d..12b7abe4ae 100644 --- a/src/tools/android/java/com/google/devtools/build/android/PlaceholderIdFieldInitializerBuilder.java +++ b/src/tools/android/java/com/google/devtools/build/android/PlaceholderIdFieldInitializerBuilder.java @@ -453,7 +453,7 @@ class PlaceholderIdFieldInitializerBuilder { // The styleable array should be sorted by ID value. // Make sure that if we have android: framework attributes, their IDs are listed first. ImmutableMap<String, Integer> arrayInitMap = - arrayInitValues.orderEntriesByValue(Ordering.natural()).build(); + arrayInitValues.orderEntriesByValue(Ordering.<Integer>natural()).build(); initList.put(field, IntArrayFieldInitializer.of(arrayInitMap.values())); int index = 0; for (String attr : arrayInitMap.keySet()) { 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 fa5174222d..1801026c96 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 @@ -32,7 +32,6 @@ import java.util.Objects; * <p>Note: Assets have no qualifiers or packages. */ public class RelativeAssetPath implements DataKey { - /** A Factory that creates RelativeAssetsPath objects whose paths are relative to a given path. */ public static class Factory { private final Path assetRoot; @@ -111,11 +110,12 @@ public class RelativeAssetPath implements DataKey { } @Override - public void writeTo(OutputStream out) throws IOException { + public void serializeTo(OutputStream output, int valueSize) throws IOException { SerializeFormat.DataKey.newBuilder() .setKeyValue(relativeAssetPath.toString()) + .setValueSize(valueSize) .build() - .writeDelimitedTo(out); + .writeDelimitedTo(output); } @Override diff --git a/src/tools/android/java/com/google/devtools/build/android/ResourceProcessorBusyBox.java b/src/tools/android/java/com/google/devtools/build/android/ResourceProcessorBusyBox.java index cb1f43c320..a99da9b931 100644 --- a/src/tools/android/java/com/google/devtools/build/android/ResourceProcessorBusyBox.java +++ b/src/tools/android/java/com/google/devtools/build/android/ResourceProcessorBusyBox.java @@ -54,7 +54,7 @@ import java.util.logging.Logger; * </pre> */ public class ResourceProcessorBusyBox { - enum Tool { + static enum Tool { PACKAGE() { @Override void call(String[] args) throws Exception { diff --git a/src/tools/android/java/com/google/devtools/build/android/ResourceShrinkerAction.java b/src/tools/android/java/com/google/devtools/build/android/ResourceShrinkerAction.java index bb68ef7e65..7d9f59fb0d 100644 --- a/src/tools/android/java/com/google/devtools/build/android/ResourceShrinkerAction.java +++ b/src/tools/android/java/com/google/devtools/build/android/ResourceShrinkerAction.java @@ -325,7 +325,7 @@ public class ResourceShrinkerAction { aaptConfigOptions.splits, new MergedAndroidData( shrunkResources, resourceFiles.resolve("assets"), options.primaryManifest), - ImmutableList.of() /* libraries */, + ImmutableList.<DependencyAndroidData>of() /* libraries */, generatedSources, options.shrunkApk, null /* proguardOutput */, diff --git a/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidDirectories.java b/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidDirectories.java index 7aa3164c7c..49fdb18296 100644 --- a/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidDirectories.java +++ b/src/tools/android/java/com/google/devtools/build/android/UnvalidatedAndroidDirectories.java @@ -41,8 +41,8 @@ public class UnvalidatedAndroidDirectories { } String[] parts = text.split(":"); return new UnvalidatedAndroidDirectories( - parts.length > 0 ? splitPaths(parts[0], fileSystem) : ImmutableList.of(), - parts.length > 1 ? splitPaths(parts[1], fileSystem) : ImmutableList.of()); + parts.length > 0 ? splitPaths(parts[0], fileSystem) : ImmutableList.<Path>of(), + parts.length > 1 ? splitPaths(parts[1], fileSystem) : ImmutableList.<Path>of()); } protected static ImmutableList<Path> splitPaths(String pathsString, FileSystem fileSystem) { diff --git a/src/tools/android/java/com/google/devtools/build/android/Writeable.java b/src/tools/android/java/com/google/devtools/build/android/Writeable.java deleted file mode 100644 index d1ef46dc0d..0000000000 --- a/src/tools/android/java/com/google/devtools/build/android/Writeable.java +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2018 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.io.OutputStream; - -/** Common interface for serialization using the {@link AndroidDataSerializer}. */ -public interface Writeable { - /** Writes the current state to the {@link OutputStream}. */ - void writeTo(OutputStream out) throws IOException; -} 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 e8cab89c19..f24d6b271a 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,8 +13,12 @@ // limitations under the License. package com.google.devtools.build.android; +import com.google.devtools.build.android.xml.Namespaces; +import java.io.IOException; +import java.io.OutputStream; + /** An {@link XmlResourceValue} is extracted from xml files in the resource 'values' directory. */ -public interface XmlResourceValue extends Writeable { +public interface XmlResourceValue { /** * Each XmlValue is expected to write a valid representation in xml to the writer. * @@ -24,6 +28,9 @@ public interface XmlResourceValue extends Writeable { */ void write(FullyQualifiedName key, DataSource source, AndroidDataWritingVisitor mergedDataWriter); + /** Serializes the resource value to the OutputStream and returns the bytes written. */ + int serializeTo(int sourceId, Namespaces namespaces, OutputStream out) throws IOException; + /** * Combines these xml values together and returns a single value. * 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 20df22251b..1ea38826bf 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 @@ -20,18 +20,15 @@ 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.ArrayXmlResourceValue; import com.google.devtools.build.android.xml.AttrXmlResourceValue; import com.google.devtools.build.android.xml.IdXmlResourceValue; import com.google.devtools.build.android.xml.Namespaces; import com.google.devtools.build.android.xml.PluralXmlResourceValue; import com.google.devtools.build.android.xml.PublicXmlResourceValue; -import com.google.devtools.build.android.xml.ResourcesAttribute; import com.google.devtools.build.android.xml.SimpleXmlResourceValue; import com.google.devtools.build.android.xml.StyleXmlResourceValue; import com.google.devtools.build.android.xml.StyleableXmlResourceValue; import com.google.protobuf.CodedOutputStream; -import com.google.protobuf.InvalidProtocolBufferException; import java.io.IOException; import java.io.OutputStream; import java.io.StringWriter; @@ -446,35 +443,4 @@ public class XmlResourceValues { public static boolean isSkip(StartElement start) { return isTag(start, TAG_SKIP); } - - /** Creates the value from a proto. */ - public static XmlResourceValue valueFromProto(SerializeFormat.DataValueXml proto) { - try { - 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 PUBLIC: - return PublicXmlResourceValue.from(proto); - case STYLE: - return StyleXmlResourceValue.from(proto); - case STYLEABLE: - return StyleableXmlResourceValue.from(proto); - case RESOURCES_ATTRIBUTE: - return ResourcesAttribute.from(proto); - default: - throw new IllegalArgumentException(); - } - } catch (InvalidProtocolBufferException e) { - throw new DeserializationException(e); - } - } } diff --git a/src/tools/android/java/com/google/devtools/build/android/proto/serialize_format.proto b/src/tools/android/java/com/google/devtools/build/android/proto/serialize_format.proto index 9412cea29f..b87d5fee83 100644 --- a/src/tools/android/java/com/google/devtools/build/android/proto/serialize_format.proto +++ b/src/tools/android/java/com/google/devtools/build/android/proto/serialize_format.proto @@ -24,29 +24,25 @@ option java_package = "com.google.devtools.build.android.proto"; // See com.google.devtools.build.android.AndroidDataSerializer for details on // how these messages are used. message Header { + // The number of entries stored in a serialized buffer. + optional uint32 entry_count = 1; // The number of ProtoSource entries. - optional uint32 source_count = 1; - // The number of keys in the serialized buffer. - optional uint32 key_count = 2; - // The number of values in the serialized buffer. - optional uint32 value_count = 3; - // The number of xml definitions in the serialized buffer. - optional uint32 xml_count = 4; - // The number of xml namespace definitions in the serialized buffer. - optional uint32 namespaces_count = 5; + optional uint32 source_count = 2; } // The serialized format for a DataKey. message DataKey { // Used for both the FullyQualifiedName name and RelativeAssetPath path - // Required - optional string key_value = 1; + optional string key_value = 2; // The resource type for FullyQualifiedNames - optional string resource_type = 2; - optional string key_package = 3; - repeated string qualifiers = 4; + optional string resource_type = 3; + optional string key_package = 4; + repeated string qualifiers = 5; + // The size of the associated value. Useful for calculating an offset. + // Required + optional int32 value_size = 6; // Whether this DataKey is a reference to another DataKey. - optional bool reference = 5; + optional bool reference = 7; } // The serialized format for a DataValue. @@ -55,41 +51,30 @@ message DataValue { // Required optional uint32 source_id = 1; - // Index of stored xml value - optional uint32 xml_id = 2; - - // Index of stored key. - // Required - optional uint32 key_id = 3; - - // Sources that have been overwritten. - repeated uint32 overwritten_source_id = 4; - - optional uint32 namespace_id = 5; + // If xml_value is defined it's an xml value, otherwise, it's a file value. + optional DataValueXml xml_value= 2; } // A container for all the source information to be persisted. message ProtoSource { // Required optional string filename = 1; -} - -message XmlNamespaces { - map<string, string> namespace = 1; + // The indexes of sources this source replaces. + repeated uint32 overwritten = 2; } // The container for a serialized xml value. message DataValueXml { enum XmlType { - ARRAY = 1; - ATTR = 2; - ID = 3; - PLURAL = 4; - PUBLIC = 5; - SIMPLE = 6; - STYLEABLE = 7; - STYLE = 8; - RESOURCES_ATTRIBUTE = 9; + ARRAY = 0; + ATTR = 1; + ID = 2; + PLURAL = 3; + PUBLIC = 4; + SIMPLE = 5; + STYLEABLE = 6; + STYLE = 7; + RESOURCES_ATTRIBUTE = 8; } optional XmlType type = 1; @@ -100,4 +85,5 @@ message DataValueXml { optional string value_type = 6; repeated DataKey references = 7; map<string, string> attribute = 8; + map<string, string> namespace = 9; } 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 f977c7b315..fb74fd2208 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 @@ -29,8 +29,6 @@ import com.google.devtools.build.android.FullyQualifiedName; import com.google.devtools.build.android.XmlResourceValue; import com.google.devtools.build.android.XmlResourceValues; import com.google.devtools.build.android.proto.SerializeFormat; -import com.google.devtools.build.android.proto.SerializeFormat.DataValueXml; -import com.google.devtools.build.android.proto.SerializeFormat.DataValueXml.XmlType; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; @@ -47,15 +45,14 @@ import javax.xml.stream.events.XMLEvent; /** * Represents an Android resource array. * - * <p>There are two flavors of Android Resource arrays: - * + * 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>Integer array (http://developer.android.com/guide/topics/resources/more-resources - * .html#IntegerArray) which are indicated by <integer-array> tag. - * <li>String array (http://developer.android.com/guide/topics/resources/string-resource - * .html#StringArray) which are indicated by <string-array> tag. + * <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. @@ -65,19 +62,9 @@ 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"); - - @Override - public void writeTo(OutputStream out) throws IOException { - DataValueXml.newBuilder() - .addAllListValue(values) - .setType(XmlType.ARRAY) - .putAllAttribute(attributes) - .setValueType(arrayType.toString()) - .build() - .writeDelimitedTo(out); - } - - /** Enumerates the different types of array parentTags. */ + /** + * Enumerates the different types of array parentTags. + */ public enum ArrayType { INTEGER_ARRAY(TAG_INTEGER_ARRAY), ARRAY(TAG_ARRAY), @@ -118,7 +105,7 @@ public class ArrayXmlResourceValue implements XmlResourceValue { } public static XmlResourceValue of(ArrayType arrayType, List<String> values) { - return of(arrayType, values, ImmutableMap.of()); + return of(arrayType, values, ImmutableMap.<String, String>of()); } public static XmlResourceValue of( @@ -151,20 +138,20 @@ public class ArrayXmlResourceValue implements XmlResourceValue { } } - return of(ArrayType.ARRAY, items, ImmutableMap.of()); + return of( + ArrayType.ARRAY, + items, + ImmutableMap.of()); } @Override public void write( FullyQualifiedName key, DataSource source, AndroidDataWritingVisitor mergedDataWriter) { - ValuesResourceDefinition definition = - mergedDataWriter - .define(key) - .derivedFrom(source) - .startTag(arrayType.tagName) - .named(key) - .addAttributesFrom(attributes.entrySet()) - .closeTag(); + ValuesResourceDefinition definition = mergedDataWriter.define(key).derivedFrom(source) + .startTag(arrayType.tagName) + .named(key) + .addAttributesFrom(attributes.entrySet()) + .closeTag(); for (String value : values) { definition = definition @@ -178,6 +165,21 @@ public class ArrayXmlResourceValue implements XmlResourceValue { } @Override + public int serializeTo(int sourceId, Namespaces namespaces, OutputStream output) + throws IOException { + return XmlResourceValues.serializeProtoDataValue( + output, + XmlResourceValues.newSerializableDataValueBuilder(sourceId) + .setXmlValue( + SerializeFormat.DataValueXml.newBuilder() + .addAllListValue(values) + .setType(SerializeFormat.DataValueXml.XmlType.ARRAY) + .putAllNamespace(namespaces.asMap()) + .putAllAttribute(attributes) + .setValueType(arrayType.toString()))); + } + + @Override public int hashCode() { return Objects.hash(arrayType, values, attributes); } 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 96ac31c947..f2645ac0a3 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 @@ -404,16 +404,23 @@ public class AttrXmlResourceValue implements XmlResourceValue { } } + @SuppressWarnings("deprecation") @Override - public void writeTo(OutputStream out) throws IOException { + public int serializeTo(int sourceId, Namespaces namespaces, OutputStream output) + throws IOException { + SerializeFormat.DataValue.Builder builder = + XmlResourceValues.newSerializableDataValueBuilder(sourceId); SerializeFormat.DataValueXml.Builder xmlValueBuilder = SerializeFormat.DataValueXml.newBuilder(); - xmlValueBuilder.setType(SerializeFormat.DataValueXml.XmlType.ATTR); + xmlValueBuilder + .setType(SerializeFormat.DataValueXml.XmlType.ATTR) + .putAllNamespace(namespaces.asMap()); for (Entry<String, ResourceXmlAttrValue> entry : formats.entrySet()) { xmlValueBuilder.putMappedXmlValue( - entry.getKey(), entry.getValue().appendTo(SerializeFormat.DataValueXml.newBuilder())); + entry.getKey(), entry.getValue().appendTo(builder.getXmlValueBuilder())); } - xmlValueBuilder.build().writeDelimitedTo(out); + builder.setXmlValue(xmlValueBuilder); + return XmlResourceValues.serializeProtoDataValue(output, builder); } @Override 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 e3fe65aa02..c3fb2faefa 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 @@ -20,8 +20,11 @@ import com.google.devtools.build.android.AndroidResourceSymbolSink; import com.google.devtools.build.android.DataSource; import com.google.devtools.build.android.FullyQualifiedName; import com.google.devtools.build.android.XmlResourceValue; +import com.google.devtools.build.android.XmlResourceValues; import com.google.devtools.build.android.proto.SerializeFormat; +import com.google.devtools.build.android.proto.SerializeFormat.DataValueXml.Builder; import com.google.devtools.build.android.proto.SerializeFormat.DataValueXml.XmlType; +import com.google.protobuf.CodedOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.Objects; @@ -91,8 +94,20 @@ public class IdXmlResourceValue implements XmlResourceValue { } @Override - public void writeTo(OutputStream out) throws IOException { - SerializeFormat.DataValueXml.newBuilder().setType(XmlType.ID).build().writeDelimitedTo(out); + public int serializeTo(int sourceId, Namespaces namespaces, OutputStream output) + throws IOException { + Builder xmlValue = + SerializeFormat.DataValueXml.newBuilder() + .setType(XmlType.ID) + .putAllNamespace(namespaces.asMap()); + if (value != null) { + xmlValue.setValue(value); + } + SerializeFormat.DataValue dataValue = + XmlResourceValues.newSerializableDataValueBuilder(sourceId).setXmlValue(xmlValue).build(); + dataValue.writeDelimitedTo(output); + return CodedOutputStream.computeUInt32SizeNoTag(dataValue.getSerializedSize()) + + dataValue.getSerializedSize(); } @Override diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/Namespaces.java b/src/tools/android/java/com/google/devtools/build/android/xml/Namespaces.java index 10da5ff225..82e8810139 100644 --- a/src/tools/android/java/com/google/devtools/build/android/xml/Namespaces.java +++ b/src/tools/android/java/com/google/devtools/build/android/xml/Namespaces.java @@ -16,13 +16,8 @@ package com.google.devtools.build.android.xml; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableMap; import com.google.devtools.build.android.DataResourceXml; -import com.google.devtools.build.android.Writeable; 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.XmlNamespaces; -import java.io.IOException; -import java.io.OutputStream; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -44,23 +39,11 @@ import javax.xml.stream.events.StartElement; * resources tag to combining multiple {@link DataResourceXml}s, the Namespaces must be tracked and * kept with each value. */ -public class Namespaces implements Iterable<Entry<String, String>>, Writeable { +public class Namespaces implements Iterable<Entry<String, String>> { private static final Logger logger = Logger.getLogger(Namespaces.class.getCanonicalName()); private static final Namespaces EMPTY_INSTANCE = new Namespaces(ImmutableMap.<String, String>of()); - @Override - public void writeTo(OutputStream out) throws IOException { - SerializeFormat.XmlNamespaces.newBuilder() - .putAllNamespace(prefixToUri) - .build() - .writeDelimitedTo(out); - } - - public static Namespaces fromProto(XmlNamespaces xmlNamespaces) { - return from(xmlNamespaces.getNamespaceMap()); - } - /** Collects prefix and uri pairs from elements. */ public static class Collector { private Map<String, String> prefixToUri = new HashMap<>(); 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 7c1b6532e3..b912cc141e 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 @@ -24,8 +24,10 @@ import com.google.devtools.build.android.AndroidResourceSymbolSink; import com.google.devtools.build.android.DataSource; import com.google.devtools.build.android.FullyQualifiedName; import com.google.devtools.build.android.XmlResourceValue; +import com.google.devtools.build.android.XmlResourceValues; import com.google.devtools.build.android.proto.SerializeFormat; import com.google.devtools.build.android.proto.SerializeFormat.DataValueXml.XmlType; +import com.google.protobuf.CodedOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.HashMap; @@ -155,13 +157,23 @@ public class PluralXmlResourceValue implements XmlResourceValue { } @Override - public void writeTo(OutputStream out) throws IOException { - SerializeFormat.DataValueXml.newBuilder() - .setType(XmlType.PLURAL) - .putAllAttribute(attributes) - .putAllMappedStringValue(values) - .build() - .writeDelimitedTo(out); + public int serializeTo(int sourceId, Namespaces namespaces, OutputStream output) + throws IOException { + SerializeFormat.DataValue.Builder builder = + XmlResourceValues.newSerializableDataValueBuilder(sourceId); + SerializeFormat.DataValue value = + builder + .setXmlValue( + builder + .getXmlValueBuilder() + .setType(XmlType.PLURAL) + .putAllNamespace(namespaces.asMap()) + .putAllAttribute(attributes) + .putAllMappedStringValue(values)) + .build(); + value.writeDelimitedTo(output); + return CodedOutputStream.computeUInt32SizeNoTag(value.getSerializedSize()) + + value.getSerializedSize(); } @Override diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/PublicXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/PublicXmlResourceValue.java index c1e3eb40a0..af4184d88a 100644 --- a/src/tools/android/java/com/google/devtools/build/android/xml/PublicXmlResourceValue.java +++ b/src/tools/android/java/com/google/devtools/build/android/xml/PublicXmlResourceValue.java @@ -19,11 +19,13 @@ import com.google.common.base.MoreObjects; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; import com.google.devtools.build.android.AndroidDataWritingVisitor; import com.google.devtools.build.android.AndroidResourceSymbolSink; import com.google.devtools.build.android.DataSource; import com.google.devtools.build.android.FullyQualifiedName; import com.google.devtools.build.android.XmlResourceValue; +import com.google.devtools.build.android.XmlResourceValues; import com.google.devtools.build.android.proto.SerializeFormat; import java.io.IOException; import java.io.OutputStream; @@ -31,7 +33,6 @@ import java.util.EnumMap; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; -import java.util.stream.Collectors; /** * Represents an Android resource <public> xml tag. @@ -138,19 +139,23 @@ public class PublicXmlResourceValue implements XmlResourceValue { } @Override - public void writeTo(OutputStream out) throws IOException { - SerializeFormat.DataValueXml.newBuilder() - .setType(SerializeFormat.DataValueXml.XmlType.PUBLIC) - .putAllMappedStringValue( - typeToId - .entrySet() - .stream() - .collect( - Collectors.toMap( - e -> e.getKey().toString(), - e -> e.getValue().transform(Object::toString).or(MISSING_ID_VALUE)))) - .build() - .writeDelimitedTo(out); + public int serializeTo(int sourceId, Namespaces namespaces, OutputStream output) + throws IOException { + Map<String, String> assignments = Maps.newLinkedHashMapWithExpectedSize(typeToId.size()); + for (Entry<ResourceType, Optional<Integer>> entry : typeToId.entrySet()) { + Optional<Integer> value = entry.getValue(); + String stringValue = value.isPresent() ? value.get().toString() : MISSING_ID_VALUE; + assignments.put(entry.getKey().toString(), stringValue); + } + SerializeFormat.DataValue.Builder builder = + XmlResourceValues.newSerializableDataValueBuilder(sourceId); + builder.setXmlValue( + builder + .getXmlValueBuilder() + .setType(SerializeFormat.DataValueXml.XmlType.PUBLIC) + .putAllNamespace(namespaces.asMap()) + .putAllMappedStringValue(assignments)); + return XmlResourceValues.serializeProtoDataValue(output, builder); } @Override @@ -173,7 +178,7 @@ public class PublicXmlResourceValue implements XmlResourceValue { } return of(combined); } - + @Override public String asConflictStringWith(DataSource source) { return source.asConflictString(); diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/ResourcesAttribute.java b/src/tools/android/java/com/google/devtools/build/android/xml/ResourcesAttribute.java index 75a6c0207e..a54667ecdb 100644 --- a/src/tools/android/java/com/google/devtools/build/android/xml/ResourcesAttribute.java +++ b/src/tools/android/java/com/google/devtools/build/android/xml/ResourcesAttribute.java @@ -21,7 +21,9 @@ import com.google.devtools.build.android.AndroidResourceSymbolSink; import com.google.devtools.build.android.DataSource; import com.google.devtools.build.android.FullyQualifiedName; import com.google.devtools.build.android.XmlResourceValue; +import com.google.devtools.build.android.XmlResourceValues; import com.google.devtools.build.android.proto.SerializeFormat; +import com.google.devtools.build.android.proto.SerializeFormat.DataValueXml.Builder; import com.google.errorprone.annotations.Immutable; import java.io.IOException; import java.io.OutputStream; @@ -38,15 +40,13 @@ public class ResourcesAttribute implements XmlResourceValue { public String combine(String first, String second); } - private static final Combiner COMMA_SEPARATED_COMBINER = - new Combiner() { - private final Joiner joiner = Joiner.on(','); - - @Override - public String combine(String first, String second) { - return joiner.join(first, second); - } - }; + private static final Combiner COMMA_SEPARATED_COMBINER = new Combiner() { + private final Joiner joiner = Joiner.on(','); + @Override + public String combine(String first, String second) { + return joiner.join(first, second); + } + }; /** * Represents the semantic meaning of an xml attribute and how it is combined with other @@ -92,7 +92,9 @@ public class ResourcesAttribute implements XmlResourceValue { Map.Entry<String, String> attribute = Iterables.getOnlyElement(proto.getAttributeMap().entrySet()); return new ResourcesAttribute( - AttributeType.valueOf(proto.getValueType()), attribute.getKey(), attribute.getValue()); + AttributeType.valueOf(proto.getValueType()), + attribute.getKey(), + attribute.getValue()); } private final AttributeType type; @@ -112,13 +114,19 @@ public class ResourcesAttribute implements XmlResourceValue { } @Override - public void writeTo(OutputStream out) throws IOException { - SerializeFormat.DataValueXml.newBuilder() - .setType(SerializeFormat.DataValueXml.XmlType.RESOURCES_ATTRIBUTE) - .setValueType(type.name()) - .putAttribute(name, value) - .build() - .writeDelimitedTo(out); + public int serializeTo(int sourceId, Namespaces namespaces, OutputStream output) + throws IOException { + SerializeFormat.DataValue.Builder builder = + XmlResourceValues.newSerializableDataValueBuilder(sourceId); + Builder xmlValueBuilder = + builder + .getXmlValueBuilder() + .putAllNamespace(namespaces.asMap()) + .setType(SerializeFormat.DataValueXml.XmlType.RESOURCES_ATTRIBUTE) + .setValueType(type.name()) + .putAttribute(name, value); + builder.setXmlValue(xmlValueBuilder); + return XmlResourceValues.serializeProtoDataValue(output, builder); } @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 55bef85bc1..32147cd49c 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 @@ -27,10 +27,9 @@ import com.google.devtools.build.android.AndroidResourceSymbolSink; import com.google.devtools.build.android.DataSource; import com.google.devtools.build.android.FullyQualifiedName; import com.google.devtools.build.android.XmlResourceValue; +import com.google.devtools.build.android.XmlResourceValues; import com.google.devtools.build.android.proto.SerializeFormat; -import com.google.devtools.build.android.proto.SerializeFormat.DataValueXml; import com.google.devtools.build.android.proto.SerializeFormat.DataValueXml.Builder; -import com.google.devtools.build.android.proto.SerializeFormat.DataValueXml.XmlType; import java.io.IOException; import java.io.OutputStream; import java.util.Arrays; @@ -42,7 +41,8 @@ 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 + * <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 @@ -66,27 +66,106 @@ public class SimpleXmlResourceValue implements XmlResourceValue { static final QName TAG_RAW = QName.valueOf("raw"); static final QName TAG_STRING = QName.valueOf("string"); - /** Provides an enumeration resource type. */ + /** Provides an enumeration resource type and simple value validation. */ public enum Type { - BOOL(TAG_BOOL), - COLOR(TAG_COLOR), - DIMEN(TAG_DIMEN), - DRAWABLE(TAG_DRAWABLE), - FRACTION(TAG_FRACTION), - INTEGER(TAG_INTEGER), - ITEM(TAG_ITEM), - LAYOUT(TAG_LAYOUT), - MENU(TAG_MENU), - MIPMAP(TAG_MIPMAP), - PUBLIC(TAG_PUBLIC), - RAW(TAG_RAW), - STRING(TAG_STRING); + BOOL(TAG_BOOL) { + @Override + public boolean validate(String value) { + final String cleanValue = value.toLowerCase().trim(); + return "true".equals(cleanValue) || "false".equals(cleanValue); + } + }, + COLOR(TAG_COLOR) { + @Override + public boolean validate(String value) { + // TODO(corysmith): Validate the hex color. + return true; + } + }, + DIMEN(TAG_DIMEN) { + @Override + public boolean validate(String value) { + // TODO(corysmith): Validate the dimension type. + return true; + } + }, + DRAWABLE(TAG_DRAWABLE) { + @Override + public boolean validate(String value) { + // TODO(corysmith): Validate the drawable type. + return true; + } + }, + FRACTION(TAG_FRACTION) { + @Override + public boolean validate(String value) { + // TODO(corysmith): Validate the fraction type. + return true; + } + }, + INTEGER(TAG_INTEGER) { + @Override + public boolean validate(String value) { + // TODO(corysmith): Validate the integer type. + return true; + } + }, + ITEM(TAG_ITEM) { + @Override + public boolean validate(String value) { + // TODO(corysmith): Validate the item type. + return true; + } + }, + LAYOUT(TAG_LAYOUT) { + @Override + public boolean validate(String value) { + // TODO(corysmith): Validate the layout type. + return true; + } + }, + MENU(TAG_MENU) { + @Override + public boolean validate(String value) { + // TODO(corysmith): Validate the menu type. + return true; + } + }, + MIPMAP(TAG_MIPMAP) { + @Override + public boolean validate(String value) { + // TODO(corysmith): Validate the mipmap type. + return true; + } + }, + PUBLIC(TAG_PUBLIC) { + @Override + public boolean validate(String value) { + // TODO(corysmith): Validate the public type. + return true; + } + }, + RAW(TAG_RAW) { + @Override + public boolean validate(String value) { + // TODO(corysmith): Validate the raw type. + return true; + } + }, + STRING(TAG_STRING) { + @Override + public boolean validate(String value) { + return true; + } + }; private final QName tagName; Type(QName tagName) { this.tagName = tagName; } + abstract boolean validate(String value); + public static Type from(ResourceType resourceType) { for (Type valueType : values()) { if (valueType.tagName.getLocalPart().equals(resourceType.getName())) { @@ -98,7 +177,8 @@ public class SimpleXmlResourceValue implements XmlResourceValue { throw new IllegalArgumentException( String.format( "%s resource type not found in available types: %s", - resourceType, Arrays.toString(values()))); + resourceType, + Arrays.toString(values()))); } } @@ -120,7 +200,8 @@ public class SimpleXmlResourceValue implements XmlResourceValue { return of(Type.ITEM, ImmutableMap.of("type", resourceType.getName(), "format", format), value); } - public static XmlResourceValue itemWithValue(ResourceType resourceType, String value) { + public static XmlResourceValue itemWithValue( + ResourceType resourceType, String value) { return of(Type.ITEM, ImmutableMap.of("type", resourceType.getName()), value); } @@ -184,11 +265,11 @@ public class SimpleXmlResourceValue implements XmlResourceValue { String.format(";%s,%d,%d", span.getTag(), span.getFirstChar(), span.getLastChar())); } stringValue = stringBuilder.toString(); - } else if ((resourceType == ResourceType.COLOR || resourceType == ResourceType.DRAWABLE) - && item.hasPrim()) { + } else if ((resourceType == ResourceType.COLOR + || resourceType == ResourceType.DRAWABLE) && item.hasPrim()) { stringValue = String.format("#%1$8s", Integer.toHexString(item.getPrim().getData())).replace(' ', '0'); - } else if (resourceType == ResourceType.INTEGER && item.hasPrim()) { + } else if (resourceType == ResourceType.INTEGER && item.hasPrim()){ stringValue = Integer.toString(item.getPrim().getData()); } else if (resourceType == ResourceType.BOOL && item.hasPrim()) { stringValue = item.getPrim().getData() == 0 ? "false" : "true"; @@ -201,7 +282,10 @@ public class SimpleXmlResourceValue implements XmlResourceValue { String.format("'%s' is not a valid resource type.", resourceType)); } - return of(Type.valueOf(resourceType.toString().toUpperCase()), ImmutableMap.of(), stringValue); + return of( + Type.valueOf(resourceType.toString().toUpperCase()), + ImmutableMap.of(), + stringValue); } @Override @@ -210,20 +294,23 @@ public class SimpleXmlResourceValue implements XmlResourceValue { } @Override - public void writeTo(OutputStream out) throws IOException { - final Builder builder = - DataValueXml.newBuilder() - .setType(XmlType.SIMPLE) - .setValueType(valueType.name()) + public int serializeTo(int sourceId, Namespaces namespaces, OutputStream output) + throws IOException { + SerializeFormat.DataValue.Builder builder = + XmlResourceValues.newSerializableDataValueBuilder(sourceId); + Builder xmlValueBuilder = + builder + .getXmlValueBuilder() + .putAllNamespace(namespaces.asMap()) + .setType(SerializeFormat.DataValueXml.XmlType.SIMPLE) // TODO(corysmith): Find a way to avoid writing strings to the serialized format // it's inefficient use of space and costs more when deserializing. .putAllAttribute(attributes); - if (value != null) { - builder.setValue(value); + xmlValueBuilder.setValue(value); } - - builder.build().writeDelimitedTo(out); + builder.setXmlValue(xmlValueBuilder.setValueType(valueType.name())); + return XmlResourceValues.serializeProtoDataValue(output, builder); } @Override @@ -255,7 +342,7 @@ public class SimpleXmlResourceValue implements XmlResourceValue { public XmlResourceValue combineWith(XmlResourceValue value) { throw new IllegalArgumentException(this + " is not a combinable resource."); } - + @Override public String asConflictStringWith(DataSource source) { if (value != null) { 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 6929c9d588..5cd0f6e0a5 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 @@ -24,6 +24,7 @@ import com.google.devtools.build.android.AndroidResourceSymbolSink; import com.google.devtools.build.android.DataSource; import com.google.devtools.build.android.FullyQualifiedName; import com.google.devtools.build.android.XmlResourceValue; +import com.google.devtools.build.android.XmlResourceValues; import com.google.devtools.build.android.proto.SerializeFormat; import com.google.devtools.build.android.proto.SerializeFormat.DataValueXml.XmlType; import java.io.IOException; @@ -38,7 +39,8 @@ 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 + * <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 @@ -152,15 +154,19 @@ public class StyleXmlResourceValue implements XmlResourceValue { } @Override - public void writeTo(OutputStream out) throws IOException { + public int serializeTo(int sourceId, Namespaces namespaces, OutputStream output) + throws IOException { SerializeFormat.DataValueXml.Builder xmlValueBuilder = SerializeFormat.DataValueXml.newBuilder() .setType(XmlType.STYLE) + .putAllNamespace(namespaces.asMap()) .putAllMappedStringValue(values); if (parent != null) { xmlValueBuilder.setValue(parent); } - xmlValueBuilder.build().writeDelimitedTo(out); + return XmlResourceValues.serializeProtoDataValue( + output, + XmlResourceValues.newSerializableDataValueBuilder(sourceId).setXmlValue(xmlValueBuilder)); } @Override @@ -189,7 +195,7 @@ public class StyleXmlResourceValue implements XmlResourceValue { public XmlResourceValue combineWith(XmlResourceValue value) { throw new IllegalArgumentException(this + " is not a combinable resource."); } - + @Override public String asConflictStringWith(DataSource source) { return source.asConflictString(); 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 5006600a48..c9661690cf 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 @@ -27,6 +27,7 @@ import com.google.devtools.build.android.AndroidResourceSymbolSink; import com.google.devtools.build.android.DataSource; import com.google.devtools.build.android.FullyQualifiedName; import com.google.devtools.build.android.XmlResourceValue; +import com.google.devtools.build.android.XmlResourceValues; import com.google.devtools.build.android.proto.SerializeFormat; import com.google.devtools.build.android.proto.SerializeFormat.DataValueXml.XmlType; import java.io.IOException; @@ -37,7 +38,6 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; -import java.util.stream.Collectors; import javax.annotation.concurrent.Immutable; /** @@ -65,11 +65,22 @@ public class StyleableXmlResourceValue implements XmlResourceValue { static final Function<Entry<FullyQualifiedName, Boolean>, SerializeFormat.DataKey> FULLY_QUALIFIED_NAME_TO_DATA_KEY = - input -> input.getKey().toSerializedBuilder().setReference(input.getValue()).build(); + new Function<Entry<FullyQualifiedName, Boolean>, SerializeFormat.DataKey>() { + @Override + public SerializeFormat.DataKey apply(Entry<FullyQualifiedName, Boolean> input) { + return input.getKey().toSerializedBuilder().setReference(input.getValue()).build(); + } + }; static final Function<SerializeFormat.DataKey, Entry<FullyQualifiedName, Boolean>> DATA_KEY_TO_FULLY_QUALIFIED_NAME = - input -> new SimpleEntry<>(FullyQualifiedName.fromProto(input), input.getReference()); + new Function<SerializeFormat.DataKey, Entry<FullyQualifiedName, Boolean>>() { + @Override + public Entry<FullyQualifiedName, Boolean> apply(SerializeFormat.DataKey input) { + FullyQualifiedName key = FullyQualifiedName.fromProto(input); + return new SimpleEntry<FullyQualifiedName, Boolean>(key, input.getReference()); + } + }; private final ImmutableMap<FullyQualifiedName, Boolean> attrs; @@ -134,17 +145,17 @@ public class StyleableXmlResourceValue implements XmlResourceValue { } @Override - public void writeTo(OutputStream out) throws IOException { - SerializeFormat.DataValueXml.newBuilder() - .setType(XmlType.STYLEABLE) - .addAllReferences( - attrs - .entrySet() - .stream() - .map(FULLY_QUALIFIED_NAME_TO_DATA_KEY) - .collect(Collectors.toSet())) - .build() - .writeDelimitedTo(out); + public int serializeTo(int sourceId, Namespaces namespaces, OutputStream output) + throws IOException { + return XmlResourceValues.serializeProtoDataValue( + output, + XmlResourceValues.newSerializableDataValueBuilder(sourceId) + .setXmlValue( + SerializeFormat.DataValueXml.newBuilder() + .setType(XmlType.STYLEABLE) + .putAllNamespace(namespaces.asMap()) + .addAllReferences( + Iterables.transform(attrs.entrySet(), FULLY_QUALIFIED_NAME_TO_DATA_KEY)))); } public static XmlResourceValue from(SerializeFormat.DataValueXml proto) { @@ -191,12 +202,12 @@ public class StyleableXmlResourceValue implements XmlResourceValue { /** * Combines this instance with another {@link StyleableXmlResourceValue}. * - * <p>Defining two Styleables (undocumented in the official Android Docs) with the same {@link - * FullyQualifiedName} results in a single Styleable containing a union of all the attribute - * references. + * Defining two Styleables (undocumented in the official Android Docs) with the same + * {@link FullyQualifiedName} results in a single Styleable containing a union of all the + * attribute references. * - * @param value Another {@link StyleableXmlResourceValue} with the same {@link - * FullyQualifiedName}. + * @param value Another {@link StyleableXmlResourceValue} with the same + * {@link FullyQualifiedName}. * @return {@link StyleableXmlResourceValue} containing a sorted union of the attribute * references. * @throws IllegalArgumentException if value is not an {@link StyleableXmlResourceValue}. |