[NO ISSUE][COMP] Expression annotation cleanup

- user model changes: no
- storage format changes: no
- interface changes: no

Details:
- Make implementations of IExpressionAnnotation immutable
- Use annotation's class as a key in the annotations map
- Add new annotation manipulation methods to
  AbstractFunctionCallExpression

Change-Id: I0c466d9a28cba9a5e07f65339969271d09060288
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/8543
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Dmitry Lychagin <dmitry.lychagin@couchbase.com>
Reviewed-by: Ali Alsuliman <ali.al.solaiman@gmail.com>
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/DisjunctivePredicateToJoinRule.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/DisjunctivePredicateToJoinRule.java
index e1bbacf..acbcb7d 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/DisjunctivePredicateToJoinRule.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/DisjunctivePredicateToJoinRule.java
@@ -18,11 +18,10 @@
  */
 package org.apache.asterix.optimizer.rules;
 
+import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 
 import org.apache.asterix.common.annotations.SkipSecondaryIndexSearchExpressionAnnotation;
 import org.apache.asterix.metadata.declared.MetadataProvider;
@@ -93,8 +92,8 @@
 
         VariableReferenceExpression varEx = null;
         IAType valType = null;
-        HashSet<AsterixConstantValue> values = new HashSet<AsterixConstantValue>();
-        Map<Object, IExpressionAnnotation> allAnnotations = Collections.emptyMap();
+        HashSet<AsterixConstantValue> values = new HashSet<>();
+        List<IExpressionAnnotation> allAnnotations = Collections.emptyList();
 
         for (Mutable<ILogicalExpression> arg : args) {
             AbstractFunctionCallExpression fctCall;
@@ -134,11 +133,11 @@
             if (!(haveVar && haveConst)) {
                 return false;
             }
-            if (!fctCall.getAnnotations().isEmpty()) {
+            if (fctCall.hasAnnotations()) {
                 if (allAnnotations.isEmpty()) {
-                    allAnnotations = new HashMap<>();
+                    allAnnotations = new ArrayList<>();
                 }
-                allAnnotations.putAll(fctCall.getAnnotations());
+                fctCall.copyAnnotationsInto(allAnnotations);
             }
         }
 
@@ -171,14 +170,13 @@
         scanVarRef.setSourceLocation(sourceLoc);
         eqExp.getArguments().add(new MutableObject<>(scanVarRef));
         eqExp.getArguments().add(new MutableObject<>(varEx.cloneExpression()));
-        if (!allAnnotations.containsKey(SkipSecondaryIndexSearchExpressionAnnotation.INSTANCE)) {
-            eqExp.getAnnotations().put(IndexedNLJoinExpressionAnnotation.INSTANCE,
-                    IndexedNLJoinExpressionAnnotation.INSTANCE);
+        if (!allAnnotations.contains(SkipSecondaryIndexSearchExpressionAnnotation.INSTANCE)) {
+            eqExp.putAnnotation(IndexedNLJoinExpressionAnnotation.INSTANCE);
         }
-        BroadcastExpressionAnnotation bcast = new BroadcastExpressionAnnotation();
-        bcast.setObject(BroadcastExpressionAnnotation.BroadcastSide.LEFT); // Broadcast the OR predicates branch.
-        eqExp.getAnnotations().put(bcast, bcast);
-        eqExp.getAnnotations().putAll(allAnnotations);
+        BroadcastExpressionAnnotation bcast =
+                new BroadcastExpressionAnnotation(BroadcastExpressionAnnotation.BroadcastSide.LEFT); // Broadcast the OR predicates branch.
+        eqExp.putAnnotation(bcast);
+        eqExp.putAnnotations(allAnnotations);
 
         InnerJoinOperator jOp = new InnerJoinOperator(new MutableObject<>(eqExp));
         jOp.setSourceLocation(sourceLoc);
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/FuzzyEqRule.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/FuzzyEqRule.java
index 895746a..15a4638 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/FuzzyEqRule.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/FuzzyEqRule.java
@@ -104,7 +104,7 @@
                     FunctionUtil.getFunctionInfo(simFunctionIdentifier), similarityArgs);
             similarityExp.setSourceLocation(sourceLoc);
             // Add annotations from the original fuzzy-eq function.
-            similarityExp.getAnnotations().putAll(funcExp.getAnnotations());
+            funcExp.copyAnnotationsInto(similarityExp);
             ArrayList<Mutable<ILogicalExpression>> cmpArgs = new ArrayList<Mutable<ILogicalExpression>>();
             cmpArgs.add(new MutableObject<ILogicalExpression>(similarityExp));
             IAObject simThreshold = FuzzyUtils.getSimThreshold(metadataProvider, simFuncName);
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/SimilarityCheckRule.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/SimilarityCheckRule.java
index d4061e0..600bd76 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/SimilarityCheckRule.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/SimilarityCheckRule.java
@@ -325,7 +325,7 @@
         }
         // Preserve all annotations.
         if (simCheckFuncExpr != null) {
-            simCheckFuncExpr.getAnnotations().putAll(funcExpr.getAnnotations());
+            funcExpr.copyAnnotationsInto(simCheckFuncExpr);
         }
         return simCheckFuncExpr;
     }
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/BTreeAccessMethod.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/BTreeAccessMethod.java
index 87a2d03..a8f6549 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/BTreeAccessMethod.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/BTreeAccessMethod.java
@@ -978,13 +978,12 @@
                     //And we were unable to determine its type
                     return false;
                 }
