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

Java tutorial

Introduction

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

Source

/*
 * Copyright 2013 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 static java.util.logging.Level.SEVERE;

import com.google.auto.value.AutoValue;
import com.google.common.base.CharMatcher;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableClassToInstanceMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.errorprone.fixes.Fix;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.refaster.UStatement.UnifierWithUnconsumedStatements;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import com.sun.source.tree.StatementTree;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCBlock;
import com.sun.tools.javac.tree.JCTree.JCStatement;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.Warner;
import java.io.IOException;
import java.io.StringWriter;
import java.lang.annotation.Annotation;
import java.util.Map;
import java.util.logging.Logger;

/**
 * Template representing a sequence of consecutive statements.
 *
 * @author lowasser@google.com (Louis Wasserman)
 */
@AutoValue
public abstract class BlockTemplate extends Template<BlockTemplateMatch> {
    private static final Logger logger = Logger.getLogger(BlockTemplate.class.toString());

    public static BlockTemplate create(UStatement... templateStatements) {
        return create(ImmutableMap.<String, UType>of(), templateStatements);
    }

    public static BlockTemplate create(Map<String, ? extends UType> expressionArgumentTypes,
            UStatement... templateStatements) {
        return create(ImmutableList.<UTypeVar>of(), expressionArgumentTypes, templateStatements);
    }

    public static BlockTemplate create(Iterable<UTypeVar> typeVariables,
            Map<String, ? extends UType> expressionArgumentTypes, UStatement... templateStatements) {
        return create(ImmutableClassToInstanceMap.<Annotation>builder().build(), typeVariables,
                expressionArgumentTypes, ImmutableList.copyOf(templateStatements));
    }

    public static BlockTemplate create(ImmutableClassToInstanceMap<Annotation> annotations,
            Iterable<UTypeVar> typeVariables, Map<String, ? extends UType> expressionArgumentTypes,
            Iterable<? extends UStatement> templateStatements) {
        return new AutoValue_BlockTemplate(annotations, ImmutableList.copyOf(typeVariables),
                ImmutableMap.copyOf(expressionArgumentTypes), ImmutableList.copyOf(templateStatements));
    }

    public BlockTemplate withStatements(Iterable<? extends UStatement> templateStatements) {
        return create(annotations(), templateTypeVariables(), expressionArgumentTypes(), templateStatements);
    }

    abstract ImmutableList<UStatement> templateStatements();

    /**
     * If the tree is a {@link JCBlock}, returns a list of disjoint matches corresponding to the exact
     * list of template statements found consecutively; otherwise, returns an empty list.
     */
    @Override
    public Iterable<BlockTemplateMatch> match(JCTree tree, Context context) {
        // TODO(lowasser): consider nonconsecutive matches?
        if (tree instanceof JCBlock) {
            JCBlock block = (JCBlock) tree;
            ImmutableList<JCStatement> targetStatements = ImmutableList.copyOf(block.getStatements());
            return matchesStartingAnywhere(block, 0, targetStatements, context).first()
                    .or(List.<BlockTemplateMatch>nil());
        }
        return ImmutableList.of();
    }

    private Choice<List<BlockTemplateMatch>> matchesStartingAtBeginning(final JCBlock block, final int offset,
            final ImmutableList<? extends StatementTree> statements, final Context context) {
        if (statements.isEmpty()) {
            return Choice.none();
        }
        final JCStatement firstStatement = (JCStatement) statements.get(0);
        Choice<UnifierWithUnconsumedStatements> choice = Choice
                .of(UnifierWithUnconsumedStatements.create(new Unifier(context), statements));
        for (UStatement templateStatement : templateStatements()) {
            choice = choice.thenChoose(templateStatement);
        }
        return choice.thenChoose(new Function<UnifierWithUnconsumedStatements, Choice<List<BlockTemplateMatch>>>() {
            @Override
            public Choice<List<BlockTemplateMatch>> apply(UnifierWithUnconsumedStatements state) {
                Unifier unifier = state.unifier();
                Inliner inliner = unifier.createInliner();
                try {
                    Optional<Unifier> checkedUnifier = typecheck(unifier, inliner, new Warner(firstStatement),
                            expectedTypes(inliner), actualTypes(inliner));
                    if (checkedUnifier.isPresent()) {
                        int consumedStatements = statements.size() - state.unconsumedStatements().size();
                        BlockTemplateMatch match = new BlockTemplateMatch(block, checkedUnifier.get(), offset,
                                offset + consumedStatements);
                        return matchesStartingAnywhere(block, offset + consumedStatements,
                                statements.subList(consumedStatements, statements.size()), context)
                                        .transform(prepend(match));
                    }
                } catch (CouldNotResolveImportException e) {
                    // fall through
                }
                return Choice.none();
            }
        });
    }

