| // 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; | |
| } | |
| } |