blob: 1bc041b66d3ee7d9674f065623588fc6b1c5f413 [file] [log] [blame]
/**
* 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();
}
}
}
}