    private static <T> Function<List<T>, List<T>> prepend(final T t) {
        return new Function<List<T>, List<T>>() {
            @Override
            public List<T> apply(List<T> list) {
                return list.prepend(t);
            }
        };
    }

    private Choice<List<BlockTemplateMatch>> matchesStartingAnywhere(JCBlock block, int offset,
            final ImmutableList<? extends StatementTree> statements, final Context context) {
        Choice<List<BlockTemplateMatch>> choice = Choice.none();
        for (int i = 0; i < statements.size(); i++) {
            choice = choice.or(matchesStartingAtBeginning(block, offset + i,
                    statements.subList(i, statements.size()), context));
        }
        return choice.or(Choice.of(List.<BlockTemplateMatch>nil()));
    }

    /**
     * Returns a {@code String} representation of a statement, including semicolon.
     */
    private static String printStatement(Context context, JCStatement statement) {
        StringWriter writer = new StringWriter();
        try {
            pretty(context, writer).printStat(statement);
        } catch (IOException e) {
            throw new AssertionError("StringWriter cannot throw IOExceptions");
        }
        return writer.toString();
    }

    /**
     * Returns a {@code String} representation of a sequence of statements, with semicolons and
     * newlines.
     */
    private static String printStatements(Context context, Iterable<JCStatement> statements) {
        StringWriter writer = new StringWriter();
        try {
            pretty(context, writer).printStats(com.sun.tools.javac.util.List.from(statements));
        } catch (IOException e) {
            throw new AssertionError("StringWriter cannot throw IOExceptions");
        }
        return writer.toString();
    }

    @Override
    public Fix replace(BlockTemplateMatch match) {
        checkNotNull(match);
        SuggestedFix.Builder fix = SuggestedFix.builder();
        Inliner inliner = match.createInliner();
        Context context = inliner.getContext();
        if (annotations().containsKey(UseImportPolicy.class)) {
            ImportPolicy.bind(context, annotations().getInstance(UseImportPolicy.class).value());
        } else {
            ImportPolicy.bind(context, ImportPolicy.IMPORT_TOP_LEVEL);
        }
        ImmutableList<JCStatement> targetStatements = match.getStatements();
        try {
            ImmutableList.Builder<JCStatement> inlinedStatementsBuilder = ImmutableList.builder();
            for (UStatement statement : templateStatements()) {
                inlinedStatementsBuilder.addAll(statement.inlineStatements(inliner));
            }
            ImmutableList<JCStatement> inlinedStatements = inlinedStatementsBuilder.build();
            int nInlined = inlinedStatements.size();
            int nTargets = targetStatements.size();
            if (nInlined <= nTargets) {
                for (int i = 0; i < nInlined; i++) {
                    fix.replace(targetStatements.get(i), printStatement(context, inlinedStatements.get(i)));
                }
                for (int i = nInlined; i < nTargets; i++) {
                    fix.delete(targetStatements.get(i));
                }
            } else {
                for (int i = 0; i < nTargets - 1; i++) {
                    fix.replace(targetStatements.get(i), printStatement(context, inlinedStatements.get(i)));
                }
                int last = nTargets - 1;
                ImmutableList<JCStatement> remainingInlined = inlinedStatements.subList(last, nInlined);
                fix.replace(targetStatements.get(last),
                        CharMatcher.whitespace().trimTrailingFrom(printStatements(context, remainingInlined)));
            }
        } catch (CouldNotResolveImportException e) {
            logger.log(SEVERE, "Failure to resolve import in replacement", e);
        }
        return addImports(inliner, fix);
    }
}