/* * 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.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import com.google.errorprone.refaster.Bindings.Key; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.Types; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.util.Context; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.annotation.Nullable; /** * A mutable representation of an attempt to match a template source tree against a target source * tree. * * @author Louis Wasserman */ public final class Unifier { private final Bindings bindings; private final Context context; public Unifier(Context context) { this.bindings = Bindings.create(); this.context = checkNotNull(context); } private Unifier(Context context, Bindings bindings) { this.context = new SubContext(context); this.bindings = Bindings.create(bindings); } /** * Returns a {@code Unifier} containing all the bindings from this {@code Unifier}, * but which can succeed or fail independently of this {@code Unifier}. */ public Unifier fork() { return new Unifier(context, bindings); } public Types types() { return Types.instance(context); } public JCExpression thisExpression(Type type) { return TreeMaker.instance(context).This(type); } public Inliner createInliner() { return new Inliner(context, bindings); } @Nullable public <V> V getBinding(Key<V> key) { return bindings.getBinding(key); } public <V> V putBinding(Key<V> key, V value) { checkArgument(!bindings.containsKey(key), "Cannot bind %s more than once", key); return bindings.putBinding(key, value); } public <V> V replaceBinding(Key<V> key, V value) { checkArgument(bindings.containsKey(key), "Binding for %s does not exist", key); return bindings.putBinding(key, value); } /** * Attempts to unify each each element of {@code toUnify} with the corresponding * element of {@code targets}. */ public static <T, U extends Unifiable<? super T>> Unifier unifyList( @Nullable Unifier unifier, @Nullable List<U> toUnify, @Nullable List<T> targets) { return unifyList(unifier, toUnify, targets, false); } public static <T, U extends Unifiable<? super T>> Unifier unifyList( @Nullable Unifier unifier, @Nullable List<U> toUnify, @Nullable List<T> targets, boolean allowVarargs) { if (toUnify == null) { return (targets == null) ? unifier : null; } else if (targets == null || !allowVarargs && toUnify.size() != targets.size()) { return null; } Iterator<U> toUnifyItr = toUnify.iterator(); Iterator<? extends T> targetItr = targets.iterator(); while (unifier != null && toUnifyItr.hasNext()) { U toUnifyNext = toUnifyItr.next(); if (allowVarargs && toUnifyNext instanceof URepeated) { URepeated repeated = (URepeated) toUnifyNext; if (toUnifyItr.hasNext()) { // There are more to unify after repeated. return null; } return unifyRepeated(unifier, repeated, targetItr); } else { unifier = toUnifyNext.unify(targetItr.next(), unifier); } } return unifier; } // Attempts to unify a repeated template variable with a sequence of targets. // Binds the key of repeated to a list of expressions if the unification succeeds. private static <T, U extends Unifiable<? super T>> Unifier unifyRepeated ( Unifier unifier, URepeated repeated, Iterator<? extends T> targetItr) { List<JCExpression> expressions = new ArrayList<>(); while (targetItr.hasNext()) { // Unification of each target uses a fork of unifier, since one key can be bound only once. Unifier forked = unifier.fork(); forked = repeated.unify((JCTree) (targetItr.next()), forked); JCExpression boundExpr = repeated.getUnderlyingBinding(forked); if (boundExpr == null) { return null; } expressions.add(boundExpr); } unifier.putBinding(repeated.key(), expressions); return unifier; } /** * Attempts to unify a nullable template with a nullable target. */ public static <T, U extends Unifiable<? super T>> Unifier unifyNullable( @Nullable Unifier unifier, @Nullable U toUnify, @Nullable T target) { if (toUnify == null && target == null) { return unifier; } else if (unifier != null && toUnify != null && target != null) { return toUnify.unify(target, unifier); } else { return null; } } public Bindings getBindings() { return bindings.snapshot(); } public Context getContext() { return context; } }