com.google.errorprone.refaster.UPlaceholderExpression.java Source code

Java tutorial

Introduction

Here is the source code for com.google.errorprone.refaster.UPlaceholderExpression.java

Source

/*
 * 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 com.google.errorprone.refaster;

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

import com.google.auto.value.AutoValue;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Optional;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TreeVisitor;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCExpression;
import com.sun.tools.javac.tree.JCTree.JCIdent;
import com.sun.tools.javac.tree.TreeCopier;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.Names;
import java.util.Map;

/**
 * {@code UTree} representation of an invocation of a placeholder method.
 *
 * @author lowasser@google.com (Louis Wasserman)
 */
@AutoValue
public abstract class UPlaceholderExpression extends UExpression {

    static UPlaceholderExpression create(PlaceholderMethod placeholder, Iterable<? extends UExpression> arguments) {
        ImmutableList<UVariableDecl> placeholderParams = placeholder.parameters().asList();
        ImmutableList<UExpression> argumentsList = ImmutableList.copyOf(arguments);
        ImmutableMap.Builder<UVariableDecl, UExpression> builder = ImmutableMap.builder();
        for (int i = 0; i < placeholderParams.size(); i++) {
            builder.put(placeholderParams.get(i), argumentsList.get(i));
        }
        return new AutoValue_UPlaceholderExpression(placeholder, builder.build());
    }

    abstract PlaceholderMethod placeholder();

    abstract ImmutableMap<UVariableDecl, UExpression> arguments();

    public static final class PlaceholderParamIdent extends JCIdent {
        final UVariableDecl param;

        PlaceholderParamIdent(UVariableDecl param, Context context) {
            super(Names.instance(context).fromString(param.getName().contents()), null);
            this.param = checkNotNull(param);
        }
    }

    static class UncheckedCouldNotResolveImportException extends RuntimeException {
        UncheckedCouldNotResolveImportException(CouldNotResolveImportException e) {
            super(e);
        }

        @Override
        public synchronized CouldNotResolveImportException getCause() {
            return (CouldNotResolveImportException) super.getCause();
        }
    }

    static TreeCopier<Inliner> copier(final Map<UVariableDecl, UExpression> arguments, Inliner inliner) {
        return new TreeCopier<Inliner>(inliner.maker()) {
            @Override
            public <T extends JCTree> T copy(T tree, Inliner inliner) {
                if (tree == null) {
                    return null;
                }
                T result = super.copy(tree, inliner);
                if (result.toString().equals(tree.toString())) {
                    return tree;
                } else {
                    return result;
                }
            }

            @Override
            public JCTree visitIdentifier(IdentifierTree node, Inliner inliner) {
                if (node instanceof PlaceholderParamIdent) {
                    try {
                        return arguments.get(((PlaceholderParamIdent) node).param).inline(inliner);
                    } catch (CouldNotResolveImportException e) {
                        throw new UncheckedCouldNotResolveImportException(e);
                    }
                } else {
                    return super.visitIdentifier(node, inliner);
                }
            }
        };
    }

    @Override
    public JCExpression inline(Inliner inliner) throws CouldNotResolveImportException {
        /*
         * Copy the original source bound to the placeholder, except anywhere we matched a placeholder
         * parameter, replace that with the corresponding expression in this invocation.
         */
        try {
            return copier(arguments(), inliner).copy(inliner.getBinding(placeholder().exprKey()), inliner);
        } catch (UncheckedCouldNotResolveImportException e) {
            throw e.getCause();
        }
    }

    @Override
    public Kind getKind() {
        return Kind.OTHER;
    }

    @Override
    public <R, D> R accept(TreeVisitor<R, D> visitor, D data) {
        return visitor.visitOther(this, data);
    }

    @Override
    protected Choice<Unifier> defaultAction(Tree node, Unifier unifier) {
        // for now we only match JCExpressions
        if (placeholder().returnType().equals(UPrimitiveType.VOID) || !(node instanceof JCExpression)) {
            return Choice.none();
        }
        final JCExpression expr = (JCExpression) node;

        PlaceholderVerificationVisitor verification = new PlaceholderVerificationVisitor(
                Collections2.transform(placeholder().requiredParameters(), Functions.forMap(arguments())),
                arguments().values());
        if (!verification.scan(node, unifier) || !verification.allRequiredMatched()) {
            return Choice.none();
        }

        /*
         * We copy the tree with a TreeCopier, replacing matches for the parameters with
         * PlaceholderParamIdents, and updating unifierHolder as we unify things, including forbidding
         * references to local variables, etc.
         */
        Choice<? extends PlaceholderUnificationVisitor.State<? extends JCExpression>> states = PlaceholderUnificationVisitor
                .create(TreeMaker.instance(unifier.getContext()), arguments()).unifyExpression(expr,
                        PlaceholderUnificationVisitor.State.create(List.<UVariableDecl>nil(), unifier, null));
        return states.thenOption(
                new Function<PlaceholderUnificationVisitor.State<? extends JCExpression>, Optional<Unifier>>() {
                    @Override
                    public Optional<Unifier> apply(
                            PlaceholderUnificationVisitor.State<? extends JCExpression> state) {
                        if (ImmutableSet.copyOf(state.seenParameters())
                                .containsAll(placeholder().requiredParameters())) {
                            Unifier resultUnifier = state.unifier();
                            JCExpression prevBinding = resultUnifier.getBinding(placeholder().exprKey());
                            if (prevBinding != null) {
                                return prevBinding.toString().equals(state.result().toString())
                                        ? Optional.of(resultUnifier)
                                        : Optional.<Unifier>absent();
                            }
                            JCExpression result = state.result();
                            if (!placeholder().matcher().matches(result,
                                    UMatches.makeVisitorState(expr, resultUnifier))) {
                                return Optional.absent();
                            }
                            result.type = expr.type;
                            resultUnifier.putBinding(placeholder().exprKey(), result);
                            return Optional.of(resultUnifier);
                        } else {
                            return Optional.absent();
                        }
                    }
                });
    }
}