/* * Copyright 2014 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 org.inferred.freebuilder.processor; import com.google.common.collect.ImmutableSet; import com.sun.source.tree.ExpressionStatementTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.MethodTree; import com.sun.source.tree.ReturnTree; import com.sun.source.tree.StatementTree; import com.sun.source.tree.Tree; import com.sun.source.tree.Tree.Kind; import com.sun.source.util.SimpleTreeVisitor; import com.sun.source.util.TreeScanner; import com.sun.source.util.Trees; import java.util.HashSet; import java.util.Set; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Name; /** Implementation of {@link MethodIntrospector} for javac. */ class JavacMethodIntrospector extends MethodIntrospector { /** * Returns a {@link MethodIntrospector} implementation for the given javac environment. * * @throws IllegalArgumentException if the environment is not from javac */ public static MethodIntrospector instance(ProcessingEnvironment env) { return new JavacMethodIntrospector(Trees.instance(env)); } private final Trees trees; private JavacMethodIntrospector(Trees trees) { this.trees = trees; } @Override public Set<Name> getOwnMethodInvocations(ExecutableElement method) { try { return ImmutableSet.copyOf(trees .getTree(method) .accept(OWN_METHOD_INVOCATIONS_FETCHER, null) .names); } catch (RuntimeException e) { // Fail gracefully return ImmutableSet.<Name>of(); } } /** Data object retuned by {@link #OWN_METHOD_INVOCATIONS_FETCHER}. */ private static class TreeAnalysis { private final Set<Name> names = new HashSet<Name>(); private boolean explicitReturn = false; } /** Tree visitor to find all method invocations that are guaranteed to be hit. */ private static final SimpleTreeVisitor<TreeAnalysis, ?> OWN_METHOD_INVOCATIONS_FETCHER = new SimpleTreeVisitor<TreeAnalysis, Void>() { @Override public TreeAnalysis visitMethod(MethodTree node, Void p) { // A method is guaranteed to call all of its statements in order // UNLESS one of them has an explicit return statement. TreeAnalysis result = new TreeAnalysis(); for (StatementTree statement : node.getBody().getStatements()) { TreeAnalysis statementAnalysis = statement.accept(this, p); result.names.addAll(statementAnalysis.names); if (statementAnalysis.explicitReturn) { result.explicitReturn = true; return result; } } return result; } @Override public TreeAnalysis visitExpressionStatement(ExpressionStatementTree node, Void p) { return node.getExpression().accept(this, p); } @Override public TreeAnalysis visitMethodInvocation(MethodInvocationTree node, Void p) { return node.getMethodSelect().accept(this, p); } @Override public TreeAnalysis visitIdentifier(IdentifierTree node, Void p) { // An identifier is an own method invocation under the condition that we // only hit this case from visitMethodInvocation. TreeAnalysis result = new TreeAnalysis(); result.names.add(node.getName()); return result; } @Override public TreeAnalysis visitMemberSelect(MemberSelectTree node, Void p) { // A member select is an "own method invocation" if the expression is "this", // under the condition that we only hit this case from visitMethodInvocation. TreeAnalysis result = new TreeAnalysis(); ExpressionTree lhs = node.getExpression(); if (lhs.getKind() != Kind.IDENTIFIER) { return result; } if (!((IdentifierTree) lhs).getName().contentEquals("this")) { return result; } result.names.add(node.getIdentifier()); return result; } @Override protected TreeAnalysis defaultAction(Tree node, Void p) { // In general, we have no knowledge about whether a language construct // will invoke a method or not, just based on analysing its children. // For instance, an "if" makes no guarantees, an "if...else" // guarantees the intersection of its paths, and a try...finally // guarantees its finally block. TreeAnalysis result = new TreeAnalysis(); // However, any explicit return call _may_ be hit. result.explicitReturn = (RETURN_TREE_FINDER.scan(node, null) != null); return result; } }; /** Tree scanner to return any ReturnTree, or null if none is present. */ private static final TreeScanner<ReturnTree, ?> RETURN_TREE_FINDER = new TreeScanner<ReturnTree, Void>() { @Override public ReturnTree visitReturn(ReturnTree node, Void p) { return node; } @Override public ReturnTree reduce(ReturnTree r1, ReturnTree r2) { return (r1 != null) ? r1 : r2; } }; }