| // 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.Joiner; |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableList; |
| import com.google.devtools.build.android.AndroidResourceMerger.MergingException; |
| import java.io.IOException; |
| import java.nio.charset.StandardCharsets; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.StandardCopyOption; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * Utility for building {@link UnvalidatedAndroidData}, {@link ParsedAndroidData}, |
| * {@link DependencyAndroidData} and {@link MergedAndroidData}. |
| */ |
| public class AndroidDataBuilder { |
| /** Templates for resource files generation. */ |
| public enum ResourceType { |
| VALUE { |
| @Override |
| public String create(String... lines) { |
| return String.format( |
| "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>%s</resources>", |
| Joiner.on("\n").join(lines)); |
| } |
| }, |
| LAYOUT { |
| @Override |
| public String create(String... lines) { |
| return String.format( |
| "<?xml version=\"1.0\" encoding=\"utf-8\"?>" |
| + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"" |
| + " android:layout_width=\"fill_parent\"" |
| + " android:layout_height=\"fill_parent\">%s</LinearLayout>", |
| Joiner.on("\n").join(lines)); |
| } |
| }, |
| UNFORMATTED { |
| @Override |
| public String create(String... lines) { |
| return String.format(Joiner.on("\n").join(lines)); |
| } |
| }; |
| |
| public abstract String create(String... lines); |
| } |
| |
| public static AndroidDataBuilder of(Path root) { |
| return new AndroidDataBuilder(root); |
| } |
| |
| private final Path root; |
| private final Path assetDir; |
| private final Path resourceDir; |
| private Map<Path, String> filesToWrite = new HashMap<>(); |
| private Map<Path, Path> filesToCopy = new HashMap<>(); |
| private Path manifest; |
| private Path rTxt; |
| |
| private AndroidDataBuilder(Path root) { |
| this.root = root; |
| assetDir = root.resolve("assets"); |
| resourceDir = root.resolve("res"); |
| } |
| |
| AndroidDataBuilder copyOf(Path newRoot) { |
| AndroidDataBuilder result = new AndroidDataBuilder(newRoot); |
| result.filesToWrite = rerootPaths(this.filesToWrite, this.root, newRoot); |
| result.filesToCopy = rerootPaths(this.filesToCopy, this.root, newRoot); |
| result.manifest = this.manifest; |
| result.rTxt = this.rTxt; |
| return result; |
| } |
| |
| public AndroidDataBuilder addResource( |
| String path, AndroidDataBuilder.ResourceType template, String... lines) { |
| filesToWrite.put(resourceDir.resolve(path), template.create(lines)); |
| return this; |
| } |
| |
| public AndroidDataBuilder addValuesWithAttributes( |
| String path, Map<String, String> attributes, String... lines) { |
| ImmutableList.Builder<String> attributeBuilder = ImmutableList.builder(); |
| for (Map.Entry<String, String> attribute : attributes.entrySet()) { |
| if (attribute.getKey() != null && attribute.getValue() != null) { |
| attributeBuilder.add(String.format("%s=\"%s\"", attribute.getKey(), attribute.getValue())); |
| } |
| } |
| String fileContents = ResourceType.VALUE.create(lines); |
| fileContents = fileContents.replace("<resources>", |
| String.format("<resources %s>", Joiner.on(" ").join(attributeBuilder.build()))); |
| filesToWrite.put(resourceDir.resolve(path), fileContents); |
| return this; |
| } |
| |
| public AndroidDataBuilder addResourceBinary(String path, Path source) { |
| final Path target = resourceDir.resolve(path); |
| filesToCopy.put(target, source); |
| return this; |
| } |
| |
| public AndroidDataBuilder addAsset(String path, String... lines) { |
| filesToWrite.put(assetDir.resolve(path), Joiner.on("\n").join(lines)); |
| return this; |
| } |
| |
| public AndroidDataBuilder createManifest(String path, String manifestPackage, String... lines) { |
| return createManifest(path, manifestPackage, ImmutableList.<String>of(), lines); |
| } |
| |
| public AndroidDataBuilder createManifest( |
| String path, String manifestPackage, List<String> namespaces, String... lines) { |
| this.manifest = root.resolve(path); |
| filesToWrite.put( |
| manifest, |
| String.format( |
| "<?xml version=\"1.0\" encoding=\"utf-8\"?>" |
| + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" %s" |
| + " package=\"%s\">" |
| + "%s</manifest>", |
| Joiner.on(" ").join(namespaces), |
| manifestPackage, |
| Joiner.on("\n").join(lines))); |
| return this; |
| } |
| |
| public AndroidDataBuilder createRTxt(String path, String... lines) { |
| this.rTxt = root.resolve(path); |
| filesToWrite.put(rTxt, Joiner.on("\n").join(lines)); |
| return this; |
| } |
| |
| public UnvalidatedAndroidData buildUnvalidated() throws IOException { |
| writeFiles(); |
| return new UnvalidatedAndroidData( |
| ImmutableList.of(resourceDir), ImmutableList.of(assetDir), manifest); |
| } |
| |
| public ParsedAndroidData buildParsed() throws IOException, MergingException { |
| return ParsedAndroidData.from(buildUnvalidated()); |
| } |
| |
| public DependencyAndroidData buildDependency() throws IOException { |
| writeFiles(); |
| return new DependencyAndroidData( |
| ImmutableList.of(resourceDir), ImmutableList.of(assetDir), manifest, rTxt, null, null); |
| } |
| |
| public MergedAndroidData buildMerged() throws IOException { |
| writeFiles(); |
| return new MergedAndroidData(resourceDir, assetDir, manifest); |
| } |
| |
| private void writeFiles() throws IOException { |
| Files.createDirectories(assetDir); |
| Files.createDirectories(resourceDir); |
| Preconditions.checkNotNull(manifest, "A manifest is required."); |
| for (Map.Entry<Path, String> entry : filesToWrite.entrySet()) { |
| Files.createDirectories(entry.getKey().getParent()); |
| Files.write(entry.getKey(), entry.getValue().getBytes(StandardCharsets.UTF_8)); |
| Preconditions.checkArgument(Files.exists(entry.getKey())); |
| } |
| for (Map.Entry<Path, Path> entry : filesToCopy.entrySet()) { |
| Path target = entry.getKey(); |
| Path source = entry.getValue(); |
| Files.createDirectories(target.getParent()); |
| Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); |
| } |
| } |
| |
| private <T> Map<Path, T> rerootPaths(Map<Path, T> origMap, Path root, Path newRoot) { |
| Map<Path, T> newMap = new HashMap<>(); |
| for (Map.Entry<Path, T> origEntry : origMap.entrySet()) { |
| Path relPath = root.relativize(origEntry.getKey()); |
| newMap.put(newRoot.resolve(relPath), origEntry.getValue()); |
| } |
| return newMap; |
| } |
| |
| } |