blob: 91adca4c8707e539a2ca815897d9cda1b0af8df5 [file] [log] [blame]
// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.ClassKind;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexType;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
/**
* Represents a collection of classes. Collection can be fully loaded,
* lazy loaded or have preloaded classes along with lazy loaded content.
*/
public abstract class ClassMap<T extends DexClass> {
// For each type which has ever been queried stores one class loaded from
// resources provided by different resource providers.
//
// NOTE: all access must be synchronized on `classes`.
private final Map<DexType, Value<T>> classes;
// Class provider if available. In case it's `null`, all classes of
// the collection must be pre-populated in `classes`.
private final ClassProvider<T> classProvider;
ClassMap(Map<DexType, Value<T>> classes, ClassProvider<T> classProvider) {
this.classes = classes == null ? new IdentityHashMap<>() : classes;
this.classProvider = classProvider;
assert this.classProvider == null || this.classProvider.getClassKind() == getClassKind();
}
/** Resolves a class conflict by selecting a class, may generate compilation error. */
abstract T resolveClassConflict(T a, T b);
/** Kind of the classes supported by this collection. */
abstract ClassKind getClassKind();
@Override
public String toString() {
synchronized (classes) {
return classes.size() + " loaded, provider: " +
(classProvider == null ? "none" : classProvider.toString());
}
}
/** Returns a definition for a class or `null` if there is no such class in the collection. */
public T get(DexType type) {
final Value<T> value = getOrCreateValue(type);
if (value == null) {
return null;
}
if (!value.ready) {
// Load the value in a context synchronized on value instance. This way
// we avoid locking the whole collection during expensive resource loading
// and classes construction operations.
synchronized (value) {
if (!value.ready) {
assert classProvider != null : "getOrCreateValue() created "
+ "Value for missing type when there is no classProvider.";
classProvider.collectClass(type, clazz -> {
assert clazz != null;
assert getClassKind().isOfKind(clazz);
assert !value.ready;
if (clazz.type != type) {
throw new CompilationError("Class content provided for type descriptor "
+ type.toSourceString() + " actually defines class " + clazz.type
.toSourceString());
}
if (value.clazz == null) {
value.clazz = clazz;
} else {
// The class resolution *may* generate a compilation error as one of
// possible resolutions. In this case we leave `value` in (false, null)
// state so in rare case of another thread trying to get the same class
// before this error is propagated it will get the same conflict.
T oldClass = value.clazz;
value.clazz = null;
value.clazz = resolveClassConflict(oldClass, clazz);
}
});
value.ready = true;
}
}
}
assert value.ready;
return value.clazz;
}
private Value<T> getOrCreateValue(DexType type) {
synchronized (classes) {
Value<T> value = classes.get(type);
if (value == null && classProvider != null) {
value = new Value<>();
classes.put(type, value);
}
return value;
}
}
/**
* Returns currently loaded classes.
*
* Method is assumed to be called when the collection is fully loaded,
* otherwise only a subset of potentially loaded classes may be returned.
*/
public List<T> collectLoadedClasses() {
List<T> loadedClasses = new ArrayList<>();
synchronized (classes) {
for (Value<T> value : classes.values()) {
// NOTE: value mutations are NOT synchronized on `classes`, here we actually
// can see value which is not ready yet. Since everything that exists should
// be guaranteed by the caller to be loaded at this point, this can only happen
// if the code references classes that do not exist. Therefore, if the value is
// not ready here, we know that the loaded value will be 'null' once it is ready.
if (value.ready && value.clazz != null) {
loadedClasses.add(value.clazz);
}
}
}
return loadedClasses;
}
/** Forces loading of all the classes satisfying the criteria specified. */
public void forceLoad(Predicate<DexType> load) {
if (classProvider != null) {
Set<DexType> loaded = Sets.newIdentityHashSet();
synchronized (classes) {
loaded.addAll(classes.keySet());
}
Collection<DexType> types = classProvider.collectTypes();
for (DexType type : types) {
if (load.test(type) && !loaded.contains(type)) {
get(type); // force-load type.
}
}
}
}
// Represents a value in the class map.
final static class Value<T> {
volatile boolean ready;
T clazz;
Value() {
ready = false;
clazz = null;
}
Value(T clazz) {
this.clazz = clazz;
this.ready = true;
}
}
}