aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/tools/android/java/com/google/devtools/build/android/AndroidParsedDataDeserializer.java
diff options
context:
space:
mode:
authorGravatar Googler <noreply@google.com>2017-11-15 11:55:15 -0800
committerGravatar Copybara-Service <copybara-piper@google.com>2017-11-15 11:57:13 -0800
commit7925d5b265249466bff385602e94509a05de6870 (patch)
tree9543038c23171dd93261a792a6f0571591e3cb09 /src/tools/android/java/com/google/devtools/build/android/AndroidParsedDataDeserializer.java
parent1e0b7cb49b5d22f72d9e32018d15972a9f28878c (diff)
Create merge action and data deserializer for aapt2 compiled resources.
RELNOTES: None PiperOrigin-RevId: 175858467
Diffstat (limited to 'src/tools/android/java/com/google/devtools/build/android/AndroidParsedDataDeserializer.java')
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidParsedDataDeserializer.java215
1 files changed, 215 insertions, 0 deletions
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
new file mode 100644
index 0000000000..71787b4b39
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/AndroidParsedDataDeserializer.java
@@ -0,0 +1,215 @@
+// Copyright 2017 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android;
+
+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;
+import com.google.devtools.build.android.AndroidResourceMerger.MergingException;
+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 java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
+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;
+
+/** Deserializes {@link DataKey}, {@link DataValue} entries from a binary file. */
+public class AndroidParsedDataDeserializer implements AndroidDataDeserializer {
+ /** Task to deserialize resources from a path. */
+ static final class Deserialize implements Callable<Boolean> {
+
+ private final Path symbolPath;
+
+ private final Builder finalDataBuilder;
+ private final AndroidParsedDataDeserializer deserializer;
+
+ private Deserialize(
+ AndroidParsedDataDeserializer deserializer, Path symbolPath, Builder finalDataBuilder) {
+ this.deserializer = deserializer;
+ this.symbolPath = symbolPath;
+ this.finalDataBuilder = finalDataBuilder;
+ }
+
+ @Override
+ public Boolean call() throws Exception {
+ final Builder parsedDataBuilder = ParsedAndroidData.Builder.newBuilder();
+ deserializer.read(symbolPath, parsedDataBuilder.consumers());
+ // The builder isn't threadsafe, so synchronize the copyTo call.
+ synchronized (finalDataBuilder) {
+ // All the resources are sorted before writing, so they can be aggregated in
+ // whatever order here.
+ parsedDataBuilder.copyTo(finalDataBuilder);
+ }
+ return Boolean.TRUE;
+ }
+ }
+
+ private static final Logger logger =
+ Logger.getLogger(AndroidParsedDataDeserializer.class.getName());
+
+ private final ImmutableSet<String> filteredResources;
+
+ /**
+ * @param filteredResources resources that were filtered out of this target and should be ignored
+ * if they are referenced in symbols files.
+ */
+ public static AndroidParsedDataDeserializer withFilteredResources(
+ Collection<String> filteredResources) {
+ return new AndroidParsedDataDeserializer(ImmutableSet.copyOf(filteredResources));
+ }
+
+ public static AndroidParsedDataDeserializer create() {
+ return new AndroidParsedDataDeserializer(ImmutableSet.<String>of());
+ }
+
+ private AndroidParsedDataDeserializer(ImmutableSet<String> filteredResources) {
+ this.filteredResources = filteredResources;
+ }
+
+ /**
+ * Reads the serialized {@link DataKey} and {@link DataValue} to the {@link KeyValueConsumers}.
+ *
+ * @param inPath The path to the serialized protocol buffer.
+ * @param consumers The {@link KeyValueConsumers} for the entries {@link DataKey} -&gt; {@link
+ * DataValue}.
+ * @throws DeserializationException Raised for an IOException or when the inPath is not a valid
+ * proto buffer.
+ */
+ @Override
+ public void read(Path inPath, KeyValueConsumers consumers) {
+ Stopwatch timer = Stopwatch.createStarted();
+ try (InputStream in = Files.newInputStream(inPath, StandardOpenOption.READ)) {
+ FileSystem currentFileSystem = inPath.getFileSystem();
+ 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(
+ String.format("Deserialized in merged in %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
+ }
+ }
+
+ private void readEntriesSegment(
+ KeyValueConsumers consumers,
+ InputStream in,
+ FileSystem currentFileSystem,
+ Header header)
+ throws IOException {
+ 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);
+ }
+ }
+
+ // 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;
+ }
+ 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));
+ }
+ }
+ }
+
+ /** Deserializes a list of serialized resource paths to a {@link ParsedAndroidData}. */
+ public static ParsedAndroidData deserializeSymbolsToData(List<Path> symbolPaths)
+ throws IOException {
+ AndroidParsedDataDeserializer deserializer = create();
+ final ListeningExecutorService executorService =
+ MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(15));
+ final Builder deserializedDataBuilder = ParsedAndroidData.Builder.newBuilder();
+ try (Closeable closeable = ExecutorServiceCloser.createWith(executorService)) {
+ List<ListenableFuture<Boolean>> deserializing = new ArrayList<>();
+ for (final Path symbolPath : symbolPaths) {
+ deserializing.add(
+ executorService.submit(
+ new AndroidParsedDataDeserializer.Deserialize(
+ deserializer, symbolPath, deserializedDataBuilder)));
+ }
+ FailedFutureAggregator<MergingException> aggregator =
+ FailedFutureAggregator.createForMergingExceptionWithMessage(
+ "Failure(s) during dependency parsing");
+ aggregator.aggregateAndMaybeThrow(deserializing);
+ }
+ return deserializedDataBuilder.build();
+ }
+
+ public static ParsedAndroidData deserializeSingleAndroidData(SerializedAndroidData data) {
+ final ParsedAndroidData.Builder builder = ParsedAndroidData.Builder.newBuilder();
+ final AndroidParsedDataDeserializer deserializer = create();
+ data.deserialize(deserializer, builder.consumers());
+ return builder.build();
+ }
+}
+
+