| /** |
| * 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.activemq.filter; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import javax.jms.JMSException; |
| |
| /** |
| * A MultiExpressionEvaluator is used to evaluate multiple expressions in single |
| * method call. <p/> Multiple Expression/ExpressionListener pairs can be added |
| * to a MultiExpressionEvaluator object. When the MultiExpressionEvaluator |
| * object is evaluated, all the registed Expressions are evaluated and then the |
| * associated ExpressionListener is invoked to inform it of the evaluation |
| * result. <p/> By evaluating multiple expressions at one time, some |
| * optimizations can be made to reduce the number of computations normally |
| * required to evaluate all the expressions. <p/> When this class adds an |
| * Expression it wrapps each node in the Expression's AST with a CacheExpression |
| * object. Then each CacheExpression object (one for each node) is placed in the |
| * cachedExpressions map. The cachedExpressions map allows us to find the sub |
| * expressions that are common across two different expressions. When adding an |
| * Expression in, if a sub Expression of the Expression is allready in the |
| * cachedExpressions map, then instead of wrapping the sub expression in a new |
| * CacheExpression object, we reuse the CacheExpression allready int the map. |
| * <p/> To help illustrate what going on, lets try to give an exmample: If we |
| * denote the AST of a Expression as follows: |
| * [AST-Node-Type,Left-Node,Right-Node], then A expression like: "3*5+6" would |
| * result in "[*,3,[+,5,6]]" <p/> If the [*,3,[+,5,6]] expression is added to |
| * the MultiExpressionEvaluator, it would really be converted to: |
| * [c0,[*,3,[c1,[+,5,6]]]] where c0 and c1 represent the CacheExpression |
| * expression objects that cache the results of the * and the + operation. |
| * Constants and Property nodes are not cached. <p/> If later on we add the |
| * following expression [=,11,[+,5,6]] ("11=5+6") to the |
| * MultiExpressionEvaluator it would be converted to: [c2,[=,11,[c1,[+,5,6]]]], |
| * where c2 is a new CacheExpression object but c1 is the same CacheExpression |
| * used in the previous expression. <p/> When the expressions are evaluated, the |
| * c1 CacheExpression object will only evaluate the [+,5,6] expression once and |
| * cache the resulting value. Hence evauating the second expression costs less |
| * because that [+,5,6] is not done 2 times. <p/> Problems: - cacheing the |
| * values introduces overhead. It may be possible to be smarter about WHICH |
| * nodes in the AST are cached and which are not. - Current implementation is |
| * not thread safe. This is because you need a way to invalidate all the cached |
| * values so that the next evaluation re-evaluates the nodes. By going single |
| * threaded, chache invalidation is done quickly by incrementing a 'view' |
| * counter. When a CacheExpressionnotices it's last cached value was generated |
| * in an old 'view', it invalidates its cached value. |
| * |
| * $Date: 2005/08/27 03:52:36 $ |
| */ |
| public class MultiExpressionEvaluator { |
| |
| Map<String, ExpressionListenerSet> rootExpressions = new HashMap<String, ExpressionListenerSet>(); |
| Map<Expression, CacheExpression> cachedExpressions = new HashMap<Expression, CacheExpression>(); |
| |
| int view; |
| |
| /** |
| * A UnaryExpression that caches the result of the nested expression. The |
| * cached value is valid if the |
| * CacheExpression.cview==MultiExpressionEvaluator.view |
| */ |
| public class CacheExpression extends UnaryExpression { |
| short refCount; |
| int cview = view - 1; |
| Object cachedValue; |
| int cachedHashCode; |
| |
| public CacheExpression(Expression realExpression) { |
| super(realExpression); |
| cachedHashCode = realExpression.hashCode(); |
| } |
| |
| /** |
| * @see org.apache.activemq.filter.Expression#evaluate(MessageEvaluationContext) |
| */ |
| public Object evaluate(MessageEvaluationContext message) throws JMSException { |
| if (view == cview) { |
| return cachedValue; |
| } |
| cachedValue = right.evaluate(message); |
| cview = view; |
| return cachedValue; |
| } |
| |
| public int hashCode() { |
| return cachedHashCode; |
| } |
| |
| public boolean equals(Object o) { |
| if (o == null) { |
| return false; |
| } |
| return ((CacheExpression)o).right.equals(right); |
| } |
| |
| public String getExpressionSymbol() { |
| return null; |
| } |
| |
| public String toString() { |
| return right.toString(); |
| } |
| |
| } |
| |
| /** |
| * Multiple listeners my be interested in the results of a single |
| * expression. |
| */ |
| static class ExpressionListenerSet { |
| Expression expression; |
| List<ExpressionListener> listeners = new ArrayList<ExpressionListener>(); |
| } |
| |
| /** |
| * Objects that are interested in the results of an expression should |
| * implement this interface. |
| */ |
| static interface ExpressionListener { |
| void evaluateResultEvent(Expression selector, MessageEvaluationContext message, Object result); |
| } |
| |
| /** |
| * Adds an ExpressionListener to a given expression. When evaluate is |
| * called, the ExpressionListener will be provided the results of the |
| * Expression applied to the evaluated message. |
| */ |
| public void addExpressionListner(Expression selector, ExpressionListener c) { |
| ExpressionListenerSet data = rootExpressions.get(selector.toString()); |
| if (data == null) { |
| data = new ExpressionListenerSet(); |
| data.expression = addToCache(selector); |
| rootExpressions.put(selector.toString(), data); |
| } |
| data.listeners.add(c); |
| } |
| |
| /** |
| * Removes an ExpressionListener from receiving the results of a given |
| * evaluation. |
| */ |
| public boolean removeEventListner(String selector, ExpressionListener c) { |
| String expKey = selector; |
| ExpressionListenerSet d = rootExpressions.get(expKey); |
| // that selector had not been added. |
| if (d == null) { |
| return false; |
| } |
| // that selector did not have that listeners.. |
| if (!d.listeners.remove(c)) { |
| return false; |
| } |
| |
| // If there are no more listeners for this expression.... |
| if (d.listeners.size() == 0) { |
| // Un-cache it... |
| removeFromCache((CacheExpression)d.expression); |
| rootExpressions.remove(expKey); |
| } |
| return true; |
| } |
| |
| /** |
| * Finds the CacheExpression that has been associated with an expression. If |
| * it is the first time the Expression is being added to the Cache, a new |
| * CacheExpression is created and associated with the expression. <p/> This |
| * method updates the reference counters on the CacheExpression to know when |
| * it is no longer needed. |
| */ |
| private CacheExpression addToCache(Expression expr) { |
| |
| CacheExpression n = cachedExpressions.get(expr); |
| if (n == null) { |
| n = new CacheExpression(expr); |
| cachedExpressions.put(expr, n); |
| if (expr instanceof UnaryExpression) { |
| |
| // Cache the sub expressions too |
| UnaryExpression un = (UnaryExpression)expr; |
| un.setRight(addToCache(un.getRight())); |
| |
| } else if (expr instanceof BinaryExpression) { |
| |
| // Cache the sub expressions too. |
| BinaryExpression bn = (BinaryExpression)expr; |
| bn.setRight(addToCache(bn.getRight())); |
| bn.setLeft(addToCache(bn.getLeft())); |
| |
| } |
| } |
| n.refCount++; |
| return n; |
| } |
| |
| /** |
| * Removes an expression from the cache. Updates the reference counters on |
| * the CacheExpression object. When the refernce counter goes to zero, the |
| * entry int the Expression to CacheExpression map is removed. |
| * |
| * @param cn |
| */ |
| private void removeFromCache(CacheExpression cn) { |
| cn.refCount--; |
| Expression realExpr = cn.getRight(); |
| if (cn.refCount == 0) { |
| cachedExpressions.remove(realExpr); |
| } |
| if (realExpr instanceof UnaryExpression) { |
| UnaryExpression un = (UnaryExpression)realExpr; |
| removeFromCache((CacheExpression)un.getRight()); |
| } |
| if (realExpr instanceof BinaryExpression) { |
| BinaryExpression bn = (BinaryExpression)realExpr; |
| removeFromCache((CacheExpression)bn.getRight()); |
| } |
| } |
| |
| /** |
| * Evaluates the message against all the Expressions added to this object. |
| * The added ExpressionListeners are notified of the result of the |
| * evaluation. |
| * |
| * @param message |
| */ |
| public void evaluate(MessageEvaluationContext message) { |
| Collection<ExpressionListenerSet> expressionListeners = rootExpressions.values(); |
| for (Iterator<ExpressionListenerSet> iter = expressionListeners.iterator(); iter.hasNext();) { |
| ExpressionListenerSet els = iter.next(); |
| try { |
| Object result = els.expression.evaluate(message); |
| for (Iterator<ExpressionListener> iterator = els.listeners.iterator(); iterator.hasNext();) { |
| ExpressionListener l = iterator.next(); |
| l.evaluateResultEvent(els.expression, message, result); |
| } |
| } catch (Throwable e) { |
| e.printStackTrace(); |
| } |
| } |
| } |
| } |