// 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.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.devtools.build.android.FullyQualifiedName.Factory; import com.google.devtools.build.android.ParsedAndroidData.CombiningConsumer; import com.google.devtools.build.android.ParsedAndroidData.KeyValueConsumer; import com.google.devtools.build.android.ParsedAndroidData.OverwritableConsumer; import com.google.devtools.build.android.xml.Namespaces; import java.nio.file.Path; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; /** * Build for ParsedAndroidData instance. */ public class ParsedAndroidDataBuilder { private final Path defaultRoot; private final FullyQualifiedName.Factory fqnFactory; private final Map<DataKey, DataResource> overwrite = new HashMap<>(); private final Map<DataKey, DataResource> combine = new HashMap<>(); private final Map<DataKey, DataAsset> assets = new HashMap<>(); private final Set<MergeConflict> conflicts = new HashSet<>(); public ParsedAndroidDataBuilder( @Nullable Path root, @Nullable FullyQualifiedName.Factory fqnFactory) { this.defaultRoot = root; this.fqnFactory = fqnFactory; } public static ParsedAndroidData empty() { return ParsedAndroidData.of( ImmutableSet.<MergeConflict>of(), ImmutableMap.<DataKey, DataResource>of(), ImmutableMap.<DataKey, DataResource>of(), ImmutableMap.<DataKey, DataAsset>of()); } public static ParsedAndroidDataBuilder buildOn( Path defaultRoot, FullyQualifiedName.Factory fqnFactory) { return new ParsedAndroidDataBuilder(defaultRoot, fqnFactory); } public static ParsedAndroidDataBuilder buildOn(FullyQualifiedName.Factory fqnFactory) { return buildOn(null, fqnFactory); } public static ParsedAndroidDataBuilder buildOn(Path defaultRoot) { return buildOn(defaultRoot, null); } public static ParsedAndroidDataBuilder builder() { return buildOn(null, null); } public ParsedAndroidDataBuilder overwritable(DataEntry... resourceBuilders) { OverwritableConsumer<DataKey, DataResource> consumer = new OverwritableConsumer<>(overwrite, conflicts); for (DataEntry resourceBuilder : resourceBuilders) { resourceBuilder.accept(fqnFactory, defaultRoot, consumer); } return this; } public ParsedAndroidDataBuilder combining(DataEntry... resourceBuilders) { CombiningConsumer consumer = new CombiningConsumer(combine); for (DataEntry resourceBuilder : resourceBuilders) { resourceBuilder.accept(fqnFactory, defaultRoot, consumer); } return this; } public ParsedAndroidDataBuilder assets(DataEntry... assetBuilders) { OverwritableConsumer<DataKey, DataAsset> consumer = new OverwritableConsumer<>(assets, conflicts); for (DataEntry assetBuilder : assetBuilders) { assetBuilder.accept(defaultRoot, consumer); } return this; } public static FileResourceBuilder file(String rawKey) { return new FileResourceBuilder(rawKey); } public static FileResourceBuilder file() { return new FileResourceBuilder(null); } public static XmlResourceBuilder xml(String rawKey) { return new XmlResourceBuilder(rawKey); } public ParsedAndroidData build() { return ParsedAndroidData.of( ImmutableSet.copyOf(conflicts), ImmutableMap.copyOf(overwrite), ImmutableMap.copyOf(combine), ImmutableMap.copyOf(assets)); } static class FileResourceBuilder { private String rawKey; private Path root; FileResourceBuilder(@Nullable String rawKey) { this.rawKey = rawKey; } FileResourceBuilder root(Path root) { this.root = root; return this; } Path chooseRoot(Path defaultRoot) { if (defaultRoot != null) { return defaultRoot; } if (root != null) { return root; } throw new IllegalStateException( "the default root and asset root are null! A root is required!"); } DataEntry source(final DataSource source) { return new DataEntry() { @Override void accept( @Nullable Factory factory, @Nullable Path root, KeyValueConsumer<DataKey, DataResource> consumer) { consumer.consume(factory.parse(rawKey), DataValueFile.of(source)); } @Override void accept(@Nullable Path defaultRoot, KeyValueConsumer<DataKey, DataAsset> target) { target.consume( RelativeAssetPath.Factory.of(chooseRoot(defaultRoot).resolve("assets")) .create(source.getPath()), DataValueFile.of(source)); } }; } DataEntry source(final String path) { return new DataEntry() { @Override public void accept( FullyQualifiedName.Factory factory, Path defaultRoot, KeyValueConsumer<DataKey, DataResource> consumer) { Path res = chooseRoot(defaultRoot).resolve("res"); consumer.consume(factory.parse(rawKey), DataValueFile.of(res.resolve(path))); } @Override public void accept( @Nullable Path defaultRoot, KeyValueConsumer<DataKey, DataAsset> consumer) { Path assets = chooseRoot(defaultRoot).resolve("assets"); Path fullPath = assets.resolve(path); consumer.consume( RelativeAssetPath.Factory.of(assets).create(fullPath), DataValueFile.of(fullPath)); } }; } } static class XmlResourceBuilder { private final String rawFqn; private Path root; private final Map<String, String> prefixToUri = new LinkedHashMap<>(); XmlResourceBuilder(String rawFqn) { this(rawFqn, null); } XmlResourceBuilder(String rawFqn, Path root) { this.rawFqn = rawFqn; this.root = root; } XmlResourceBuilder source(final String path) { return new XmlResourceBuilder(rawFqn, root) { @Override public DataEntry value(final XmlResourceValue value) { return new DataEntry() { @Override public void accept( FullyQualifiedName.Factory factory, Path defaultRoot, KeyValueConsumer<DataKey, DataResource> consumer) { Path res = (root == null ? defaultRoot : root).resolve("res"); consumer.consume( factory.parse(rawFqn), DataResourceXml.createWithNamespaces( res.resolve(path), value, Namespaces.from(prefixToUri))); } }; } }; } XmlResourceBuilder source(final DataSource dataSource) { return new XmlResourceBuilder(rawFqn, root) { @Override public DataEntry value(final XmlResourceValue value) { return new DataEntry() { @Override public void accept( FullyQualifiedName.Factory factory, Path defaultRoot, KeyValueConsumer<DataKey, DataResource> consumer) { consumer.consume( factory.parse(rawFqn), DataResourceXml.createWithNamespaces( dataSource, value, Namespaces.from(prefixToUri))); } }; } }; } XmlResourceBuilder root(Path root) { this.root = root; return this; } XmlResourceBuilder namespace(String prefix, String uri) { prefixToUri.put(prefix, uri); return this; } DataEntry value(final XmlResourceValue value) { throw new UnsupportedOperationException("A source must be declared!"); } } abstract static class DataEntry { void accept( @Nullable FullyQualifiedName.Factory factory, @Nullable Path root, KeyValueConsumer<DataKey, DataResource> consumer) { throw new UnsupportedOperationException("assets cannot be resources!"); } void accept(@Nullable Path root, KeyValueConsumer<DataKey, DataAsset> target) { throw new UnsupportedOperationException("xml resources cannot be assets!"); } } }