// 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.resources; import com.android.builder.core.VariantConfiguration; import com.android.builder.dependency.SymbolFileProvider; import com.android.resources.ResourceType; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.MultimapBuilder; import com.google.common.collect.SortedSetMultimap; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.logging.Logger; import javax.annotation.Nullable; /** Encapsulates the logic for loading and writing resource symbols. */ public class ResourceSymbols { private static final Logger logger = Logger.getLogger(ResourceSymbols.class.getCanonicalName()); /** Task to load and parse R.txt symbols */ private static final class SymbolLoadingTask implements Callable<ResourceSymbols> { private final Path rTxtSymbols; SymbolLoadingTask(Path symbolFile) { this.rTxtSymbols = symbolFile; } @Override public ResourceSymbols call() throws Exception { List<String> lines = Files.readAllLines(rTxtSymbols, StandardCharsets.UTF_8); final SortedSetMultimap<ResourceType, FieldInitializer> initializers = MultimapBuilder.enumKeys(ResourceType.class).treeSetValues().build(); for (int lineIndex = 1; lineIndex <= lines.size(); lineIndex++) { String line = null; try { line = lines.get(lineIndex - 1); // format is "<type> <class> <name> <value>" // don't want to split on space as value could contain spaces. int pos = line.indexOf(' '); String type = line.substring(0, pos); int pos2 = line.indexOf(' ', pos + 1); String className = line.substring(pos + 1, pos2); int pos3 = line.indexOf(' ', pos2 + 1); String name = line.substring(pos2 + 1, pos3); String value = line.substring(pos3 + 1); final ResourceType resourceType = ResourceType.getEnum(className); if ("int".equals(type)) { initializers.put(resourceType, IntFieldInitializer.of(name, value)); } else { initializers.put(resourceType, IntArrayFieldInitializer.of(name, value)); } } catch (IndexOutOfBoundsException e) { String s = String.format( "File format error reading %s\tline %d: '%s'", rTxtSymbols.toString(), lineIndex, line); logger.severe(s); throw new IOException(s, e); } } return ResourceSymbols.from(FieldInitializers.copyOf(initializers.asMap())); } } private static final class PackageParsingTask implements Callable<String> { private final File manifest; PackageParsingTask(File manifest) { this.manifest = manifest; } @Override public String call() throws Exception { return VariantConfiguration.getManifestPackage(manifest); } } /** * Loads the SymbolTables from a list of SymbolFileProviders. * * @param dependencies The full set of library symbols to load. * @param executor The executor use during loading. * @param packageToExclude A string package to elide if it exists in the providers. * @return A list of loading {@link ResourceSymbols} instances. * @throws ExecutionException * @throws InterruptedException when there is an error loading the symbols. */ public static Multimap<String, ListenableFuture<ResourceSymbols>> loadFrom( Collection<SymbolFileProvider> dependencies, ListeningExecutorService executor, @Nullable String packageToExclude) throws InterruptedException, ExecutionException { Map<SymbolFileProvider, ListenableFuture<String>> providerToPackage = new HashMap<>(); for (SymbolFileProvider dependency : dependencies) { providerToPackage.put( dependency, executor.submit(new PackageParsingTask(dependency.getManifest()))); } Multimap<String, ListenableFuture<ResourceSymbols>> packageToTable = HashMultimap.create(); for (Entry<SymbolFileProvider, ListenableFuture<String>> entry : providerToPackage.entrySet()) { File symbolFile = entry.getKey().getSymbolFile(); if (!Objects.equals(entry.getValue().get(), packageToExclude)) { packageToTable.put(entry.getValue().get(), load(symbolFile.toPath(), executor)); } } return packageToTable; } public static ResourceSymbols from(FieldInitializers fieldInitializers) { return new ResourceSymbols(fieldInitializers); } public static ResourceSymbols merge(Collection<ResourceSymbols> symbolTables) { final SortedSetMultimap<ResourceType, FieldInitializer> initializers = MultimapBuilder.enumKeys(ResourceType.class).treeSetValues().build(); for (ResourceSymbols symbolTableProvider : symbolTables) { for (Entry<ResourceType, Collection<FieldInitializer>> entry : symbolTableProvider.asInitializers()) { initializers.putAll(entry.getKey(), entry.getValue()); } } return from(FieldInitializers.copyOf(initializers.asMap())); } /** Read the symbols from the provided symbol file. */ public static ListenableFuture<ResourceSymbols> load( Path primaryRTxt, ListeningExecutorService executorService) { return executorService.submit(new SymbolLoadingTask(primaryRTxt)); } private final FieldInitializers values; private ResourceSymbols(FieldInitializers fieldInitializers) { this.values = fieldInitializers; } /** * Writes the java sources for a given package. * * @param sourceOut The directory to write the java package structures and sources to. * @param packageName The name of the package to write. * @param packageSymbols The symbols defined in the given package. * @param finalFields * @throws IOException when encountering an error during writing. */ public void writeTo( Path sourceOut, String packageName, Collection<ResourceSymbols> packageSymbols, boolean finalFields) throws IOException { RSourceGenerator.with(sourceOut, asInitializers(), finalFields) .write(packageName, merge(packageSymbols).asInitializers()); } public FieldInitializers asInitializers() { return values; } }