-            } else if (!optFuncExpr.getFuncExpr().getAnnotations()
-                    .containsKey(IndexedNLJoinExpressionAnnotation.INSTANCE)) {
+            } else if (!optFuncExpr.getFuncExpr().hasAnnotation(IndexedNLJoinExpressionAnnotation.class)) {
                 return false;
             }
         }
-        if (!index.isPrimaryIndex() && optFuncExpr.getFuncExpr().getAnnotations()
-                .containsKey(SkipSecondaryIndexSearchExpressionAnnotation.INSTANCE)) {
+        if (!index.isPrimaryIndex()
+                && optFuncExpr.getFuncExpr().hasAnnotation(SkipSecondaryIndexSearchExpressionAnnotation.class)) {
             return false;
         }
         // No additional analysis required for BTrees.
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/InvertedIndexAccessMethod.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/InvertedIndexAccessMethod.java
index 1be0ce0..1c151a1 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/InvertedIndexAccessMethod.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/InvertedIndexAccessMethod.java
@@ -960,8 +960,7 @@
 
     @Override
     public boolean exprIsOptimizable(Index index, IOptimizableFuncExpr optFuncExpr) throws AlgebricksException {
-        if (optFuncExpr.getFuncExpr().getAnnotations()
-                .containsKey(SkipSecondaryIndexSearchExpressionAnnotation.INSTANCE)) {
+        if (optFuncExpr.getFuncExpr().hasAnnotation(SkipSecondaryIndexSearchExpressionAnnotation.class)) {
             return false;
         }
 
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/RTreeAccessMethod.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/RTreeAccessMethod.java
index a4be488..a578d2c 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/RTreeAccessMethod.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/RTreeAccessMethod.java
@@ -370,8 +370,7 @@
 
     @Override
     public boolean exprIsOptimizable(Index index, IOptimizableFuncExpr optFuncExpr) {
-        if (optFuncExpr.getFuncExpr().getAnnotations()
-                .containsKey(SkipSecondaryIndexSearchExpressionAnnotation.INSTANCE)) {
+        if (optFuncExpr.getFuncExpr().hasAnnotation(SkipSecondaryIndexSearchExpressionAnnotation.class)) {
             return false;
         }
         // No additional analysis required.
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/util/AsterixJoinUtils.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/util/AsterixJoinUtils.java
index 3a07e10..bc2c860 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/util/AsterixJoinUtils.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/util/AsterixJoinUtils.java
@@ -68,7 +68,7 @@
             return;
         }
         //Check RangeMap type
-        RangeMap rangeMap = (RangeMap) rangeAnnotation.getObject();
+        RangeMap rangeMap = rangeAnnotation.getRangeMap();
         if (rangeMap.getTag(0, 0) != ATypeTag.DATETIME.serialize() && rangeMap.getTag(0, 0) != ATypeTag.DATE.serialize()
                 && rangeMap.getTag(0, 0) != ATypeTag.TIME.serialize()) {
             IWarningCollector warningCollector = context.getWarningCollector();
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/util/IntervalJoinUtils.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/util/IntervalJoinUtils.java
index fa5ef51..dbe1c9e 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/util/IntervalJoinUtils.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/util/IntervalJoinUtils.java
@@ -23,7 +23,6 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
@@ -50,7 +49,6 @@
 import org.apache.hyracks.algebricks.core.algebra.base.LogicalExpressionTag;
 import org.apache.hyracks.algebricks.core.algebra.base.LogicalVariable;
 import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractFunctionCallExpression;
-import org.apache.hyracks.algebricks.core.algebra.expressions.IExpressionAnnotation;
 import org.apache.hyracks.algebricks.core.algebra.expressions.ScalarFunctionCallExpression;
 import org.apache.hyracks.algebricks.core.algebra.expressions.VariableReferenceExpression;
 import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
@@ -79,14 +77,7 @@
     }
 
     protected static RangeAnnotation findRangeAnnotation(AbstractFunctionCallExpression fexp) {
-        Iterator<IExpressionAnnotation> annotationIter = fexp.getAnnotations().values().iterator();
-        while (annotationIter.hasNext()) {
-            IExpressionAnnotation annotation = annotationIter.next();
-            if (annotation instanceof RangeAnnotation) {
-                return (RangeAnnotation) annotation;
-            }
-        }
-        return null;
+        return fexp.getAnnotation(RangeAnnotation.class);
     }
 
     protected static void setSortMergeIntervalJoinOp(AbstractBinaryJoinOperator op, FunctionIdentifier fi,
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/LangExpressionToPlanTranslator.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/LangExpressionToPlanTranslator.java
index 3c56a98..93cb403 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/LangExpressionToPlanTranslator.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/LangExpressionToPlanTranslator.java
@@ -116,7 +116,6 @@
 import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractFunctionCallExpression.FunctionKind;
 import org.apache.hyracks.algebricks.core.algebra.expressions.AggregateFunctionCallExpression;
 import org.apache.hyracks.algebricks.core.algebra.expressions.ConstantExpression;
-import org.apache.hyracks.algebricks.core.algebra.expressions.IExpressionAnnotation;
 import org.apache.hyracks.algebricks.core.algebra.expressions.ScalarFunctionCallExpression;
 import org.apache.hyracks.algebricks.core.algebra.expressions.UnnestingFunctionCallExpression;
 import org.apache.hyracks.algebricks.core.algebra.expressions.VariableReferenceExpression;
@@ -895,9 +894,7 @@
 
         // Put hints into function call expr.
         if (fcall.hasHints()) {
-            for (IExpressionAnnotation hint : fcall.getHints()) {
-                f.getAnnotations().put(hint, hint);
-            }
+            f.putAnnotations(fcall.getHints());
         }
 
         AssignOperator op = new AssignOperator(v, new MutableObject<>(f));
@@ -1240,9 +1237,7 @@
 
         // Add hints as annotations.
         if (op.hasHints()) {
-            for (IExpressionAnnotation hint : op.getHints()) {
-                currExpr.getAnnotations().put(hint, hint);
-            }
+            currExpr.putAnnotations(op.getHints());
         }
 
         LogicalVariable assignedVar = context.newVar();
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/SqlppExpressionToPlanTranslator.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/SqlppExpressionToPlanTranslator.java
index 8eaf52a..95026a5 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/SqlppExpressionToPlanTranslator.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/SqlppExpressionToPlanTranslator.java
@@ -1111,9 +1111,7 @@
         opExpr.getArguments().add(new MutableObject<>(lhsExpr));
         opExpr.getArguments().add(new MutableObject<>(rhsExpr));
         if (hints != null) {
-            for (IExpressionAnnotation hint : hints) {
-                opExpr.getAnnotations().put(hint, hint);
-            }
+            opExpr.putAnnotations(hints);
         }
         return opExpr;
     }
diff --git a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/annotations/RangeAnnotation.java b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/annotations/RangeAnnotation.java
index 4465167..c4f3452 100644
--- a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/annotations/RangeAnnotation.java
+++ b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/annotations/RangeAnnotation.java
@@ -18,28 +18,27 @@
  */
 package org.apache.asterix.common.annotations;
 
+import java.util.Objects;
+
 import org.apache.hyracks.algebricks.core.algebra.expressions.IExpressionAnnotation;
 import org.apache.hyracks.dataflow.common.data.partition.range.RangeMap;
 
-public class RangeAnnotation implements IExpressionAnnotation {
+public final class RangeAnnotation implements IExpressionAnnotation {
 
-    private RangeMap map;
+    public static final String HINT_STRING = "range";
 
-    @Override
-    public Object getObject() {
+    private final RangeMap map;
+
+    public RangeAnnotation(RangeMap map) {
+        this.map = Objects.requireNonNull(map);
+    }
+
+    public RangeMap getRangeMap() {
         return map;
     }
 
     @Override
-    public void setObject(Object side) {
-        this.map = (RangeMap) side;
+    public String toString() {
+        return HINT_STRING + ':' + map;
     }
-
-    @Override
-    public IExpressionAnnotation copy() {
-        RangeAnnotation rangAnn = new RangeAnnotation();
-        rangAnn.map = map;
-        return rangAnn;
-    }
-
 }
diff --git a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/annotations/SkipSecondaryIndexSearchExpressionAnnotation.java b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/annotations/SkipSecondaryIndexSearchExpressionAnnotation.java
index 4d08b54..3cc34f5 100644
--- a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/annotations/SkipSecondaryIndexSearchExpressionAnnotation.java
+++ b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/annotations/SkipSecondaryIndexSearchExpressionAnnotation.java
@@ -18,20 +18,16 @@
  */
 package org.apache.asterix.common.annotations;
 
-import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractExpressionAnnotation;
 import org.apache.hyracks.algebricks.core.algebra.expressions.IExpressionAnnotation;
 
-public class SkipSecondaryIndexSearchExpressionAnnotation extends AbstractExpressionAnnotation {
+public final class SkipSecondaryIndexSearchExpressionAnnotation implements IExpressionAnnotation {
 
     public static final String HINT_STRING = "skip-index";
+
     public static final SkipSecondaryIndexSearchExpressionAnnotation INSTANCE =
             new SkipSecondaryIndexSearchExpressionAnnotation();
 
-    @Override
-    public IExpressionAnnotation copy() {
-        SkipSecondaryIndexSearchExpressionAnnotation clone = new SkipSecondaryIndexSearchExpressionAnnotation();
-        clone.setObject(object);
-        return clone;
+    private SkipSecondaryIndexSearchExpressionAnnotation() {
     }
 
     @Override
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/annotation/ExcludeFromSelectStarAnnotation.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/annotation/ExcludeFromSelectStarAnnotation.java
index a430ae6..a2998a7 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/annotation/ExcludeFromSelectStarAnnotation.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/annotation/ExcludeFromSelectStarAnnotation.java
@@ -19,15 +19,12 @@
 
 package org.apache.asterix.lang.sqlpp.annotation;
 
-import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractExpressionAnnotation;
 import org.apache.hyracks.algebricks.core.algebra.expressions.IExpressionAnnotation;
 
-public final class ExcludeFromSelectStarAnnotation extends AbstractExpressionAnnotation {
+public final class ExcludeFromSelectStarAnnotation implements IExpressionAnnotation {
 
     public static final ExcludeFromSelectStarAnnotation INSTANCE = new ExcludeFromSelectStarAnnotation();
 
-    @Override
-    public IExpressionAnnotation copy() {
-        return this;
+    private ExcludeFromSelectStarAnnotation() {
     }
 }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
index 5172020..9f81216 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
+++ b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
@@ -206,6 +206,7 @@
 import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
 import org.apache.hyracks.api.exceptions.SourceLocation;
 import org.apache.hyracks.api.exceptions.Warning;
+import org.apache.hyracks.dataflow.common.data.partition.range.RangeMap;
 import org.apache.hyracks.util.LogRedactionUtil;
 import org.apache.hyracks.util.StringUtil;
 
@@ -2736,8 +2737,7 @@
                 annotation = SkipSecondaryIndexSearchExpressionAnnotation.INSTANCE;
                 break;
               case HASH_BROADCAST_JOIN_HINT:
-                annotation = new BroadcastExpressionAnnotation();
-                annotation.setObject(BroadcastExpressionAnnotation.BroadcastSide.RIGHT);
+                annotation = new BroadcastExpressionAnnotation(BroadcastExpressionAnnotation.BroadcastSide.RIGHT);
                 break;
             }
           }
@@ -3487,8 +3487,9 @@
             break;
           case RANGE_HINT:
             try {
-              RangeAnnotation rangeAnn = new RangeAnnotation();
-              rangeAnn.setObject((Object) RangeMapBuilder.parseHint(parseExpression(funcName.hintToken.hintParams)));
+              Expression rangeExpr = parseExpression(funcName.hintToken.hintParams);
+              RangeMap rangeMap = RangeMapBuilder.parseHint(rangeExpr);
+              RangeAnnotation rangeAnn = new RangeAnnotation(rangeMap);
               callExpr.addHint(rangeAnn);
             } catch (CompilationException e) {
               {
@@ -4164,7 +4165,9 @@
               break;
             case RANGE_HINT:
               try {
-                oc.setRangeMap(RangeMapBuilder.parseHint(parseExpression(hintToken.hintParams)));
+                Expression rangeExpr = parseExpression(hintToken.hintParams);
+                RangeMap rangeMap = RangeMapBuilder.parseHint(rangeExpr);
+                oc.setRangeMap(rangeMap);
               } catch (CompilationException e) {
                 throw new SqlppParseException(getSourceLocation(hintToken), e.getMessage());
               }
diff --git a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/AbstractExpressionAnnotation.java b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/AbstractExpressionAnnotation.java
deleted file mode 100644
index de02572..0000000
--- a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/AbstractExpressionAnnotation.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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 org.apache.hyracks.algebricks.core.algebra.expressions;
-
-public abstract class AbstractExpressionAnnotation implements IExpressionAnnotation {
-
-    protected Object object;
-
-    @Override
-    public Object getObject() {
-        return object;
-    }
-
-    @Override
-    public void setObject(Object object) {
-        this.object = object;
-    }
-
-}
diff --git a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/AbstractFunctionCallExpression.java b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/AbstractFunctionCallExpression.java
index 81aac03..7f79e0c 100644
--- a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/AbstractFunctionCallExpression.java
+++ b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/AbstractFunctionCallExpression.java
@@ -52,7 +52,7 @@
     final private List<Mutable<ILogicalExpression>> arguments;
     private Object[] opaqueParameters;
     private final FunctionKind kind;
-    private final Map<Object, IExpressionAnnotation> annotationMap = new HashMap<Object, IExpressionAnnotation>();
+    private final Map<Class<? extends IExpressionAnnotation>, IExpressionAnnotation> annotationMap = new HashMap<>();
 
     public AbstractFunctionCallExpression(FunctionKind kind, IFunctionInfo finfo,
             List<Mutable<ILogicalExpression>> arguments) {
@@ -230,17 +230,40 @@
         }
     }
 
-    public Map<Object, IExpressionAnnotation> getAnnotations() {
-        return annotationMap;
+    public boolean hasAnnotations() {
+        return !annotationMap.isEmpty();
     }
 
-    protected Map<Object, IExpressionAnnotation> cloneAnnotations() {
-        Map<Object, IExpressionAnnotation> m = new HashMap<Object, IExpressionAnnotation>();
-        for (Object k : annotationMap.keySet()) {
-            IExpressionAnnotation annot2 = annotationMap.get(k).copy();
-            m.put(k, annot2);
+    public boolean hasAnnotation(Class<? extends IExpressionAnnotation> annotationType) {
+        return annotationMap.containsKey(annotationType);
+    }
+
+    public <T extends IExpressionAnnotation> T getAnnotation(Class<T> annotationType) {
+        IExpressionAnnotation annotation = annotationMap.get(annotationType);
+        return annotation != null ? annotationType.cast(annotation) : null;
+    }
+
+    public void putAnnotation(IExpressionAnnotation annotation) {
+        annotationMap.put(annotation.getClass(), annotation);
+    }
+
+    public void putAnnotations(Collection<? extends IExpressionAnnotation> annotations) {
+        for (IExpressionAnnotation annotation : annotations) {
+            putAnnotation(annotation);
         }
-        return m;
+    }
+
+    public <T extends IExpressionAnnotation> T removeAnnotation(Class<T> annotationType) {
+        IExpressionAnnotation annotation = annotationMap.remove(annotationType);
+        return annotation != null ? annotationType.cast(annotation) : null;
+    }
+
+    public void copyAnnotationsInto(AbstractFunctionCallExpression outCallExpr) {
+        outCallExpr.putAnnotations(annotationMap.values());
+    }
+
+    public void copyAnnotationsInto(Collection<? super IExpressionAnnotation> outCollection) {
+        outCollection.addAll(annotationMap.values());
     }
 
     private final static void addFD(Collection<FunctionalDependency> fds, LogicalVariable var1, LogicalVariable var2) {
diff --git a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/AggregateFunctionCallExpression.java b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/AggregateFunctionCallExpression.java
index 97c4252..033d739 100644
--- a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/AggregateFunctionCallExpression.java
+++ b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/AggregateFunctionCallExpression.java
@@ -54,7 +54,6 @@
 
     @Override
     public AggregateFunctionCallExpression cloneExpression() {
-        cloneAnnotations();
         List<Mutable<ILogicalExpression>> clonedArgs = cloneArguments();
         AggregateFunctionCallExpression fun = new AggregateFunctionCallExpression(finfo, twoStep, clonedArgs);
         fun.setStepTwoAggregate(stepTwoAggregate);
@@ -62,6 +61,7 @@
         fun.setSourceLocation(sourceLoc);
         // opaqueParameters are not really cloned
         fun.setOpaqueParameters(getOpaqueParameters());
+        copyAnnotationsInto(fun);
         return fun;
     }
 
diff --git a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/BroadcastExpressionAnnotation.java b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/BroadcastExpressionAnnotation.java
index 9eef8f6..79b9e2c 100644
--- a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/BroadcastExpressionAnnotation.java
+++ b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/BroadcastExpressionAnnotation.java
@@ -18,30 +18,33 @@
  */
 package org.apache.hyracks.algebricks.core.algebra.expressions;
 
-public class BroadcastExpressionAnnotation implements IExpressionAnnotation {
+import java.util.Objects;
+
+public final class BroadcastExpressionAnnotation implements IExpressionAnnotation {
 
     public enum BroadcastSide {
         LEFT,
-        RIGHT
+        RIGHT;
+
+        public static BroadcastSide getOppositeSide(BroadcastSide side) {
+            switch (side) {
+                case LEFT:
+                    return RIGHT;
+                case RIGHT:
+                    return LEFT;
+                default:
+                    throw new IllegalStateException(String.valueOf(side));
+            }
+        }
     }
 
-    private BroadcastSide side;
+    private final BroadcastSide side;
 
-    @Override
-    public Object getObject() {
+    public BroadcastExpressionAnnotation(BroadcastSide side) {
+        this.side = Objects.requireNonNull(side);
+    }
+
+    public BroadcastSide getBroadcastSide() {
         return side;
     }
-
-    @Override
-    public void setObject(Object side) {
-        this.side = (BroadcastSide) side;
-    }
-
-    @Override
-    public IExpressionAnnotation copy() {
-        BroadcastExpressionAnnotation hashBcast = new BroadcastExpressionAnnotation();
-        hashBcast.side = side;
-        return hashBcast;
-    }
-
 }
diff --git a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/ExpressionAnnotationNoCopyImpl.java b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/ExpressionAnnotationNoCopyImpl.java
deleted file mode 100644
index 3aa34c8..0000000
--- a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/ExpressionAnnotationNoCopyImpl.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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 org.apache.hyracks.algebricks.core.algebra.expressions;
-
-public class ExpressionAnnotationNoCopyImpl extends AbstractExpressionAnnotation {
-
-    @Override
-    public IExpressionAnnotation copy() {
-        return this;
-    }
-
-}
diff --git a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/IExpressionAnnotation.java b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/IExpressionAnnotation.java
index 165c57f..8bbda78 100644
--- a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/IExpressionAnnotation.java
+++ b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/IExpressionAnnotation.java
@@ -18,10 +18,12 @@
  */
 package org.apache.hyracks.algebricks.core.algebra.expressions;
 
+/**
+ *  Represents an expression annotation.
+ *  <ol>
+ *  <li>Implementation classes are used as keys in the expression annotation map</li>
+ *  <li>Each annotation instance must be an immutable object</li>
+ *  </ol>
+ */
 public interface IExpressionAnnotation {
-    public Object getObject();
-
-    public void setObject(Object object);
-
-    public IExpressionAnnotation copy();
-}
+}
\ No newline at end of file
diff --git a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/IndexedNLJoinExpressionAnnotation.java b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/IndexedNLJoinExpressionAnnotation.java
index 91c0a8b..c026883 100644
--- a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/IndexedNLJoinExpressionAnnotation.java
+++ b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/IndexedNLJoinExpressionAnnotation.java
@@ -18,16 +18,13 @@
  */
 package org.apache.hyracks.algebricks.core.algebra.expressions;
 
-public class IndexedNLJoinExpressionAnnotation extends AbstractExpressionAnnotation {
+public final class IndexedNLJoinExpressionAnnotation implements IExpressionAnnotation {
 
     public static final String HINT_STRING = "indexnl";
+
     public static final IndexedNLJoinExpressionAnnotation INSTANCE = new IndexedNLJoinExpressionAnnotation();
 
-    @Override
-    public IExpressionAnnotation copy() {
-        IndexedNLJoinExpressionAnnotation clone = new IndexedNLJoinExpressionAnnotation();
-        clone.setObject(object);
-        return clone;
+    private IndexedNLJoinExpressionAnnotation() {
     }
 
     @Override
diff --git a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/ScalarFunctionCallExpression.java b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/ScalarFunctionCallExpression.java
index f8b25e2..86f38c4 100644
--- a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/ScalarFunctionCallExpression.java
+++ b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/ScalarFunctionCallExpression.java
@@ -45,9 +45,10 @@
     public ScalarFunctionCallExpression cloneExpression() {
         List<Mutable<ILogicalExpression>> clonedArgs = cloneArguments();
         ScalarFunctionCallExpression funcExpr = new ScalarFunctionCallExpression(finfo, clonedArgs);
-        funcExpr.getAnnotations().putAll(cloneAnnotations());
-        funcExpr.setOpaqueParameters(this.getOpaqueParameters());
         funcExpr.setSourceLocation(sourceLoc);
+        // opaqueParameters are not really cloned
+        funcExpr.setOpaqueParameters(getOpaqueParameters());
+        copyAnnotationsInto(funcExpr);
         return funcExpr;
     }
 
diff --git a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/StatefulFunctionCallExpression.java b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/StatefulFunctionCallExpression.java
index 0d310f7..40ec68f 100644
--- a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/StatefulFunctionCallExpression.java
+++ b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/StatefulFunctionCallExpression.java
@@ -50,11 +50,13 @@
 
     @Override
     public StatefulFunctionCallExpression cloneExpression() {
-        cloneAnnotations();
         List<Mutable<ILogicalExpression>> clonedArgs = cloneArguments();
         StatefulFunctionCallExpression clonedExpr =
                 new StatefulFunctionCallExpression(finfo, propertiesComputer, clonedArgs);
         clonedExpr.setSourceLocation(sourceLoc);
+        // opaqueParameters are not really cloned
+        clonedExpr.setOpaqueParameters(getOpaqueParameters());
+        copyAnnotationsInto(clonedExpr);
         return clonedExpr;
     }
 
diff --git a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/UnnestingFunctionCallExpression.java b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/UnnestingFunctionCallExpression.java
index c5f1ac1..83a793d 100644
--- a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/UnnestingFunctionCallExpression.java
+++ b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/expressions/UnnestingFunctionCallExpression.java
@@ -44,12 +44,13 @@
 
     @Override
     public UnnestingFunctionCallExpression cloneExpression() {
-        cloneAnnotations();
         List<Mutable<ILogicalExpression>> clonedArgs = cloneArguments();
         UnnestingFunctionCallExpression ufce = new UnnestingFunctionCallExpression(finfo, clonedArgs);
-        ufce.setReturnsUniqueValues(returnsUniqueValues);
-        ufce.setOpaqueParameters(this.getOpaqueParameters());
         ufce.setSourceLocation(sourceLoc);
+        ufce.setReturnsUniqueValues(returnsUniqueValues);
+        // opaqueParameters are not really cloned
+        ufce.setOpaqueParameters(this.getOpaqueParameters());
+        copyAnnotationsInto(ufce);
         return ufce;
     }
 
diff --git a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/BroadcastSideSwitchingVisitor.java b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/BroadcastSideSwitchingVisitor.java
index c9da3f6..aa0f642 100644
--- a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/BroadcastSideSwitchingVisitor.java
+++ b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/BroadcastSideSwitchingVisitor.java
@@ -18,9 +18,6 @@
  */
 package org.apache.hyracks.algebricks.core.algebra.operators.logical.visitors;
 
-import java.util.Iterator;
-import java.util.Map;
-
 import org.apache.commons.lang3.mutable.Mutable;
 import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
 import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
@@ -66,32 +63,17 @@
     @Override
     public ILogicalExpression visitScalarFunctionCallExpression(ScalarFunctionCallExpression expr, Void arg)
             throws AlgebricksException {
-        BroadcastExpressionAnnotation.BroadcastSide bSide;
         FunctionIdentifier fi = expr.getFunctionIdentifier();
         if (fi.equals(AlgebricksBuiltinFunctions.AND)) {
             for (Mutable<ILogicalExpression> a : expr.getArguments()) {
                 a.getValue().accept(this, null);
             }
         }
-        Iterator it = expr.getAnnotations().entrySet().iterator();
-        while (it.hasNext()) {
-            Map.Entry pair = (Map.Entry) it.next();
-            if (pair.getKey() instanceof BroadcastExpressionAnnotation) {
-                bSide = (BroadcastExpressionAnnotation.BroadcastSide) ((BroadcastExpressionAnnotation) pair.getValue())
-                        .getObject();
-                switch (bSide) {
-                    case RIGHT:
-                        bSide = BroadcastExpressionAnnotation.BroadcastSide.LEFT;
-                        break;
-                    case LEFT:
-                        bSide = BroadcastExpressionAnnotation.BroadcastSide.RIGHT;
-                        break;
-                    default:
-                        bSide = null;
-                        break;
-                }
-                ((BroadcastExpressionAnnotation) pair.getValue()).setObject(bSide);
-            }
+        BroadcastExpressionAnnotation bcastAnn = expr.removeAnnotation(BroadcastExpressionAnnotation.class);
+        if (bcastAnn != null) {
+            BroadcastExpressionAnnotation.BroadcastSide oppositeSide =
+                    BroadcastExpressionAnnotation.BroadcastSide.getOppositeSide(bcastAnn.getBroadcastSide());
+            expr.putAnnotation(new BroadcastExpressionAnnotation(oppositeSide));
         }
         return null;
     }
diff --git a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/LogicalExpressionDeepCopyWithNewVariablesVisitor.java b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/LogicalExpressionDeepCopyWithNewVariablesVisitor.java
index 6be8ee0..13a758c 100644
--- a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/LogicalExpressionDeepCopyWithNewVariablesVisitor.java
+++ b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/LogicalExpressionDeepCopyWithNewVariablesVisitor.java
@@ -33,7 +33,6 @@
 import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractLogicalExpression;
 import org.apache.hyracks.algebricks.core.algebra.expressions.AggregateFunctionCallExpression;
 import org.apache.hyracks.algebricks.core.algebra.expressions.ConstantExpression;
-import org.apache.hyracks.algebricks.core.algebra.expressions.IExpressionAnnotation;
 import org.apache.hyracks.algebricks.core.algebra.expressions.ScalarFunctionCallExpression;
 import org.apache.hyracks.algebricks.core.algebra.expressions.StatefulFunctionCallExpression;
 import org.apache.hyracks.algebricks.core.algebra.expressions.UnnestingFunctionCallExpression;
@@ -64,9 +63,7 @@
     }
 
     private void deepCopyAnnotations(AbstractFunctionCallExpression src, AbstractFunctionCallExpression dest) {
-        Map<Object, IExpressionAnnotation> srcAnnotations = src.getAnnotations();
-        Map<Object, IExpressionAnnotation> destAnnotations = dest.getAnnotations();
-        srcAnnotations.forEach((key, value) -> destAnnotations.put(key, value.copy()));
+        src.copyAnnotationsInto(dest);
     }
 
     private void deepCopyOpaqueParameters(AbstractFunctionCallExpression src, AbstractFunctionCallExpression dest) {
diff --git a/hyracks-fullstack/algebricks/algebricks-rewriter/src/main/java/org/apache/hyracks/algebricks/rewriter/util/JoinUtils.java b/hyracks-fullstack/algebricks/algebricks-rewriter/src/main/java/org/apache/hyracks/algebricks/rewriter/util/JoinUtils.java
index 6eede7f..eccb6c0 100644
--- a/hyracks-fullstack/algebricks/algebricks-rewriter/src/main/java/org/apache/hyracks/algebricks/rewriter/util/JoinUtils.java
+++ b/hyracks-fullstack/algebricks/algebricks-rewriter/src/main/java/org/apache/hyracks/algebricks/rewriter/util/JoinUtils.java
@@ -21,7 +21,6 @@
 import java.util.Collection;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Set;
 
 import org.apache.commons.lang3.mutable.Mutable;
 import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
@@ -33,7 +32,6 @@
 import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractFunctionCallExpression;
 import org.apache.hyracks.algebricks.core.algebra.expressions.BroadcastExpressionAnnotation;
 import org.apache.hyracks.algebricks.core.algebra.expressions.BroadcastExpressionAnnotation.BroadcastSide;
-import org.apache.hyracks.algebricks.core.algebra.expressions.IExpressionAnnotation;
 import org.apache.hyracks.algebricks.core.algebra.expressions.VariableReferenceExpression;
 import org.apache.hyracks.algebricks.core.algebra.functions.AlgebricksBuiltinFunctions;
 import org.apache.hyracks.algebricks.core.algebra.functions.AlgebricksBuiltinFunctions.ComparisonKind;
@@ -208,12 +206,9 @@
             }
             return side;
         } else {
-            Set<Object> annotationSet = fexp.getAnnotations().keySet();
-            for (Object o : annotationSet) {
-                if (o instanceof BroadcastExpressionAnnotation) {
-                    IExpressionAnnotation ann = (BroadcastExpressionAnnotation) o;
-                    return (BroadcastSide) ann.getObject();
-                }
+            BroadcastExpressionAnnotation bcastAnnnotation = fexp.getAnnotation(BroadcastExpressionAnnotation.class);
+            if (bcastAnnnotation != null) {
+                return bcastAnnnotation.getBroadcastSide();
             }
         }
         return null;