/*
* Copyright 2015 Google Inc. 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.errorprone.bugpatterns.collectionincompatibletype;
import com.google.auto.value.AutoValue;
import com.google.errorprone.VisitorState;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Types;
import java.util.Collection;
import java.util.Map;
import javax.annotation.Nullable;
/**
* Extracts the necessary information from a {@link MethodInvocationTree} to check whether
* calls to a method are using incompatible types and to emit a helpful error message.
*/
abstract class AbstractCollectionIncompatibleTypeMatcher {
/**
* Returns a matcher for the appropriate method invocation for this matcher. For example,
* this might match {@link Collection#remove(Object)} or {@link Map#containsKey(Object)}.
*/
abstract Matcher<ExpressionTree> methodMatcher();
/**
* Extracts the source type that must be castable to the target type. For example, in this code
* sample:
* <pre>
* {@code
* Collection<Integer> collection;
* collection.contains("foo");
* }
* </pre>
*
* The source type is String.
*
* @return the source type or null if not available
*/
@Nullable
abstract Type extractSourceType(MethodInvocationTree tree, VisitorState state);
/**
* Returns the AST node from which the source type was extracted. Needed to produce readable
* error messages. For example, in this code sample:
* <pre>
* {@code
* Collection<Integer> collection;
* collection.contains("foo");
* }
* </pre>
*
* The source tree is "foo".
*
* @return the source AST node or null if not available
*/
@Nullable
abstract ExpressionTree extractSourceTree(MethodInvocationTree tree, VisitorState state);
/**
* Extracts the target type to which the source type must be castable. For example, in this code
* sample:
* <pre>
* {@code
* Collection<Integer> collection;
* collection.contains("foo");
* }
* </pre>
*
* The target type is Integer.
*
* @return the target type or null if not available
*/
@Nullable
abstract Type extractTargetType(MethodInvocationTree tree, VisitorState state);
@AutoValue
abstract static class MatchResult {
public abstract ExpressionTree sourceTree();
public abstract Type sourceType();
public abstract Type targetType();
public abstract AbstractCollectionIncompatibleTypeMatcher matcher();
public static MatchResult create(
ExpressionTree sourceTree,
Type sourceType,
Type targetType,
AbstractCollectionIncompatibleTypeMatcher matcher) {
return new AutoValue_AbstractCollectionIncompatibleTypeMatcher_MatchResult(
sourceTree, sourceType, targetType, matcher);
}
}
@Nullable
public final MatchResult matches(MethodInvocationTree tree, VisitorState state) {
if (!methodMatcher().matches(tree, state)) {
return null;
}
ExpressionTree sourceTree = extractSourceTree(tree, state);
Type sourceType = extractSourceType(tree, state);
Type targetType = extractTargetType(tree, state);
if (sourceTree == null || sourceType == null || targetType == null) {
return null;
}
return MatchResult.create(
extractSourceTree(tree, state),
extractSourceType(tree, state),
extractTargetType(tree, state),
this);
}
/**
* Extracts the appropriate type argument from a specific supertype of the given {@code type}.
* This handles the case when a subtype has different type arguments than the expected type.
* For example, {@code ClassToInstanceMap<T>} implements {@code Map<Class<? extends T>, T>}.
*
* @param type the (sub)type from which to extract the type argument
* @param superTypeSym the symbol of the supertype on which the type parameter is defined
* @param typeArgIndex the index of the type argument to extract from the supertype
* @param types the {@link Types} utility class from the {@link VisitorState}
* @return the type argument, if defined, or null otherwise
*/
@Nullable
protected static final Type extractTypeArgAsMemberOfSupertype(
Type type, Symbol superTypeSym, int typeArgIndex, Types types) {
Type collectionType = types.asSuper(type, superTypeSym);
if (collectionType == null) {
return null;
}
com.sun.tools.javac.util.List<Type> tyargs = collectionType.getTypeArguments();
if (tyargs.size() <= typeArgIndex) {
// Collection is raw, nothing we can do.
return null;
}
return tyargs.get(typeArgIndex);
}
}