/* * Copyright 2015 Google Inc. * * 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.template.soy.exprtree; import com.google.common.base.Equivalence; import com.google.common.primitives.Doubles; import com.google.common.primitives.Longs; import com.google.template.soy.exprtree.ExprNode.OperatorNode; import com.google.template.soy.exprtree.ExprNode.ParentExprNode; import java.util.Objects; /** * An equivalence relation for expressions. * * <p>This equivalence relation is meant to identify identical subexpressions and as such we ignore * a number of {@link ExprNode} properties. For example: * * <ul> * <li>{@link ExprNode#getSourceLocation()} * <li>{@link ExprNode#getType()} * <li>{@link ExprNode#getParent()} * </ul> */ public final class ExprEquivalence extends Equivalence<ExprNode> { private static final ExprEquivalence INSTANCE = new ExprEquivalence(); public static ExprEquivalence get() { return INSTANCE; } private final AbstractReturningExprNodeVisitor<Integer> hashCodeVisitor = new AbstractReturningExprNodeVisitor<Integer>() { @Override protected Integer visitVarRefNode(VarRefNode node) { return Objects.hashCode(node.getDefnDecl()); } @Override protected Integer visitFieldAccessNode(FieldAccessNode node) { return Objects.hash( wrap(node.getBaseExprChild()), node.getFieldName(), node.isNullSafe()); } @Override protected Integer visitItemAccessNode(ItemAccessNode node) { return Objects.hash( wrap(node.getBaseExprChild()), wrap(node.getKeyExprChild()), node.isNullSafe()); } @Override protected Integer visitFunctionNode(FunctionNode node) { return Objects.hash(pairwise().wrap(node.getChildren()), node.getFunctionName()); } @Override protected Integer visitGlobalNode(GlobalNode node) { return node.getName().hashCode(); } @Override protected Integer visitListLiteralNode(ListLiteralNode node) { return hashChildren(node); } @Override protected Integer visitMapLiteralNode(MapLiteralNode node) { return hashChildren(node); } // literals @Override protected Integer visitIntegerNode(IntegerNode node) { return Longs.hashCode(node.getValue()); } @Override protected Integer visitFloatNode(FloatNode node) { return Doubles.hashCode(node.getValue()); } @Override protected Integer visitStringNode(StringNode node) { return node.getValue().hashCode(); } @Override protected Integer visitExprRootNode(ExprRootNode node) { return hashChildren(node); } @Override protected Integer visitOperatorNode(OperatorNode node) { // operators are determined entirely by their children return hashChildren(node); } @Override protected Integer visitNullNode(NullNode node) { return 0; } @Override protected Integer visitExprNode(ExprNode node) { return 0; } private int hashChildren(ParentExprNode node) { return pairwise().hash(node.getChildren()); } }; final class EqualsVisitor extends AbstractReturningExprNodeVisitor<Boolean> { private final ExprNode other; EqualsVisitor(ExprNode other) { this.other = other; } @Override protected Boolean visitVarRefNode(VarRefNode node) { VarRefNode typedOther = (VarRefNode) other; // VarRefs are considered equivalent if they have identical and non-null VarDefns. if (node.getDefnDecl() != null || typedOther.getDefnDecl() != null) { return typedOther.getDefnDecl() == node.getDefnDecl(); } // When getDefnDecl() are null, we should not directly return true. Instead, we should compare // the names and if both are injected parameters. // This checking seems redundant but it is possible that getDefnDecl will return null if // we haven't assigned the VarDefns yet. This happens in unit tests and will potentially // happen in some passes before we assign the VarDefns. // Note that this might return true for VarRefNodes from different templates. Be careful when // you use this to compare ExprNodes among templates. return typedOther.getName().equals(node.getName()) && typedOther.isDollarSignIjParameter() == node.isDollarSignIjParameter(); } @Override protected Boolean visitFieldAccessNode(FieldAccessNode node) { FieldAccessNode typedOther = (FieldAccessNode) other; return equivalent(node.getBaseExprChild(), typedOther.getBaseExprChild()) && node.getFieldName().equals(typedOther.getFieldName()) && node.isNullSafe() == typedOther.isNullSafe(); } @Override protected Boolean visitItemAccessNode(ItemAccessNode node) { ItemAccessNode typedOther = (ItemAccessNode) other; return equivalent(node.getBaseExprChild(), typedOther.getBaseExprChild()) && equivalent(node.getKeyExprChild(), typedOther.getKeyExprChild()) && node.isNullSafe() == typedOther.isNullSafe(); } @Override protected Boolean visitFunctionNode(FunctionNode node) { FunctionNode typedOther = (FunctionNode) other; return compareChildren(node) && node.getFunctionName().equals(typedOther.getFunctionName()); } @Override protected Boolean visitGlobalNode(GlobalNode node) { return node.getName().equals(((GlobalNode) other).getName()); } @Override protected Boolean visitListLiteralNode(ListLiteralNode node) { return compareChildren(node); } @Override protected Boolean visitMapLiteralNode(MapLiteralNode node) { return compareChildren(node); } // literals @Override protected Boolean visitBooleanNode(BooleanNode node) { return node.getValue() == ((BooleanNode) other).getValue(); } @Override protected Boolean visitIntegerNode(IntegerNode node) { return node.getValue() == ((IntegerNode) other).getValue(); } @Override protected Boolean visitFloatNode(FloatNode node) { return node.getValue() == ((FloatNode) other).getValue(); } @Override protected Boolean visitStringNode(StringNode node) { return node.getValue().equals(((StringNode) other).getValue()); } @Override protected Boolean visitExprRootNode(ExprRootNode node) { return compareChildren(node); } @Override protected Boolean visitOperatorNode(OperatorNode node) { // operators are determined entirely by their children return compareChildren(node); } @Override protected Boolean visitNullNode(NullNode node) { return true; } @Override protected Boolean visitExprNode(ExprNode node) { // default return false; } private boolean compareChildren(ParentExprNode node) { return pairwise().equivalent(node.getChildren(), ((ParentExprNode) other).getChildren()); } } private ExprEquivalence() {} @Override protected boolean doEquivalent(ExprNode a, ExprNode b) { return a.getKind() == b.getKind() && new EqualsVisitor(a).exec(b); } @Override protected int doHash(ExprNode t) { return 31 * t.getKind().hashCode() + hashCodeVisitor.exec(t); } }