com.android.ide.common.repository.ResourceVisibilityLookup Maven / Gradle / Ivy
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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.android.ide.common.repository;
import static com.android.SdkConstants.FN_RESOURCE_TEXT;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.annotations.VisibleForTesting;
import com.android.builder.model.AndroidArtifact;
import com.android.builder.model.AndroidLibrary;
import com.android.builder.model.AndroidProject;
import com.android.builder.model.MavenCoordinates;
import com.android.builder.model.Variant;
import com.android.ide.common.resources.ResourceUrl;
import com.android.resources.ResourceType;
import com.google.common.base.Charsets;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.io.Files;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Class which provides information about whether Android resources for a given library are
* public or private.
*/
public abstract class ResourceVisibilityLookup {
/**
* Returns true if the given resource is private
*
* @param type the type of the resource
* @param name the resource field name of the resource (in other words, for
* style Theme:Variant.Cls the name would be Theme_Variant_Cls
* @return true if the given resource is private
*/
public abstract boolean isPrivate(
@NonNull ResourceType type,
@NonNull String name);
/**
* Returns true if the given resource is private
*
* @param url the resource URL
* @return true if the given resource is private
*/
public boolean isPrivate(@NonNull ResourceUrl url) {
assert !url.framework; // Framework resources are not part of the library
return isPrivate(url.type, url.name);
}
/**
* For a private resource, return the {@link AndroidLibrary} that the resource was defined as
* private in
*
* @param type the type of the resource
* @param name the name of the resource
* @return the library which defines the resource as private
*/
@Nullable
public abstract AndroidLibrary getPrivateIn(@NonNull ResourceType type, @NonNull String name);
/** Returns true if this repository does not declare any resources to be private */
public abstract boolean isEmpty();
/**
* Creates a {@link ResourceVisibilityLookup} for a given library.
*
* NOTE: The {@link Provider} class can be used to share/cache {@link ResourceVisibilityLookup}
* instances, e.g. when you have library1 and library2 each referencing libraryBase, the {@link
* Provider} will ensure that a the libraryBase data is shared.
*
* @param library the library
* @return a corresponding {@link ResourceVisibilityLookup}
*/
@NonNull
public static ResourceVisibilityLookup create(@NonNull AndroidLibrary library) {
return new LibraryResourceVisibility(library, new SymbolProvider());
}
/**
* Creates a {@link ResourceVisibilityLookup} for the set of libraries.
*
* NOTE: The {@link Provider} class can be used to share/cache {@link ResourceVisibilityLookup}
* instances, e.g. when you have library1 and library2 each referencing libraryBase, the {@link
* Provider} will ensure that a the libraryBase data is shared.
*
* @param libraries the list of libraries
* @param provider an optional manager instance for caching of individual libraries, if any
* @return a corresponding {@link ResourceVisibilityLookup}
*/
@NonNull
public static ResourceVisibilityLookup create(@NonNull List libraries,
@Nullable Provider provider) {
List list = Lists.newArrayListWithExpectedSize(libraries.size());
for (AndroidLibrary library : libraries) {
ResourceVisibilityLookup v = provider != null ? provider.get(library)
: new LibraryResourceVisibility(library, new SymbolProvider());
if (!v.isEmpty()) {
list.add(v);
}
}
return new MultipleLibraryResourceVisibility(list);
}
public static final ResourceVisibilityLookup NONE = new ResourceVisibilityLookup() {
@Override
public boolean isPrivate(@NonNull ResourceType type, @NonNull String name) {
return false;
}
@Nullable
@Override
public AndroidLibrary getPrivateIn(@NonNull ResourceType type, @NonNull String name) {
return null;
}
@Override
public boolean isEmpty() {
return true;
}
};
/**
* Create a key that can be used to identify a library for a specific version.
* We can't use {@link AndroidLibrary} directly, because (due to a lot of magic in the
* Gradle model) we end up with separate instances of {@link AndroidLibrary} when a single
* library appears more than once, such as a downstream dependency reachable from multiple
* upstream libraries.
*
* @param library the library to produce a map key for
* @return a suitable key to use with {@link Map}
*/
private static String getMapKey(@NonNull AndroidLibrary library) {
MavenCoordinates c = library.getResolvedCoordinates();
if (c != null) {
return c.getGroupId() + ':' + c.getArtifactId() + ':' + c.getVersion();
} else {
return library.getBundle().getPath();
}
}
private static String getMapKey(@NonNull AndroidArtifact artifact) {
return artifact.getApplicationId();
}
private static String getMapKey(@NonNull Variant variant) {
return getMapKey(variant.getMainArtifact()) + '-' + variant.getName();
}
/**
* Given a library, return all the libraries it depends on, transitively, with each library
* appearing only once
*
* @param library the library to compute transitive dependencies for
* @return the list of libraries the given library depends on
*/
private static List getTransitiveDependencies(
@NonNull AndroidLibrary library) {
List result = Lists.newArrayList();
for (AndroidLibrary dependency : library.getLibraryDependencies()) {
addLibraries(result, new HashSet(), dependency);
}
return result;
}
/** Adds unique libraries the given library depends on into the given result */
private static void addLibraries(@NonNull List result,
@NonNull Set seen, @NonNull AndroidLibrary library) {
String key = getMapKey(library);
if (seen.contains(key)) {
return;
}
seen.add(key);
result.add(library);
for (AndroidLibrary dependency : library.getLibraryDependencies()) {
addLibraries(result, seen, dependency);
}
}
/** Searches multiple libraries */
private static class MultipleLibraryResourceVisibility extends ResourceVisibilityLookup {
private final List mRepositories;
public MultipleLibraryResourceVisibility(List repositories) {
mRepositories = repositories;
}
// It's anticipated that these methods will be called a lot (e.g. in inner loops
// iterating over all resources matching code completion etc) so since we know
// that our list has random access, avoid creating iterators here
@SuppressWarnings("ForLoopReplaceableByForEach")
@Override
public boolean isPrivate(@NonNull ResourceType type, @NonNull String name) {
for (int i = 0, n = mRepositories.size(); i < n; i++) {
ResourceVisibilityLookup lookup = mRepositories.get(i);
if (lookup.isPrivate(type, name)) {
return true;
}
}
return false;
}
@SuppressWarnings("ForLoopReplaceableByForEach")
@Override
public boolean isEmpty() {
for (int i = 0, n = mRepositories.size(); i < n; i++) {
if (!mRepositories.get(i).isEmpty()) {
return false;
}
}
return true;
}
@SuppressWarnings("ForLoopReplaceableByForEach")
@Nullable
@Override
public AndroidLibrary getPrivateIn(@NonNull ResourceType type, @NonNull String name) {
for (int i = 0, n = mRepositories.size(); i < n; i++) {
ResourceVisibilityLookup r = mRepositories.get(i);
if (r.isPrivate(type, name)) {
return r.getPrivateIn(type, name);
}
}
return null;
}
@Override
public String toString() {
return mRepositories.toString();
}
}
/**
* Provider which keeps a set of {@link ResourceVisibilityLookup} instances around for
* repeated queries, including from different libraries that may share dependencies
*/
public static class Provider {
/**
* We store lookup instances for multiple separate types of keys here:
* {@link AndroidLibrary}, {@link AndroidArtifact}, and {@link Variant}
*/
private Map