blob: 971851d103801a2bceb3c2607f6090b5291c57b1 [file] [log] [blame]
// Copyright 2016 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.MoreObjects;
import com.google.common.collect.ImmutableList;
import com.android.ide.common.res2.MergingException;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import javax.annotation.concurrent.Immutable;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
/**
* Represents a collection of Android Resources.
*
* The AndroidDataSet is the primary building block for merging several AndroidDependencies
* together. It extracts the android resource symbols (e.g. R.string.Foo) from the xml files to
* allow an AndroidDataMerger to consume and produce a merged set of data.
*/
@Immutable
public class AndroidDataSet {
/**
* A FileVisitor that walks a resource tree and extract FullyQualifiedName and resource values.
*/
private static class ResourceFileVisitor extends SimpleFileVisitor<Path> {
private final List<DataResource> overwritingResources;
private final List<DataResource> nonOverwritingResources;
private final List<Exception> errors = new ArrayList<>();
private boolean inValuesSubtree;
private FullyQualifiedName.Factory fqnFactory;
private final XMLInputFactory xmlInputFactory = XMLInputFactory.newFactory();
public ResourceFileVisitor(
List<DataResource> overwritingResources, List<DataResource> nonOverwritingResources) {
this.overwritingResources = overwritingResources;
this.nonOverwritingResources = nonOverwritingResources;
}
private void checkForErrors() throws MergingException {
if (!getErrors().isEmpty()) {
StringBuilder errors = new StringBuilder();
for (Exception e : getErrors()) {
errors.append("\n").append(e.getMessage());
}
throw new MergingException(errors.toString());
}
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
throws IOException {
final String[] dirNameAndQualifiers = dir.getFileName().toString().split("-");
inValuesSubtree = "values".equals(dirNameAndQualifiers[0]);
fqnFactory = FullyQualifiedName.Factory.from(getQualifiers(dirNameAndQualifiers));
return FileVisitResult.CONTINUE;
}
private List<String> getQualifiers(String[] dirNameAndQualifiers) {
if (dirNameAndQualifiers.length == 1) {
return ImmutableList.<String>of();
}
return Arrays.asList(
Arrays.copyOfRange(dirNameAndQualifiers, 1, dirNameAndQualifiers.length));
}
@Override
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
try {
if (!Files.isDirectory(path)) {
if (inValuesSubtree) {
XmlDataResource.fromPath(
xmlInputFactory, path, fqnFactory, overwritingResources, nonOverwritingResources);
} else {
overwritingResources.add(FileDataResource.fromPath(path, fqnFactory));
}
}
} catch (IllegalArgumentException | XMLStreamException e) {
errors.add(e);
}
return super.visitFile(path, attrs);
}
public List<Exception> getErrors() {
return errors;
}
}
/** Creates an AndroidDataSet of the overwriting and nonOverwritingResources lists. */
public static AndroidDataSet of(
List<DataResource> overwritingResources, List<DataResource> nonOverwritingResources) {
return new AndroidDataSet(
ImmutableList.copyOf(overwritingResources), ImmutableList.copyOf(nonOverwritingResources));
}
public static AndroidDataSet from(UnvalidatedAndroidData primary)
throws IOException, MergingException {
List<DataResource> overwritingResources = new ArrayList<>();
List<DataResource> nonOverwritingResources = new ArrayList<>();
ResourceFileVisitor visitor =
new ResourceFileVisitor(overwritingResources, nonOverwritingResources);
primary.walkResources(visitor);
visitor.checkForErrors();
return of(overwritingResources, nonOverwritingResources);
}
/**
* Creates an AndroidDataSet from a list of DependencyAndroidDatas.
*
* The adding process parses out all the provided symbol into DataResource objects.
*
* @param dependencyAndroidDataList The dependency data to parse into DataResources.
* @throws IOException when there are issues with reading files.
* @throws MergingException when there is invalid resource information.
*/
public static AndroidDataSet from(List<DependencyAndroidData> dependencyAndroidDataList)
throws IOException, MergingException {
List<DataResource> overwritingResources = new ArrayList<>();
List<DataResource> nonOverwritingResources = new ArrayList<>();
ResourceFileVisitor visitor =
new ResourceFileVisitor(overwritingResources, nonOverwritingResources);
for (DependencyAndroidData data : dependencyAndroidDataList) {
data.walkResources(visitor);
}
visitor.checkForErrors();
return of(overwritingResources, nonOverwritingResources);
}
private final ImmutableList<DataResource> overwritingResources;
private final ImmutableList<DataResource> nonOverwritingResources;
private AndroidDataSet(
ImmutableList<DataResource> overwritingResources,
ImmutableList<DataResource> nonOverwritingResources) {
this.overwritingResources = overwritingResources;
this.nonOverwritingResources = nonOverwritingResources;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("overwritingResources", overwritingResources)
.add("nonOverwritingResources", nonOverwritingResources)
.toString();
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof AndroidDataSet)) {
return false;
}
AndroidDataSet that = (AndroidDataSet) other;
return Objects.equals(overwritingResources, that.overwritingResources)
&& Objects.equals(nonOverwritingResources, that.nonOverwritingResources);
}
@Override
public int hashCode() {
return Objects.hash(overwritingResources, nonOverwritingResources);
}
/**
* Returns a list of resources that would overwrite other values when defined.
*
* <p>Example:
*
* A string resource (string.Foo=bar) could be redefined at string.Foo=baz.
*
* @return A list of overwriting resources.
*/
public List<DataResource> getOverwritingResources() {
return overwritingResources;
}
/**
* Returns a list of resources that would not overwrite other values when defined.
*
* <p>Example:
*
* A id resource (id.Foo) could be redefined at id.Foo with no adverse effects.
*
* @return A list of non-overwriting resources.
*/
public List<DataResource> getNonOverwritingResources() {
return nonOverwritingResources;
}
}