/* * 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 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.google.errorprone.refaster.PlaceholderUnificationVisitor.State; import com.google.errorprone.refaster.UPlaceholderExpression.UncheckedCouldNotResolveImportException; import com.sun.source.tree.StatementTree; import com.sun.source.tree.TreeVisitor; import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.JCTree.JCStatement; import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.util.List; /** * A representation of a block placeholder. * * @author lowasser@google.com (Louis Wasserman) */ @AutoValue abstract class UPlaceholderStatement implements UStatement { static UPlaceholderStatement create(PlaceholderMethod placeholder, Iterable<? extends UExpression> arguments, ControlFlowVisitor.Result implementationFlow) { 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_UPlaceholderStatement(placeholder, builder.build(), implementationFlow); } abstract PlaceholderMethod placeholder(); abstract ImmutableMap<UVariableDecl, UExpression> arguments(); abstract ControlFlowVisitor.Result implementationFlow(); @Override public Kind getKind() { return Kind.OTHER; } @Override public <R, D> R accept(TreeVisitor<R, D> visitor, D data) { return visitor.visitOther(this, data); } @AutoValue abstract static class ConsumptionState { static ConsumptionState empty() { return new AutoValue_UPlaceholderStatement_ConsumptionState(0, List.<JCStatement>nil()); } abstract int consumedStatements(); abstract List<JCStatement> placeholderImplInReverseOrder(); ConsumptionState consume(JCStatement impl) { return new AutoValue_UPlaceholderStatement_ConsumptionState(consumedStatements() + 1, placeholderImplInReverseOrder().prepend(impl)); } } @Override public Choice<UnifierWithUnconsumedStatements> apply( final UnifierWithUnconsumedStatements initState) { final PlaceholderUnificationVisitor visitor = PlaceholderUnificationVisitor.create( TreeMaker.instance(initState.unifier().getContext()), arguments()); PlaceholderVerificationVisitor verification = new PlaceholderVerificationVisitor( Collections2.transform(placeholder().requiredParameters(), Functions.forMap(arguments())), arguments().values()); // The choices where we might conceivably have a completed placeholder match. Choice<State<ConsumptionState>> realOptions = Choice.none(); // The choice of consumption states to this point in the block. Choice<State<ConsumptionState>> choiceToHere = Choice.of( State.create(List.<UVariableDecl>nil(), initState.unifier(), ConsumptionState.empty())); if (verification.allRequiredMatched()) { realOptions = choiceToHere.or(realOptions); } for (final StatementTree targetStatement : initState.unconsumedStatements()) { if (!verification.scan(targetStatement, initState.unifier())) { break; // we saw a variable that's not allowed to be referenced } // Consume another statement, or if that fails, fall back to the previous choices... choiceToHere = choiceToHere.thenChoose( new Function<State<ConsumptionState>, Choice<State<ConsumptionState>>>() { @Override public Choice<State<ConsumptionState>> apply( final State<ConsumptionState> consumptionState) { return visitor.unifyStatement(targetStatement, consumptionState).transform( new Function<State<? extends JCStatement>, State<ConsumptionState>>() { @Override public State<ConsumptionState> apply(State<? extends JCStatement> stmtState) { return stmtState.withResult( consumptionState.result().consume(stmtState.result())); } }); } }); if (verification.allRequiredMatched()) { realOptions = choiceToHere.or(realOptions); } } return realOptions.thenOption( new Function<State<ConsumptionState>, Optional<UnifierWithUnconsumedStatements>>() { @Override public Optional<UnifierWithUnconsumedStatements> apply( State<ConsumptionState> consumptionState) { if (ImmutableSet.copyOf(consumptionState.seenParameters()).containsAll( placeholder().requiredParameters())) { Unifier resultUnifier = consumptionState.unifier().fork(); int nConsumedStatements = consumptionState.result().consumedStatements(); java.util.List<? extends StatementTree> remainingStatements = initState .unconsumedStatements().subList(nConsumedStatements, initState.unconsumedStatements().size()); UnifierWithUnconsumedStatements result = UnifierWithUnconsumedStatements.create(resultUnifier, remainingStatements); List<JCStatement> impl = consumptionState.result().placeholderImplInReverseOrder().reverse(); ControlFlowVisitor.Result implFlow = ControlFlowVisitor.INSTANCE.visitStatements(impl); if (implFlow == implementationFlow()) { List<JCStatement> prevBinding = resultUnifier.getBinding(placeholder().blockKey()); if (prevBinding != null && prevBinding.toString().equals(impl.toString())) { return Optional.of(result); } else if (prevBinding == null) { resultUnifier.putBinding(placeholder().blockKey(), impl); return Optional.of(result); } } } return Optional.absent(); } }); } @Override public List<JCStatement> inlineStatements(final Inliner inliner) throws CouldNotResolveImportException { try { Optional<List<JCStatement>> binding = inliner.getOptionalBinding(placeholder().blockKey()); // If a placeholder was used as an expression binding in the @BeforeTemplate, // and as a bare statement or as a return in the @AfterTemplate, we may need to convert. Optional<JCExpression> exprBinding = inliner.getOptionalBinding(placeholder().exprKey()); binding = binding.or(exprBinding.transform(new Function<JCExpression, List<JCStatement>>() { @Override public List<JCStatement> apply(JCExpression expr) { switch (implementationFlow()) { case NEVER_EXITS: return List.of((JCStatement) inliner.maker().Exec(expr)); case ALWAYS_RETURNS: return List.of((JCStatement) inliner.maker().Return(expr)); default: throw new AssertionError(); } } })); return UPlaceholderExpression.copier(arguments(), inliner).copy(binding.get(), inliner); } catch (UncheckedCouldNotResolveImportException e) { throw e.getCause(); } } }