aboutsummaryrefslogtreecommitdiffstats
path: root/guava/src/com/google/common/reflect/TypeResolver.java
blob: 8c2d58226fd0e90ea6d08366313564cb53014b68 (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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
/*
 * Copyright (C) 2009 The Guava Authors
 *
 * 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.common.reflect;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

import javax.annotation.Nullable;

/**
 * An object of this class encapsulates type mappings from type variables. Mappings are established
 * with {@link #where} and types are resolved using {@link #resolveType}.
 *
 * <p>Note that usually type mappings are already implied by the static type hierarchy (for example,
 * the {@code E} type variable declared by class {@code List} naturally maps to {@code String} in
 * the context of {@code class MyStringList implements List<String>}. In such case, prefer to use
 * {@link TypeToken#resolveType} since it's simpler and more type safe. This class should only be
 * used when the type mapping isn't implied by the static type hierarchy, but provided through other
 * means such as an annotation or external configuration file.
 *
 * @author Ben Yu
 */
class TypeResolver {

  private final ImmutableMap<TypeVariable<?>, Type> typeTable;

  public TypeResolver() {
    this.typeTable = ImmutableMap.of();
  }

  private TypeResolver(ImmutableMap<TypeVariable<?>, Type> typeTable) {
    this.typeTable = typeTable;
  }

  static TypeResolver accordingTo(Type type) {
    return new TypeResolver().where(TypeMappingIntrospector.getTypeMappings(type));
  }

  /**
   * Returns a new {@code TypeResolver} with type variables in {@code formal} mapping to types in
   * {@code actual}.
   *
   * <p>For example, if {@code formal} is a {@code TypeVariable T}, and {@code actual} is {@code
   * String.class}, then {@code new TypeResolver().where(formal, actual)} will {@linkplain
   * #resolveType resolve} {@code ParameterizedType List<T>} to {@code List<String>}, and resolve
   * {@code Map<T, Something>} to {@code Map<String, Something>} etc. Similarly, {@code formal} and
   * {@code actual} can be {@code Map<K, V>} and {@code Map<String, Integer>} respectively, or they
   * can be {@code E[]} and {@code String[]} respectively, or even any arbitrary combination
   * thereof.
   *
   * @param formal The type whose type variables or itself is mapped to other type(s). It's almost
   *        always a bug if {@code formal} isn't a type variable and contains no type variable. Make
   *        sure you are passing the two parameters in the right order.
   * @param actual The type that the formal type variable(s) are mapped to. It can be or contain yet
   *        other type variables, in which case these type variables will be further resolved if
   *        corresponding mappings exist in the current {@code TypeResolver} instance.
   */
  public final TypeResolver where(Type formal, Type actual) {
    Map<TypeVariable<?>, Type> mappings = Maps.newHashMap();
    populateTypeMappings(mappings, checkNotNull(formal), checkNotNull(actual));
    return where(mappings);
  }

  /** Returns a new {@code TypeResolver} with {@code variable} mapping to {@code type}. */
  final TypeResolver where(Map<? extends TypeVariable<?>, ? extends Type> mappings) {
    ImmutableMap.Builder<TypeVariable<?>, Type> builder = ImmutableMap.builder();
    builder.putAll(typeTable);
    for (Map.Entry<? extends TypeVariable<?>, ? extends Type> mapping : mappings.entrySet()) {
      TypeVariable<?> variable = mapping.getKey();
      Type type = mapping.getValue();
      checkArgument(!variable.equals(type), "Type variable %s bound to itself", variable);
      builder.put(variable, type);
    }
    return new TypeResolver(builder.build());
  }

  private static void populateTypeMappings(
      Map<TypeVariable<?>, Type> mappings, Type from, Type to) {
    if (from.equals(to)) {
      return;
    }
    if (from instanceof TypeVariable) {
      mappings.put((TypeVariable<?>) from, to);
    } else if (from instanceof GenericArrayType) {
      populateTypeMappings(mappings,
          ((GenericArrayType) from).getGenericComponentType(),
          checkNonNullArgument(Types.getComponentType(to), "%s is not an array type.", to));
    } else if (from instanceof ParameterizedType) {
      ParameterizedType fromParameterizedType = (ParameterizedType) from;
      ParameterizedType toParameterizedType = expectArgument(ParameterizedType.class, to);
      checkArgument(fromParameterizedType.getRawType().equals(toParameterizedType.getRawType()),
          "Inconsistent raw type: %s vs. %s", from, to);
      Type[] fromArgs = fromParameterizedType.getActualTypeArguments();
      Type[] toArgs = toParameterizedType.getActualTypeArguments();
      checkArgument(fromArgs.length == toArgs.length);
      for (int i = 0; i < fromArgs.length; i++) {
        populateTypeMappings(mappings, fromArgs[i], toArgs[i]);
      }
    } else if (from instanceof WildcardType) {
      WildcardType fromWildcardType = (WildcardType) from;
      WildcardType toWildcardType = expectArgument(WildcardType.class, to);
      Type[] fromUpperBounds = fromWildcardType.getUpperBounds();
      Type[] toUpperBounds = toWildcardType.getUpperBounds();
      Type[] fromLowerBounds = fromWildcardType.getLowerBounds();
      Type[] toLowerBounds = toWildcardType.getLowerBounds();
      checkArgument(
          fromUpperBounds.length == toUpperBounds.length
              && fromLowerBounds.length == toLowerBounds.length,
          "Incompatible type: %s vs. %s", from, to);
      for (int i = 0; i < fromUpperBounds.length; i++) {
        populateTypeMappings(mappings, fromUpperBounds[i], toUpperBounds[i]);
      }
      for (int i = 0; i < fromLowerBounds.length; i++) {
        populateTypeMappings(mappings, fromLowerBounds[i], toLowerBounds[i]);
      }
    } else {
      throw new IllegalArgumentException("No type mapping from " + from);
    }
  }

  /**
   * Resolves all type variables in {@code type} and all downstream types and
   * returns a corresponding type with type variables resolved.
   */
  public final Type resolveType(Type type) {
    checkNotNull(type);
    if (type instanceof TypeVariable) {
      return resolveTypeVariable((TypeVariable<?>) type);
    } else if (type instanceof ParameterizedType) {
      return resolveParameterizedType((ParameterizedType) type);
    } else if (type instanceof GenericArrayType) {
      return resolveGenericArrayType((GenericArrayType) type);
    } else if (type instanceof WildcardType) {
      WildcardType wildcardType = (WildcardType) type;
      return new Types.WildcardTypeImpl(
          resolveTypes(wildcardType.getLowerBounds()),
          resolveTypes(wildcardType.getUpperBounds()));
    } else {
      // if Class<?>, no resolution needed, we are done.
      return type;
    }
  }

  private Type[] resolveTypes(Type[] types) {
    Type[] result = new Type[types.length];
    for (int i = 0; i < types.length; i++) {
      result[i] = resolveType(types[i]);
    }
    return result;
  }

  private Type resolveGenericArrayType(GenericArrayType type) {
    Type componentType = resolveType(type.getGenericComponentType());
    return Types.newArrayType(componentType);
  }

  private Type resolveTypeVariable(final TypeVariable<?> var) {
    final TypeResolver unguarded = this;
    TypeResolver guarded = new TypeResolver(typeTable) {
      @Override Type resolveTypeVariable(
          TypeVariable<?> intermediateVar, TypeResolver guardedResolver) {
        if (intermediateVar.getGenericDeclaration().equals(var.getGenericDeclaration())) {
          return intermediateVar;
        }
        return unguarded.resolveTypeVariable(intermediateVar, guardedResolver);
      }
    };
    return resolveTypeVariable(var, guarded);
  }

  /**
   * Resolves {@code var} using the encapsulated type mapping. If it maps to yet another
   * non-reified type, {@code guardedResolver} is used to do further resolution, which doesn't try
   * to resolve any type variable on generic declarations that are already being resolved.
   */
  Type resolveTypeVariable(TypeVariable<?> var, TypeResolver guardedResolver) {
    checkNotNull(guardedResolver);
    Type type = typeTable.get(var);
    if (type == null) {
      Type[] bounds = var.getBounds();
      if (bounds.length == 0) {
        return var;
      }
      return Types.newTypeVariable(
          var.getGenericDeclaration(),
          var.getName(),
          guardedResolver.resolveTypes(bounds));
    }
    return guardedResolver.resolveType(type); // in case the type is yet another type variable.
  }

  private ParameterizedType resolveParameterizedType(ParameterizedType type) {
    Type owner = type.getOwnerType();
    Type resolvedOwner = (owner == null) ? null : resolveType(owner);
    Type resolvedRawType = resolveType(type.getRawType());

    Type[] vars = type.getActualTypeArguments();
    Type[] resolvedArgs = new Type[vars.length];
    for (int i = 0; i < vars.length; i++) {
      resolvedArgs[i] = resolveType(vars[i]);
    }
    return Types.newParameterizedTypeWithOwner(
        resolvedOwner, (Class<?>) resolvedRawType, resolvedArgs);
  }

  private static <T> T checkNonNullArgument(T arg, String format, Object... messageParams) {
    checkArgument(arg != null, format, messageParams);
    return arg;
  }

  private static <T> T expectArgument(Class<T> type, Object arg) {
    try {
      return type.cast(arg);
    } catch (ClassCastException e) {
      throw new IllegalArgumentException(arg + " is not a " + type.getSimpleName());
    }
  }

  private static final class TypeMappingIntrospector {

    private static final WildcardCapturer wildcardCapturer = new WildcardCapturer();

    private final Map<TypeVariable<?>, Type> mappings = Maps.newHashMap();
    private final Set<Type> introspectedTypes = Sets.newHashSet();

    /**
     * Returns type mappings using type parameters and type arguments found in
     * the generic superclass and the super interfaces of {@code contextClass}.
     */
    static ImmutableMap<TypeVariable<?>, Type> getTypeMappings(
        Type contextType) {
      TypeMappingIntrospector introspector = new TypeMappingIntrospector();
      introspector.introspect(wildcardCapturer.capture(contextType));
      return ImmutableMap.copyOf(introspector.mappings);
    }

    private void introspect(Type type) {
      if (!introspectedTypes.add(type)) {
        return;
      }
      if (type instanceof ParameterizedType) {
        introspectParameterizedType((ParameterizedType) type);
      } else if (type instanceof Class) {
        introspectClass((Class<?>) type);
      } else if (type instanceof TypeVariable) {
        for (Type bound : ((TypeVariable<?>) type).getBounds()) {
          introspect(bound);
        }
      } else if (type instanceof WildcardType) {
        for (Type bound : ((WildcardType) type).getUpperBounds()) {
          introspect(bound);
        }
      }
    }

    private void introspectClass(Class<?> clazz) {
      introspect(clazz.getGenericSuperclass());
      for (Type interfaceType : clazz.getGenericInterfaces()) {
        introspect(interfaceType);
      }
    }

    private void introspectParameterizedType(
        ParameterizedType parameterizedType) {
      Class<?> rawClass = (Class<?>) parameterizedType.getRawType();
      TypeVariable<?>[] vars = rawClass.getTypeParameters();
      Type[] typeArgs = parameterizedType.getActualTypeArguments();
      checkState(vars.length == typeArgs.length);
      for (int i = 0; i < vars.length; i++) {
        map(vars[i], typeArgs[i]);
      }
      introspectClass(rawClass);
      introspect(parameterizedType.getOwnerType());
    }

    private void map(final TypeVariable<?> var, final Type arg) {
      if (mappings.containsKey(var)) {
        // Mapping already established
        // This is possible when following both superClass -> enclosingClass
        // and enclosingclass -> superClass paths.
        // Since we follow the path of superclass first, enclosing second,
        // superclass mapping should take precedence.
        return;
      }
      // First, check whether var -> arg forms a cycle
      for (Type t = arg; t != null; t = mappings.get(t)) {
        if (var.equals(t)) {
          // cycle detected, remove the entire cycle from the mapping so that
          // each type variable resolves deterministically to itself.
          // Otherwise, a F -> T cycle will end up resolving both F and T
          // nondeterministically to either F or T.
          for (Type x = arg; x != null; x = mappings.remove(x)) {}
          return;
        }
      }
      mappings.put(var, arg);
    }
  }

  // This is needed when resolving types against a context with wildcards
  // For example:
  // class Holder<T> {
  //   void set(T data) {...}
  // }
  // Holder<List<?>> should *not* resolve the set() method to set(List<?> data).
  // Instead, it should create a capture of the wildcard so that set() rejects any List<T>.
  private static final class WildcardCapturer {

    private final AtomicInteger id = new AtomicInteger();

    Type capture(Type type) {
      checkNotNull(type);
      if (type instanceof Class) {
        return type;
      }
      if (type instanceof TypeVariable) {
        return type;
      }
      if (type instanceof GenericArrayType) {
        GenericArrayType arrayType = (GenericArrayType) type;
        return Types.newArrayType(capture(arrayType.getGenericComponentType()));
      }
      if (type instanceof ParameterizedType) {
        ParameterizedType parameterizedType = (ParameterizedType) type;
        return Types.newParameterizedTypeWithOwner(
            captureNullable(parameterizedType.getOwnerType()),
            (Class<?>) parameterizedType.getRawType(),
            capture(parameterizedType.getActualTypeArguments()));
      }
      if (type instanceof WildcardType) {
        WildcardType wildcardType = (WildcardType) type;
        Type[] lowerBounds = wildcardType.getLowerBounds();
        if (lowerBounds.length == 0) { // ? extends something changes to capture-of
          Type[] upperBounds = wildcardType.getUpperBounds();
          String name = "capture#" + id.incrementAndGet() + "-of ? extends "
              + Joiner.on('&').join(upperBounds);
          return Types.newTypeVariable(
              WildcardCapturer.class, name, wildcardType.getUpperBounds());
        } else {
          // TODO(benyu): handle ? super T somehow.
          return type;
        }
      }
      throw new AssertionError("must have been one of the known types");
    }

    private Type captureNullable(@Nullable Type type) {
      if (type == null) {
        return null;
      }
      return capture(type);
    }

    private Type[] capture(Type[] types) {
      Type[] result = new Type[types.length];
      for (int i = 0; i < types.length; i++) {
        result[i] = capture(types[i]);
      }
      return result;
    }
  }
}