blob: 10ec3427434764b6d24bfb9d2fdc76b6ccd714c8 [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.idea.blaze.android.sync.importer.problems;
import com.android.SdkConstants;
import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.ide.common.resources.configuration.LocaleQualifier;
import com.android.ide.common.resources.configuration.ResourceQualifier;
import com.android.resources.ResourceFolderType;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.idea.blaze.base.ideinfo.ArtifactLocation;
import com.google.idea.blaze.base.io.FileAttributeProvider;
import com.google.idea.blaze.base.sync.workspace.ArtifactLocationDecoder;
import com.intellij.openapi.diagnostic.Logger;
import java.io.File;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
/**
* Classifies generated resource directories as "interesting" or not.
*
* <p>Uninteresting resources: It is very common to have string translations as the only generated
* resources for a rule. If that's the case, we can ignore them since we likely already have a
* source variant of the same resource as a baseline for resolving references in Java or XML.
*
* <p>Other generated resources are interesting.
*/
class GeneratedResourceClassifier {
private static final Logger logger = Logger.getInstance(GeneratedResourceClassifier.class);
private final ImmutableSortedMap<ArtifactLocation, Integer> interestingDirectories;
GeneratedResourceClassifier(
Collection<ArtifactLocation> generatedResourceLocations,
ArtifactLocationDecoder artifactLocationDecoder,
ListeningExecutorService executorService) {
FileAttributeProvider fileAttributeProvider = FileAttributeProvider.getInstance();
List<ListenableFuture<GenResourceClassification>> jobs =
generatedResourceLocations
.stream()
.map(
location ->
executorService.submit(
() ->
classifyLocation(
location, artifactLocationDecoder, fileAttributeProvider)))
.collect(Collectors.toList());
ImmutableSortedMap.Builder<ArtifactLocation, Integer> interesting =
ImmutableSortedMap.naturalOrder();
try {
for (GenResourceClassification classification : Futures.allAsList(jobs).get()) {
if (classification.isInteresting) {
interesting.put(classification.artifactLocation, classification.numSubDirs);
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
interesting = considerAllInteresting(generatedResourceLocations);
} catch (ExecutionException e) {
logger.error(e);
interesting = considerAllInteresting(generatedResourceLocations);
} finally {
interestingDirectories = interesting.build();
}
}
/**
* Returns a collection of interesting directories as a sorted map from directory to estimated
* size of subdirectory (to estimate cost of indexing).
*
* @return map of interesting directories and associated sizes
*/
public ImmutableSortedMap<ArtifactLocation, Integer> getInterestingDirectories() {
return interestingDirectories;
}
private static ImmutableSortedMap.Builder<ArtifactLocation, Integer> considerAllInteresting(
Collection<ArtifactLocation> generatedResourceLocations) {
ImmutableSortedMap.Builder<ArtifactLocation, Integer> builder =
ImmutableSortedMap.naturalOrder();
for (ArtifactLocation location : generatedResourceLocations) {
builder.put(location, -1);
}
return builder;
}
private static class GenResourceClassification {
final boolean isInteresting;
final ArtifactLocation artifactLocation;
final int numSubDirs;
GenResourceClassification(
boolean isInteresting, ArtifactLocation artifactLocation, int numSubDirs) {
this.isInteresting = isInteresting;
this.artifactLocation = artifactLocation;
this.numSubDirs = numSubDirs;
}
static GenResourceClassification uninteresting(
ArtifactLocation artifactLocation, int numSubDirs) {
return new GenResourceClassification(false, artifactLocation, numSubDirs);
}
static GenResourceClassification interesting(
ArtifactLocation artifactLocation, int numSubDirs) {
return new GenResourceClassification(true, artifactLocation, numSubDirs);
}
}
private static GenResourceClassification classifyLocation(
ArtifactLocation artifactLocation,
ArtifactLocationDecoder artifactLocationDecoder,
FileAttributeProvider fileAttributeProvider) {
File resDirectory = artifactLocationDecoder.decode(artifactLocation);
File[] children = fileAttributeProvider.listFiles(resDirectory);
if (children == null) {
return GenResourceClassification.uninteresting(artifactLocation, 0);
}
if (mayHaveNonStringTranslations(children)) {
return GenResourceClassification.interesting(artifactLocation, children.length);
} else {
return GenResourceClassification.uninteresting(artifactLocation, children.length);
}
}
@VisibleForTesting
static boolean mayHaveNonStringTranslations(File[] resDirectoryChildren) {
return Arrays.stream(resDirectoryChildren)
.anyMatch(child -> mayHaveNonStringTranslations(child.getName()));
}
private static boolean mayHaveNonStringTranslations(String dirName) {
// String translations only sit in the values-xx-rYY directories, so we can rule out other
// directories quickly.
if (!dirName.contains(SdkConstants.RES_QUALIFIER_SEP)) {
return true;
}
if (ResourceFolderType.getFolderType(dirName) != ResourceFolderType.VALUES) {
return true;
}
FolderConfiguration config = FolderConfiguration.getConfigForFolder(dirName);
// Conservatively say it's interesting if there is an unrecognized configuration.
if (config == null) {
return true;
}
// If this is a translation mixed with something else, consider it a translation directory.
boolean hasTranslation = false;
for (ResourceQualifier qualifier : config.getQualifiers()) {
if (qualifier instanceof LocaleQualifier) {
hasTranslation = true;
}
}
return !hasTranslation;
}
}