aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com/android/tools/r8/utils/ClassMap.java
blob: 91adca4c8707e539a2ca815897d9cda1b0af8df5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
// 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;
    }
  }
}