// 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; import java.util.Map.Entry; /** * 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 (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); } 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 (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 (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; } }