blob: 7e55e3e37dfcefdedff30e04bbc3f0c2447e186e [file] [log] [blame]
// 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;
}
}