This is an automated email from the ASF dual-hosted git repository.

rmiddleton pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-chainsaw.git


The following commit(s) were added to refs/heads/master by this push:
     new 0cd2b89  Copied the relevant log4j1 extras into chainsaw (#10)
0cd2b89 is described below

commit 0cd2b8996ed332c5e451c5e98500e706e1665ac4
Author: Robert Middleton <[email protected]>
AuthorDate: Fri Dec 31 14:50:56 2021 -0500

    Copied the relevant log4j1 extras into chainsaw (#10)
---
 pom.xml                                            |   5 -
 .../java/org/apache/log4j/rule/AbstractRule.java   |  80 +++++
 src/main/java/org/apache/log4j/rule/AndRule.java   | 127 +++++++
 src/main/java/org/apache/log4j/rule/ColorRule.java | 127 +++++++
 .../java/org/apache/log4j/rule/EqualsRule.java     | 119 +++++++
 .../java/org/apache/log4j/rule/ExistsRule.java     | 105 ++++++
 .../java/org/apache/log4j/rule/ExpressionRule.java | 181 ++++++++++
 .../java/org/apache/log4j/rule/InFixToPostFix.java | 379 +++++++++++++++++++++
 .../java/org/apache/log4j/rule/InequalityRule.java | 162 +++++++++
 .../org/apache/log4j/rule/LevelEqualsRule.java     | 149 ++++++++
 .../org/apache/log4j/rule/LevelInequalityRule.java | 270 +++++++++++++++
 src/main/java/org/apache/log4j/rule/LikeRule.java  | 164 +++++++++
 .../java/org/apache/log4j/rule/NotEqualsRule.java  | 121 +++++++
 .../org/apache/log4j/rule/NotLevelEqualsRule.java  | 149 ++++++++
 src/main/java/org/apache/log4j/rule/NotRule.java   | 104 ++++++
 src/main/java/org/apache/log4j/rule/OrRule.java    | 127 +++++++
 .../apache/log4j/rule/PartialTextMatchRule.java    | 112 ++++++
 src/main/java/org/apache/log4j/rule/Rule.java      |  64 ++++
 .../java/org/apache/log4j/rule/RuleFactory.java    | 187 ++++++++++
 .../org/apache/log4j/rule/TimestampEqualsRule.java | 121 +++++++
 .../apache/log4j/rule/TimestampInequalityRule.java | 142 ++++++++
 .../log4j/spi/LoggingEventFieldResolver.java       | 275 +++++++++++++++
 22 files changed, 3265 insertions(+), 5 deletions(-)

diff --git a/pom.xml b/pom.xml
index c7b5fbf..93ff082 100644
--- a/pom.xml
+++ b/pom.xml
@@ -365,11 +365,6 @@
   <dependencies>
     <dependency>
       <groupId>log4j</groupId>
-      <artifactId>apache-log4j-extras</artifactId>
-      <version>1.1</version>
-    </dependency>
-    <dependency>
-      <groupId>log4j</groupId>
       <artifactId>log4j</artifactId>
       <version>1.2.16</version>
     </dependency>
diff --git a/src/main/java/org/apache/log4j/rule/AbstractRule.java 
b/src/main/java/org/apache/log4j/rule/AbstractRule.java
new file mode 100644
index 0000000..f74bf50
--- /dev/null
+++ b/src/main/java/org/apache/log4j/rule/AbstractRule.java
@@ -0,0 +1,80 @@
+/*
+ * 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.log4j.rule;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.Serializable;
+
+
+/**
+ * An abstract Rule class that provides the PropertyChange support plumbing.
+ *
+ * @author Paul Smith ([email protected])
+ * @author Scott Deboy ([email protected])
+ */
+public abstract class AbstractRule implements Rule, Serializable {
+    /**
+     * Serialization id.
+     */
+  static final long serialVersionUID = -2844288145563025172L;
+
+    /**
+     * PropertySupport instance.
+     */
+  private PropertyChangeSupport propertySupport =
+    new PropertyChangeSupport(this);
+
+    /**
+     * Add property change listener.
+     * @param l listener.
+     */
+  public void addPropertyChangeListener(final PropertyChangeListener l) {
+    propertySupport.addPropertyChangeListener(l);
+  }
+
+    /**
+     * Remove property change listener.
+     * @param l listener.
+     */
+  public void removePropertyChangeListener(final PropertyChangeListener l) {
+    propertySupport.removePropertyChangeListener(l);
+  }
+
+    /**
+     * Send property change notification to attached listeners.
+     * @param propertyName property name.
+     * @param oldVal old value.
+     * @param newVal new value.
+     */
+  protected void firePropertyChange(
+    final String propertyName,
+    final Object oldVal,
+    final Object newVal) {
+    propertySupport.firePropertyChange(propertyName, oldVal, newVal);
+  }
+
+  /**
+   * Send property change notification to attached listeners.
+   * @param evt property change event.
+   */
+  public void firePropertyChange(final PropertyChangeEvent evt) {
+    propertySupport.firePropertyChange(evt);
+  }
+}
diff --git a/src/main/java/org/apache/log4j/rule/AndRule.java 
b/src/main/java/org/apache/log4j/rule/AndRule.java
new file mode 100644
index 0000000..2334cb6
--- /dev/null
+++ b/src/main/java/org/apache/log4j/rule/AndRule.java
@@ -0,0 +1,127 @@
+/*
+ * 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.log4j.rule;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+
+/**
+ * A Rule class implementing a logical 'and'.
+ *
+ * @author Scott Deboy ([email protected])
+ */
+public class AndRule extends AbstractRule {
+    /**
+     * First rule.
+     */
+  private final Rule firstRule;
+    /**
+     * Second rule.
+     */
+  private final Rule secondRule;
+    /**
+     * Serialization id.
+     */
+  static final long serialVersionUID = -8233444426923854651L;
+
+    /**
+     * Create new instance.
+     * @param first first rule.
+     * @param second second rule.
+     */
+  private AndRule(final Rule first, final Rule second) {
+    super();
+    this.firstRule = first;
+    this.secondRule = second;
+  }
+
+    /**
+     * Create rule from top two elements of stack.
+     * @param stack stack of rules.
+     * @return Rule that evaluates true only if both rules are true.
+     */
+  public static Rule getRule(final Stack stack) {
+    if (stack.size() < 2) {
+        throw new IllegalArgumentException(
+                "Invalid AND rule - expected two rules but received "
+                        + stack.size());
+    }
+    Object o2 = stack.pop();
+    Object o1 = stack.pop();
+    if ((o2 instanceof Rule) && (o1 instanceof Rule)) {
+        Rule p2 = (Rule) o2;
+        Rule p1 = (Rule) o1;
+        return new AndRule(p1, p2);
+    }
+    throw new IllegalArgumentException("Invalid AND rule: " + o2 + "..." + o1);
+  }
+
+    /**
+     * Get rule.
+     * @param firstParam first rule.
+     * @param secondParam second rule.
+     * @return Rule that evaluates true only if both rules are true.
+     */
+  public static Rule getRule(final Rule firstParam, final Rule secondParam) {
+    return new AndRule(firstParam, secondParam);
+  }
+
+    /**
+     * {@inheritDoc}
+     */
+  public boolean evaluate(final LoggingEvent event, Map matches) {
+        if (matches == null) {
+            return firstRule.evaluate(event, null) && 
secondRule.evaluate(event, null);
+        }
+        Map tempMatches1 = new HashMap();
+        Map tempMatches2 = new HashMap();
+        boolean result = firstRule.evaluate(event, tempMatches1) && 
secondRule.evaluate(event, tempMatches2);
+        if (result) {
+            for (Iterator iter = 
tempMatches1.entrySet().iterator();iter.hasNext();) {
+                Map.Entry entry = (Map.Entry)iter.next();
+                Object key = entry.getKey();
+                Set value = (Set)entry.getValue();
+                Set mainSet = (Set) matches.get(key);
+                if (mainSet == null) {
+                    mainSet = new HashSet();
+                    matches.put(key, mainSet);
+                }
+                mainSet.addAll(value);
+            }
+            for (Iterator iter = 
tempMatches2.entrySet().iterator();iter.hasNext();) {
+                Map.Entry entry = (Map.Entry)iter.next();
+                Object key = entry.getKey();
+                Set value = (Set)entry.getValue();
+                Set mainSet = (Set) matches.get(key);
+                if (mainSet == null) {
+                    mainSet = new HashSet();
+                    matches.put(key, mainSet);
+                }
+                mainSet.addAll(value);
+            }
+        }
+        return result;
+  }
+}
diff --git a/src/main/java/org/apache/log4j/rule/ColorRule.java 
b/src/main/java/org/apache/log4j/rule/ColorRule.java
new file mode 100644
index 0000000..c3f7465
--- /dev/null
+++ b/src/main/java/org/apache/log4j/rule/ColorRule.java
@@ -0,0 +1,127 @@
+/*
+ * 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.log4j.rule;
+
+import java.awt.Color;
+import java.io.Serializable;
+import java.util.Map;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+
+/**
+ * A Rule class which also holds a color.
+ *
+ * @author Scott Deboy ([email protected])
+ */
+public class ColorRule extends AbstractRule implements Serializable {
+    /**
+     * Serialization id.
+     */
+  static final long serialVersionUID = -794434783372847773L;
+
+    /**
+     * Wrapped rule.
+     */
+  private final Rule rule;
+    /**
+     * Foreground color.
+     */
+  private final Color foregroundColor;
+    /**
+     * Background color.
+     */
+  private final Color backgroundColor;
+    /**
+     * Expression.
+     */
+  private final String expression;
+
+    /**
+     * Create new instance.
+     * @param expression expression.
+     * @param rule rule.
+     * @param backgroundColor background color.
+     * @param foregroundColor foreground color.
+     */
+  public ColorRule(final String expression,
+                   final Rule rule,
+                   final Color backgroundColor,
+                   final Color foregroundColor) {
+    super();
+    this.expression = expression;
+    this.rule = rule;
+    this.backgroundColor = backgroundColor;
+    this.foregroundColor = foregroundColor;
+  }
+
+    /**
+     * Get rule.
+     * @return underlying rule.
+     */
+  public Rule getRule() {
+      return rule;
+  }
+
+    /**
+     * Get foreground color.
+     * @return foreground color.
+     */
+  public Color getForegroundColor() {
+    return foregroundColor;
+  }
+
+    /**
+     * Get background color.
+     * @return background color.
+     */
+  public Color getBackgroundColor() {
+    return backgroundColor;
+  }
+
+    /**
+     * Get expression.
+     * @return expression.
+     */
+  public String getExpression() {
+      return expression;
+  }
+
+    /**
+     * {@inheritDoc}
+     */
+  public boolean evaluate(final LoggingEvent event, Map matches) {
+    //no need for color rules to build matches
+    return (rule != null && rule.evaluate(event, null));
+  }
+
+    /**
+     * {@inheritDoc}
+     */
+  public String toString() {
+      StringBuffer buf = new StringBuffer("color rule - expression: ");
+      buf.append(expression);
+      buf.append(", rule: ");
+      buf.append(rule);
+      buf.append(" bg: ");
+      buf.append(backgroundColor);
+      buf.append(" fg: ");
+      buf.append(foregroundColor);
+      return buf.toString();
+  }
+}
diff --git a/src/main/java/org/apache/log4j/rule/EqualsRule.java 
b/src/main/java/org/apache/log4j/rule/EqualsRule.java
new file mode 100644
index 0000000..298768d
--- /dev/null
+++ b/src/main/java/org/apache/log4j/rule/EqualsRule.java
@@ -0,0 +1,119 @@
+/*
+ * 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.log4j.rule;
+
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.LoggingEventFieldResolver;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+
+/**
+ * A Rule class which returns the result of
+ * performing equals against two strings.
+ *
+ * @author Scott Deboy ([email protected])
+ */
+public class EqualsRule extends AbstractRule {
+    /**
+     * Serialization ID.
+     */
+  static final long serialVersionUID = 1712851553477517245L;
+    /**
+     * Resolver.
+     */
+  private static final LoggingEventFieldResolver RESOLVER =
+    LoggingEventFieldResolver.getInstance();
+    /**
+     * Value.
+     */
+  private final String value;
+    /**
+     * Field.
+     */
+  private final String field;
+
+    /**
+     * Create new instance.
+     * @param field field
+     * @param value value
+     */
+  private EqualsRule(final String field, final String value) {
+    super();
+    if (!RESOLVER.isField(field)) {
+      throw new IllegalArgumentException(
+        "Invalid EQUALS rule - " + field + " is not a supported field");
+    }
+
+    this.field = field;
+    this.value = value;
+  }
+
+    /**
+     * Create new instance from top two elements of stack.
+     * @param stack stack
+     * @return new instance
+     */
+  public static Rule getRule(final Stack stack) {
+    if (stack.size() < 2) {
+      throw new IllegalArgumentException(
+        "Invalid EQUALS rule - expected two parameters but received "
+        + stack.size());
+    }
+
+    String p2 = stack.pop().toString();
+    String p1 = stack.pop().toString();
+
+    return getRule(p1, p2);
+  }
+
+    /**
+     * Create new instance.
+     * @param p1 field, special treatment for level and timestamp.
+     * @param p2 value
+     * @return new instance
+     */
+  public static Rule getRule(final String p1, final String p2) {
+    if (p1.equalsIgnoreCase(LoggingEventFieldResolver.LEVEL_FIELD)) {
+        return LevelEqualsRule.getRule(p2);
+    } else if (p1.equalsIgnoreCase(LoggingEventFieldResolver.TIMESTAMP_FIELD)) 
{
+        return TimestampEqualsRule.getRule(p2);
+    } else {
+        return new EqualsRule(p1, p2);
+    }
+  }
+
+    /** {@inheritDoc} */
+  public boolean evaluate(final LoggingEvent event, Map matches) {
+    Object p2 = RESOLVER.getValue(field, event);
+
+    boolean result = (p2 != null) && p2.toString().equals(value);
+    if (result && matches != null) {
+        Set entries = (Set) matches.get(field.toUpperCase());
+        if (entries == null) {
+            entries = new HashSet();
+            matches.put(field.toUpperCase(), entries);
+        }
+        entries.add(value);
+    }
+    return result;
+  }
+}
diff --git a/src/main/java/org/apache/log4j/rule/ExistsRule.java 
b/src/main/java/org/apache/log4j/rule/ExistsRule.java
new file mode 100644
index 0000000..5dbc6c5
--- /dev/null
+++ b/src/main/java/org/apache/log4j/rule/ExistsRule.java
@@ -0,0 +1,105 @@
+/*
+ * 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.log4j.rule;
+
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.LoggingEventFieldResolver;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+
+/**
+ * A Rule class implementing a not null (and not empty string) check.
+ *
+ * @author Scott Deboy ([email protected])
+ */
+public class ExistsRule extends AbstractRule {
+    /**
+     * Serialization id.
+     */
+  static final long serialVersionUID = -5386265224649967464L;
+    /**
+     * field resolver.
+     */
+  private static final LoggingEventFieldResolver RESOLVER =
+    LoggingEventFieldResolver.getInstance();
+    /**
+     * field name.
+     */
+  private final String field;
+
+    /**
+     * Create new instance.
+     * @param fld field name.
+     */
+  private ExistsRule(final String fld) {
+    super();
+    if (!RESOLVER.isField(fld)) {
+      throw new IllegalArgumentException(
+        "Invalid EXISTS rule - " + fld + " is not a supported field");
+    }
+
+    this.field = fld;
+  }
+
+    /**
+     * Get an instance of ExistsRule.
+     * @param field field.
+     * @return instance of ExistsRule.
+     */
+  public static Rule getRule(final String field) {
+    return new ExistsRule(field);
+  }
+
+    /**
+     * Create an instance of ExistsRule using the
+     * top name on the stack.
+     * @param stack stack
+     * @return instance of ExistsRule.
+     */
+  public static Rule getRule(final Stack stack) {
+    if (stack.size() < 1) {
+      throw new IllegalArgumentException(
+        "Invalid EXISTS rule - expected one parameter but received "
+        + stack.size());
+    }
+
+    return new ExistsRule(stack.pop().toString());
+  }
+
+    /**
+     * {@inheritDoc}
+     */
+  public boolean evaluate(final LoggingEvent event, Map matches) {
+    Object p2 = RESOLVER.getValue(field, event);
+
+    boolean result = !((p2 == null) || (p2.toString().equals("")));
+    if (result && matches != null) {
+        Set entries = (Set) matches.get(field.toUpperCase());
+        if (entries == null) {
+            entries = new HashSet();
+            matches.put(field.toUpperCase(), entries);
+        }
+        entries.add(p2);
+    }
+    return result;
+  }
+}
diff --git a/src/main/java/org/apache/log4j/rule/ExpressionRule.java 
b/src/main/java/org/apache/log4j/rule/ExpressionRule.java
new file mode 100644
index 0000000..875e3b9
--- /dev/null
+++ b/src/main/java/org/apache/log4j/rule/ExpressionRule.java
@@ -0,0 +1,181 @@
+/*
+ * 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.log4j.rule;
+
+import java.util.Map;
+import java.util.Stack;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+
+/**
+ * A Rule class supporting both infix and postfix expressions,
+ * accepting any rule which
+ * is supported by the <code>RuleFactory</code>.
+ *
+ * NOTE: parsing is supported through the use of
+ * <code>StringTokenizer</code>, which
+ * implies two limitations:
+ * 1: all tokens in the expression must be separated by spaces,
+ * including parenthesis
+ * 2: operands which contain spaces MUST be wrapped in single quotes.
+ *    For example, the expression:
+ *      msg == 'some msg'
+ *    is a valid expression.
+ * 3: To group expressions, use parentheses.
+ *    For example, the expression:
+ *      level >= INFO || ( msg == 'some msg' || logger == 'test' )
+ *    is a valid expression.
+ * See org.apache.log4j.rule.InFixToPostFix for a
+ * description of supported operators.
+ * See org.apache.log4j.spi.LoggingEventFieldResolver for field keywords.
+ *
+ * @author Scott Deboy ([email protected])
+ */
+public class ExpressionRule extends AbstractRule {
+    /**
+     * Serialization ID.
+     */
+  static final long serialVersionUID = 5809121703146893729L;
+    /**
+     * Converter.
+     */
+  private static final InFixToPostFix CONVERTER = new InFixToPostFix();
+    /**
+     * Compiler.
+     */
+  private static final PostFixExpressionCompiler COMPILER =
+          new PostFixExpressionCompiler();
+    /**
+     * Rule.
+     */
+  private final Rule rule;
+
+    /**
+     * Create new instance.
+     * @param r rule
+     */
+  private ExpressionRule(final Rule r) {
+    super();
+    this.rule = r;
+  }
+
+    /**
+     * Get rule.
+     * @param expression expression.
+     * @return rule.
+     */
+  public static Rule getRule(final String expression) {
+      return getRule(expression, false);
+  }
+
+    /**
+     * Get rule.
+     * @param expression expression.
+     * @param isPostFix If post-fix.
+     * @return rule
+     */
+  public static Rule getRule(final String expression,
+                             final boolean isPostFix) {
+    String postFix = expression;
+    if (!isPostFix) {
+      postFix = CONVERTER.convert(expression);
+    }
+
+    return new ExpressionRule(COMPILER.compileExpression(postFix));
+  }
+
+    /**
+     * {@inheritDoc}
+     */
+  public boolean evaluate(final LoggingEvent event, Map matches) {
+    return rule.evaluate(event, matches);
+  }
+
+    /**
+     * {@inheritDoc}
+     */
+  public String toString() {
+      return rule.toString();
+  }
+
+  /**
+   * Evaluate a boolean postfix expression.
+   *
+   */
+  static final class PostFixExpressionCompiler {
+      /**
+       * Compile expression.
+       * @param expression expression.
+       * @return rule.
+       */
+    public Rule compileExpression(final String expression) {
+      RuleFactory factory = RuleFactory.getInstance();
+
+      Stack stack = new Stack();
+      InFixToPostFix.CustomTokenizer tokenizer = new 
InFixToPostFix.CustomTokenizer(expression);
+
+          while (tokenizer.hasMoreTokens()) {
+            //examine each token
+            String token = tokenizer.nextToken();
+              if (token.startsWith("'") || token.startsWith("\"")) {
+                String quoteChar = token.substring(0, 1);
+                token = token.substring(1);
+                while (!token.endsWith(quoteChar) && 
tokenizer.hasMoreTokens()) {
+                  token = token + " " + tokenizer.nextToken();
+                }
+                if (token.length() > 0) {
+                  token = token.substring(0, token.length() - 1);
+                }
+              } else {
+                //if a symbol is found, pop 2 off the stack,
+                  // evaluate and push the result
+                if (factory.isRule(token)) {
+                  Rule r = factory.getRule(token, stack);
+                  stack.push(r);
+                  //null out the token so we don't try to push it below
+                  token = null;
+                }
+            }
+              //variables or constants are pushed onto the stack
+              if (token != null && token.length() > 0) {
+                  stack.push(token);
+              }
+          }
+
+          if ((stack.size() == 1) && (!(stack.peek() instanceof Rule))) {
+            //while this may be an attempt at creating an expression,
+            //for ease of use, convert this single entry to a partial-text
+            //match on the MSG field
+            Object o = stack.pop();
+            stack.push("MSG");
+            stack.push(o);
+            return factory.getRule("~=", stack);
+          }
+
+          //stack should contain a single rule if the expression is valid
+          if ((stack.size() != 1) || (!(stack.peek() instanceof Rule))) {
+            throw new IllegalArgumentException("invalid expression: " + 
expression);
+          } else {
+            return (Rule) stack.pop();
+          }
+        }
+  }
+}
+
+
diff --git a/src/main/java/org/apache/log4j/rule/InFixToPostFix.java 
b/src/main/java/org/apache/log4j/rule/InFixToPostFix.java
new file mode 100644
index 0000000..765574a
--- /dev/null
+++ b/src/main/java/org/apache/log4j/rule/InFixToPostFix.java
@@ -0,0 +1,379 @@
+/*
+ * 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.log4j.rule;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Stack;
+import java.util.Vector;
+
+import org.apache.log4j.spi.LoggingEventFieldResolver;
+
+/**
+ * A helper class which converts infix expressions to postfix expressions
+ * Currently grouping is supported, as well as all of the
+ * Rules supported by <code>RuleFactory</code>
+ *
+ * Supports grouping via parens, mult-word operands using single or double 
quotes,
+ * and these operators:
+ *
+ * !        NOT operator
+ * !=       NOT EQUALS operator
+ * ==       EQUALS operator
+ * ~=       CASE-INSENSITIVE equals operator
+ * ||       OR operator
+ * &&       AND operator
+ * like     REGEXP operator
+ * exists   NOT NULL operator
+ * &lt      LESS THAN operator
+ * &gt      GREATER THAN operator
+ * &lt=     LESS THAN EQUALS operator
+ * &gt=     GREATER THAN EQUALS operator
+ *
+ * @author Scott Deboy ([email protected])
+ */
+
+public class InFixToPostFix {
+    /**
+     * Precedence map.
+     */
+  private static final Map precedenceMap = new HashMap();
+    /**
+     * Operators.
+     */
+  private static final List operators = new Vector();
+
+
+  static {
+    //order multi-char operators before single-char operators (will use this 
order during parsing)
+    operators.add("<=");
+    operators.add(">=");
+    operators.add("!=");
+    operators.add("==");
+    operators.add("~=");
+    operators.add("||");
+    operators.add("&&");
+    operators.add("like");
+    operators.add("exists");
+    operators.add("!");
+    operators.add("<");
+    operators.add(">");
+
+    //boolean precedence
+    precedenceMap.put("<", new Integer(3));
+    precedenceMap.put(">", new Integer(3));
+    precedenceMap.put("<=", new Integer(3));
+    precedenceMap.put(">=", new Integer(3));
+
+    precedenceMap.put("!", new Integer(3));
+    precedenceMap.put("!=", new Integer(3));
+    precedenceMap.put("==", new Integer(3));
+    precedenceMap.put("~=", new Integer(3));
+    precedenceMap.put("like", new Integer(3));
+    precedenceMap.put("exists", new Integer(3));
+
+    precedenceMap.put("||", new Integer(2));
+    precedenceMap.put("&&", new Integer(2));
+  }
+    /**
+     * Convert in-fix expression to post-fix.
+     * @param expression in-fix expression.
+     * @return post-fix expression.
+     */
+  public String convert(final String expression) {
+    return infixToPostFix(new CustomTokenizer(expression));
+  }
+
+    /**
+     * Evaluates whether symbol is operand.
+     * @param s symbol.
+     * @return true if operand.
+     */
+  public static boolean isOperand(final String s) {
+    String symbol = s.toLowerCase(Locale.ENGLISH);
+    return (!operators.contains(symbol));
+  }
+
+    /**
+     * Determines whether one symbol precedes another.
+     * @param s1 symbol 1
+     * @param s2 symbol 2
+     * @return true if symbol 1 precedes symbol 2
+     */
+  boolean precedes(final String s1, final String s2) {
+    String symbol1 = s1.toLowerCase(Locale.ENGLISH);
+    String symbol2 = s2.toLowerCase(Locale.ENGLISH);
+
+    if (!precedenceMap.keySet().contains(symbol1)) {
+      return false;
+    }
+
+    if (!precedenceMap.keySet().contains(symbol2)) {
+      return false;
+    }
+
+    int index1 = ((Integer) precedenceMap.get(symbol1)).intValue();
+    int index2 = ((Integer) precedenceMap.get(symbol2)).intValue();
+
+    boolean precedesResult = (index1 < index2);
+
+    return precedesResult;
+  }
+
+    /**
+     * convert in-fix expression to post-fix.
+     * @param tokenizer tokenizer.
+     * @return post-fix expression.
+     */
+  String infixToPostFix(final CustomTokenizer tokenizer) {
+    final String space = " ";
+    StringBuffer postfix = new StringBuffer();
+
+    Stack stack = new Stack();
+
+    while (tokenizer.hasMoreTokens()) {
+      String token = tokenizer.nextToken();
+
+      boolean inText = (token.startsWith("'") && (!token.endsWith("'"))) || 
(token.startsWith("\"") && (!token.endsWith("\"")));
+      String quoteChar = token.substring(0, 1);
+      if (inText) {
+          while (inText && tokenizer.hasMoreTokens()) {
+            token = token + " " + tokenizer.nextToken();
+            inText = !(token.endsWith(quoteChar));
+        }
+      }
+
+      if ("(".equals(token)) {
+        //recurse
+        postfix.append(infixToPostFix(tokenizer));
+        postfix.append(space);
+      } else if (")".equals(token)) {
+        //exit recursion level
+        while (stack.size() > 0) {
+          postfix.append(stack.pop().toString());
+          postfix.append(space);
+        }
+
+        return postfix.toString();
+      } else if (isOperand(token)) {
+        postfix.append(token);
+        postfix.append(space);
+      } else {
+        //operator..
+        //peek the stack..if the top element has a lower precedence than token
+        //(peeked + has lower precedence than token *),
+        // push token onto the stack
+        //otherwise, pop top element off stack and add to postfix string
+        //in a loop until lower precedence or empty..then push token
+        if (stack.size() > 0) {
+
+          String peek = stack.peek().toString();
+
+          if (precedes(peek, token)) {
+            stack.push(token);
+          } else {
+            boolean bypass = false;
+
+            do {
+              if (
+                (stack.size() > 0)
+                  && !precedes(stack.peek().toString(), token)) {
+                postfix.append(stack.pop().toString());
+                postfix.append(space);
+              } else {
+                bypass = true;
+              }
+            } while (!bypass);
+
+            stack.push(token);
+          }
+        } else {
+          stack.push(token);
+        }
+      }
+    }
+
+    while (stack.size() > 0) {
+      postfix.append(stack.pop().toString());
+      postfix.append(space);
+    }
+
+    return postfix.toString();
+  }
+
+  public static class CustomTokenizer {
+    private LinkedList linkedList = new LinkedList();
+
+    public CustomTokenizer(String input) {
+      parseInput(input, linkedList);
+    }
+
+    public void parseInput(String input, LinkedList linkedList) {
+      /*
+     Operators:
+         !
+         !=
+         ==
+         ~=
+         ||
+         &&
+         like
+         exists
+         <
+         <=
+         >
+         >=
+      */
+      //for operators, handle multi-char matches before single-char matches
+      //order does not matter for keywords
+      List keywords = LoggingEventFieldResolver.KEYWORD_LIST;
+      //remove PROP. from the keyword list...it is a keyword in that list, but 
is handled separately here from the other keywords since its name is not fixed
+      keywords.remove("PROP.");
+      int pos = 0;
+      while (pos < input.length()) {
+        if (nextValueIs(input, pos, "'") || nextValueIs(input, pos, "\"")) {
+          pos = handleQuotedString(input, pos, linkedList);
+        }
+        if (nextValueIs(input, pos, "PROP.")) {
+          pos = handleProperty(input, pos, linkedList);
+        }
+        boolean operatorFound = false;
+        for (Iterator iter = operators.iterator();iter.hasNext();) {
+          String operator = (String)iter.next();
+          if (nextValueIs(input, pos, operator)) {
+            operatorFound = true;
+            pos = handle(pos, linkedList, operator);
+          }
+        }
+        boolean keywordFound = false;
+        for (Iterator iter = keywords.iterator();iter.hasNext();) {
+          String keyword = (String)iter.next();
+          if (nextValueIs(input, pos, keyword)) {
+            keywordFound = true;
+            pos = handle(pos, linkedList, keyword);
+          }
+        }
+        if (operatorFound || keywordFound) {
+          continue;
+        }
+        if (nextValueIs(input, pos, ")")) {
+          pos = handle(pos, linkedList, ")");
+        } else if (nextValueIs(input, pos, "(")) {
+          pos = handle(pos, linkedList, "(");
+        } else if (nextValueIs(input, pos, " ")) {
+          pos++;
+        } else {
+          pos = handleText(input, pos, linkedList);
+        }
+      }
+    }
+
+    private boolean nextValueIs(String input, int pos, String value) {
+      return (input.length() >= (pos + value.length())) && 
(input.substring(pos, pos + value.length()).equalsIgnoreCase(value));
+    }
+
+    private int handle(int pos, LinkedList linkedList, String value) {
+      linkedList.add(value);
+      return pos + value.length();
+    }
+
+    private int handleQuotedString(String input, int pos, LinkedList 
linkedList) {
+      String quoteChar = input.substring(pos, pos + 1);
+      int nextSingleQuotePos = input.indexOf(quoteChar, pos + 1);
+      if (nextSingleQuotePos < 0) {
+        throw new IllegalArgumentException("Missing an end quote");
+      }
+      String result = input.substring(pos, nextSingleQuotePos + 1);
+      linkedList.add(result);
+      return nextSingleQuotePos + 1;
+    }
+
+    private int handleText(String input, int pos, LinkedList linkedList) {
+     StringBuffer text = new StringBuffer("");
+     int newPos = pos;
+     while (newPos < input.length()) {
+       if (nextValueIs(input, newPos, " ")) {
+         linkedList.add(text);
+         return newPos;
+       }
+       if (nextValueIs(input, newPos, "(")) {
+         linkedList.add(text);
+         return newPos;
+       }
+       if (nextValueIs(input, newPos, ")")) {
+         linkedList.add(text);
+         return newPos;
+       }
+       for (Iterator iter = operators.iterator();iter.hasNext();) {
+         String operator = (String)iter.next();
+         if (nextValueIs(input, newPos, operator)) {
+           linkedList.add(text);
+           return newPos;
+         }
+       }
+       text.append(input.substring(newPos, ++newPos));
+     }
+     //don't add empty text entry (may be at end)
+     if (!text.toString().trim().equals("")) {
+      linkedList.add(text);
+     }
+     return newPos;
+    }
+
+    private int handleProperty(String input, int pos, LinkedList linkedList) {
+     int propertyPos = pos + "PROP.".length();
+     StringBuffer propertyName = new StringBuffer("PROP.");
+     while (propertyPos < input.length()) {
+       if (nextValueIs(input, propertyPos, " ")) {
+         linkedList.add(propertyName);
+         return propertyPos;
+       }
+       if (nextValueIs(input, propertyPos, "(")) {
+         linkedList.add(propertyName);
+         return propertyPos;
+       }
+       if (nextValueIs(input, propertyPos, ")")) {
+         linkedList.add(propertyName);
+         return propertyPos;
+       }
+       for (Iterator iter = operators.iterator();iter.hasNext();) {
+         String operator = (String)iter.next();
+         if (nextValueIs(input, propertyPos, operator)) {
+           linkedList.add(propertyName);
+           return propertyPos;
+         }
+       }
+       propertyName.append(input.substring(propertyPos, ++propertyPos));
+     }
+      linkedList.add(propertyName);
+      return propertyPos;
+    }
+
+    public boolean hasMoreTokens() {
+      return linkedList.size() > 0;
+    }
+
+    public String nextToken() {
+      return linkedList.remove().toString();
+    }
+  }
+}
diff --git a/src/main/java/org/apache/log4j/rule/InequalityRule.java 
b/src/main/java/org/apache/log4j/rule/InequalityRule.java
new file mode 100644
index 0000000..8395b75
--- /dev/null
+++ b/src/main/java/org/apache/log4j/rule/InequalityRule.java
@@ -0,0 +1,162 @@
+/*
+ * 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.log4j.rule;
+
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.LoggingEventFieldResolver;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+
+/**
+ * A Rule class implementing inequality evaluation.
+ * expects to be able to convert two values to longs.
+ * If a specific inequality evaluation class has been provided
+ * for the event field, the appropriate rule is returned.
+ * (For example, if the expression is Level &lt DEBUG,
+ * a LevelInequalityRule is returned).
+ *
+ * @author Scott Deboy ([email protected])
+ */
+public class InequalityRule extends AbstractRule {
+    /**
+     * Serialization ID.
+     */
+  static final long serialVersionUID = -5592986598528885122L;
+    /**
+     * field RESOLVER.
+     */
+  private static final LoggingEventFieldResolver RESOLVER =
+          LoggingEventFieldResolver.getInstance();
+    /**
+     * Field name.
+     */
+  private final String field;
+    /**
+     * Comparison value.
+     */
+  private final String value;
+    /**
+     * Inequality symbol.
+     */
+  private final String inequalitySymbol;
+
+    /**
+     * Create new instance.
+     * @param inequalitySymbol inequality symbol.
+     * @param field field
+     * @param value comparison value.
+     */
+  private InequalityRule(
+    final String inequalitySymbol,
+    final String field,
+    final String value) {
+    super();
+    this.inequalitySymbol = inequalitySymbol;
+    if (!RESOLVER.isField(field)) {
+        throw new IllegalArgumentException("Invalid " + inequalitySymbol
+                + " rule - " + field + " is not a supported field");
+    }
+
+    this.field = field;
+    this.value = value;
+  }
+
+    /**
+     * Create new instance from top two elements on stack.
+     * @param inequalitySymbol inequality symbol.
+     * @param stack stack.
+     * @return rule.
+     */
+  public static Rule getRule(final String inequalitySymbol,
+                             final Stack stack) {
+      if (stack.size() < 2) {
+          throw new IllegalArgumentException("Invalid " + inequalitySymbol
+                  + " rule - expected two parameters but received "
+                  + stack.size());
+      } 
+
+      String p2 = stack.pop().toString();
+      String p1 = stack.pop().toString();
+      return getRule(inequalitySymbol, p1, p2);
+  }
+
+    /**
+     * Create new instance from top two elements on stack.
+     * @param inequalitySymbol inequality symbol.
+     * @param field field.
+     * @param value comparison value.
+     * @return rule.
+     */
+  public static Rule getRule(final String inequalitySymbol,
+                             final String field,
+                             final String value) {
+    if (field.equalsIgnoreCase(LoggingEventFieldResolver.LEVEL_FIELD)) {
+      //push the value back on the stack and
+        // allow the level-specific rule pop values
+      return LevelInequalityRule.getRule(inequalitySymbol, value);
+    } else if (
+            field.equalsIgnoreCase(LoggingEventFieldResolver.TIMESTAMP_FIELD)) 
{
+      return TimestampInequalityRule.getRule(inequalitySymbol, value);
+    } else {
+      return new InequalityRule(inequalitySymbol, field, value);
+    }
+  }
+
+    /** {@inheritDoc} */
+  public boolean evaluate(final LoggingEvent event, Map matches) {
+    long first = 0;
+      try {
+          first = new Long(RESOLVER.getValue(field, 
event).toString()).longValue();
+    } catch (NumberFormatException nfe) {
+      return false;
+    }
+
+    long second = 0;
+
+    try {
+      second = new Long(value).longValue();
+    } catch (NumberFormatException nfe) {
+      return false;
+    }
+
+    boolean result = false;
+
+    if ("<".equals(inequalitySymbol)) {
+      result = first < second;
+    } else if (">".equals(inequalitySymbol)) {
+      result = first > second;
+    } else if ("<=".equals(inequalitySymbol)) {
+      result = first <= second;
+    } else if (">=".equals(inequalitySymbol)) {
+      result = first >= second;
+    }
+    if (result && matches != null) {
+        Set entries = (Set) matches.get(field.toUpperCase());
+        if (entries == null) {
+            entries = new HashSet();
+            matches.put(field.toUpperCase(), entries);
+        }
+        entries.add(String.valueOf(first));
+    }
+    return result;
+  }
+}
diff --git a/src/main/java/org/apache/log4j/rule/LevelEqualsRule.java 
b/src/main/java/org/apache/log4j/rule/LevelEqualsRule.java
new file mode 100644
index 0000000..68219bf
--- /dev/null
+++ b/src/main/java/org/apache/log4j/rule/LevelEqualsRule.java
@@ -0,0 +1,149 @@
+/*
+ * 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.log4j.rule;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.helpers.UtilLoggingLevel;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.LoggingEventFieldResolver;
+
+/**
+ * A Rule class implementing equals against two levels.
+ *
+ * @author Scott Deboy ([email protected])
+ */
+public class LevelEqualsRule extends AbstractRule {
+    /**
+     * Serialization ID.
+     */
+    static final long serialVersionUID = -3638386582899583994L;
+
+    /**
+     * Level.
+     */
+    private transient Level level;
+
+    /**
+     * List of levels.
+     */
+    private static List levelList = new LinkedList();
+
+    static {
+        populateLevels();
+    }
+
+    /**
+     * Create new instance.
+     * @param level level.
+     */
+    private LevelEqualsRule(final Level level) {
+        super();
+        this.level = level;
+    }
+
+    /**
+     * Populate list of levels.
+     */
+    private static void populateLevels() {
+        levelList = new LinkedList();
+
+        levelList.add(Level.FATAL.toString());
+        levelList.add(Level.ERROR.toString());
+        levelList.add(Level.WARN.toString());
+        levelList.add(Level.INFO.toString());
+        levelList.add(Level.DEBUG.toString());
+               Level trace = Level.toLevel(5000, null);
+               if (trace != null) {
+                       levelList.add(trace.toString());
+           }
+    }
+
+    /**
+     * Create new rule.
+     * @param value name of level.
+     * @return instance of LevelEqualsRule.
+     */
+    public static Rule getRule(final String value) {
+        Level thisLevel;
+        if (levelList.contains(value.toUpperCase())) {
+            thisLevel = Level.toLevel(value.toUpperCase());
+          } else {
+            thisLevel = UtilLoggingLevel.toLevel(value.toUpperCase());
+        }
+
+        return new LevelEqualsRule(thisLevel);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean evaluate(final LoggingEvent event, Map matches) {
+        //both util.logging and log4j contain 'info' - use the int values 
instead of equality
+        //info level set to the same value for both levels
+        Level eventLevel = event.getLevel();
+        boolean result = (level.toInt() == eventLevel.toInt());
+        if (result && matches != null) {
+            Set entries = (Set) 
matches.get(LoggingEventFieldResolver.LEVEL_FIELD);
+            if (entries == null) {
+                entries = new HashSet();
+                matches.put(LoggingEventFieldResolver.LEVEL_FIELD, entries);
+            }
+            entries.add(eventLevel);
+        }
+        return result;
+    }
+
+    /**
+     * Deserialize the state of the object.
+     *
+     * @param in object input stream.
+     *
+     * @throws IOException if error in reading stream for deserialization.
+     */
+    private void readObject(final java.io.ObjectInputStream in)
+            throws IOException {
+        populateLevels();
+        boolean isUtilLogging = in.readBoolean();
+        int levelInt = in.readInt();
+        if (isUtilLogging) {
+            level = UtilLoggingLevel.toLevel(levelInt);
+        } else {
+            level = Level.toLevel(levelInt);
+        }
+    }
+
+    /**
+     * Serialize the state of the object.
+     *
+     * @param out object output stream.
+     *
+     * @throws IOException if error in writing stream during serialization.
+     */
+    private void writeObject(final java.io.ObjectOutputStream out)
+            throws IOException {
+        out.writeBoolean(level instanceof UtilLoggingLevel);
+        out.writeInt(level.toInt());
+    }
+}
diff --git a/src/main/java/org/apache/log4j/rule/LevelInequalityRule.java 
b/src/main/java/org/apache/log4j/rule/LevelInequalityRule.java
new file mode 100644
index 0000000..1662a17
--- /dev/null
+++ b/src/main/java/org/apache/log4j/rule/LevelInequalityRule.java
@@ -0,0 +1,270 @@
+/*
+ * 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.log4j.rule;
+
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.helpers.UtilLoggingLevel;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.LoggingEventFieldResolver;
+
+/**
+ * A Rule class implementing inequality evaluation for Levels (log4j and
+ * util.logging) using the toInt method.
+ *
+ * @author Scott Deboy ([email protected])
+ */
+public class LevelInequalityRule {
+    /**
+     * Level list.
+     */
+    private static List levelList;
+    /**
+     * List equivalents of java.util.logging levels.
+     */
+    private static List utilLoggingLevelList;
+
+
+    static {
+        populateLevels();
+    }
+
+    /**
+     * Create new instance.
+     */
+    private LevelInequalityRule() {
+        super();
+    }
+
+    /**
+     * Populate list of levels.
+     */
+    private static void populateLevels() {
+        levelList = new LinkedList();
+
+        levelList.add(Level.FATAL.toString());
+        levelList.add(Level.ERROR.toString());
+        levelList.add(Level.WARN.toString());
+        levelList.add(Level.INFO.toString());
+        levelList.add(Level.DEBUG.toString());
+               Level trace = Level.toLevel(5000, null);
+               if (trace != null) {
+                       levelList.add(trace.toString());
+               }
+
+        utilLoggingLevelList = new LinkedList();
+
+        utilLoggingLevelList.add(UtilLoggingLevel.SEVERE.toString());
+        utilLoggingLevelList.add(UtilLoggingLevel.WARNING.toString());
+        utilLoggingLevelList.add(UtilLoggingLevel.INFO.toString());
+        utilLoggingLevelList.add(UtilLoggingLevel.CONFIG.toString());
+        utilLoggingLevelList.add(UtilLoggingLevel.FINE.toString());
+        utilLoggingLevelList.add(UtilLoggingLevel.FINER.toString());
+        utilLoggingLevelList.add(UtilLoggingLevel.FINEST.toString());
+
+    }
+
+    /**
+     * Create new rule.
+     * @param inequalitySymbol inequality symbol.
+     * @param value Symbolic name of comparison level.
+     * @return instance of AbstractRule.
+     */
+    public static Rule getRule(final String inequalitySymbol,
+                               final String value) {
+
+        Level thisLevel;
+
+        //if valid util.logging levels are used against events
+        // with log4j levels, the
+        //DEBUG level is used and an illegalargumentexception won't be 
generated
+
+        //an illegalargumentexception is only generated
+        // if the user types a level name
+        //that doesn't exist as either a log4j or util.logging level name
+        if (levelList.contains(value.toUpperCase())) {
+            thisLevel = Level.toLevel(value.toUpperCase());
+        } else if (utilLoggingLevelList.contains(value.toUpperCase())) {
+            thisLevel = UtilLoggingLevel.toLevel(value.toUpperCase());
+        } else {
+            throw new IllegalArgumentException(
+                    "Invalid level inequality rule - " + value
+                            + " is not a supported level");
+        }
+
+        if ("<".equals(inequalitySymbol)) {
+            return new LessThanRule(thisLevel);
+        }
+        if (">".equals(inequalitySymbol)) {
+            return new GreaterThanRule(thisLevel);
+        }
+        if ("<=".equals(inequalitySymbol)) {
+            return new LessThanEqualsRule(thisLevel);
+        }
+        if (">=".equals(inequalitySymbol)) {
+            return new GreaterThanEqualsRule(thisLevel);
+        }
+
+        return null;
+    }
+
+    /**
+     * Rule returning true if event level less than specified level.
+     */
+    private static final class LessThanRule extends AbstractRule {
+        /**
+         * Comparison level.
+         */
+        private final int newLevelInt;
+
+        /**
+         * Create new instance.
+         * @param level comparison level.
+         */
+        public LessThanRule(final Level level) {
+            super();
+            newLevelInt = level.toInt();
+        }
+
+        /** {@inheritDoc} */
+        public boolean evaluate(final LoggingEvent event, Map matches) {
+            Level eventLevel = event.getLevel();
+            boolean result = (eventLevel.toInt() < newLevelInt);
+            if (result && matches != null) {
+                Set entries = (Set) 
matches.get(LoggingEventFieldResolver.LEVEL_FIELD);
+                if (entries == null) {
+                    entries = new HashSet();
+                    matches.put(LoggingEventFieldResolver.LEVEL_FIELD, 
entries);
+                }
+                entries.add(eventLevel);
+            }
+            return result;
+        }
+    }
+
+    /**
+     * Rule returning true if event level greater than specified level.
+     */
+    private static final class GreaterThanRule extends AbstractRule {
+        /**
+         * Comparison level.
+         */
+        private final int newLevelInt;
+
+        /**
+         * Create new instance.
+         * @param level comparison level.
+         */
+        public GreaterThanRule(final Level level) {
+            super();
+            newLevelInt = level.toInt();
+        }
+
+        /** {@inheritDoc} */
+        public boolean evaluate(final LoggingEvent event, Map matches) {
+            Level eventLevel = event.getLevel();
+            boolean result = (eventLevel.toInt() > newLevelInt);
+            if (result && matches != null) {
+                Set entries = (Set) 
matches.get(LoggingEventFieldResolver.LEVEL_FIELD);
+                if (entries == null) {
+                    entries = new HashSet();
+                    matches.put(LoggingEventFieldResolver.LEVEL_FIELD, 
entries);
+                }
+                entries.add(eventLevel);
+            }
+            return result;
+        }
+    }
+
+    /**
+     * Rule returning true if event level greater than
+     * or equal to specified level.
+     */
+    private static final class GreaterThanEqualsRule extends AbstractRule {
+        /**
+         * Comparison level.
+         */
+        private final int newLevelInt;
+
+        /**
+         * Create new instance.
+         * @param level comparison level.
+         */
+        public GreaterThanEqualsRule(final Level level) {
+            super();
+            newLevelInt = level.toInt();
+        }
+
+        /** {@inheritDoc} */
+        public boolean evaluate(final LoggingEvent event, Map matches) {
+            Level eventLevel = event.getLevel();
+            boolean result = eventLevel.toInt() >= newLevelInt;
+            if (result && matches != null) {
+                Set entries = (Set) 
matches.get(LoggingEventFieldResolver.LEVEL_FIELD);
+                if (entries == null) {
+                    entries = new HashSet();
+                    matches.put(LoggingEventFieldResolver.LEVEL_FIELD, 
entries);
+                }
+                entries.add(eventLevel);
+            }
+            return result;
+        }
+    }
+
+    /**
+     * Rule returning true if event level less than or
+     * equal to specified level.
+     */
+
+    private static final class LessThanEqualsRule extends AbstractRule {
+        /**
+         * Comparison level.
+         */
+        private final int newLevelInt;
+
+        /**
+         * Create new instance.
+         * @param level comparison level.
+         */
+        public LessThanEqualsRule(final Level level) {
+            super();
+            newLevelInt = level.toInt();
+        }
+
+        /** {@inheritDoc} */
+        public boolean evaluate(final LoggingEvent event, Map matches) {
+            Level eventLevel = event.getLevel();
+            boolean result = eventLevel.toInt() <= newLevelInt;
+            if (result && matches != null) {
+                Set entries = (Set) 
matches.get(LoggingEventFieldResolver.LEVEL_FIELD);
+                if (entries == null) {
+                    entries = new HashSet();
+                    matches.put(LoggingEventFieldResolver.LEVEL_FIELD, 
entries);
+                }
+                entries.add(eventLevel);
+            }
+
+            return result;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/log4j/rule/LikeRule.java 
b/src/main/java/org/apache/log4j/rule/LikeRule.java
new file mode 100644
index 0000000..ffc7a69
--- /dev/null
+++ b/src/main/java/org/apache/log4j/rule/LikeRule.java
@@ -0,0 +1,164 @@
+/*
+ * 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.log4j.rule;
+
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.LoggingEventFieldResolver;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * A Rule class supporting java.util.regex regular expression syntax.
+ *
+ * @author Scott Deboy ([email protected])
+ */
+public class LikeRule extends AbstractRule {
+    /**
+     * Serialization ID.
+     */
+  static final long serialVersionUID = -3375458885595683156L;
+
+    /**
+     * Resolver.
+     */
+  private static final LoggingEventFieldResolver RESOLVER =
+            LoggingEventFieldResolver.getInstance();
+    /**
+     * Pattern.
+     */
+  private transient Pattern pattern;
+    /**
+     * Regular expression matcher.
+     */
+  private transient Matcher matcher = null;
+    /**
+     * Field.
+     */
+  private transient String field;
+
+    /**
+     * Create new instance.
+     * @param field field
+     * @param pattern pattern
+     */
+  private LikeRule(final String field, final Pattern pattern) {
+    super();
+    if (!RESOLVER.isField(field)) {
+        throw new IllegalArgumentException(
+                "Invalid LIKE rule - " + field + " is not a supported field");
+    }
+
+    this.field = field;
+    this.pattern = pattern;
+  }
+
+    /**
+     * Create new instance from top two elements of stack.
+     * @param stack stack
+     * @return new instance
+     */
+  public static Rule getRule(final Stack stack) {
+      if (stack.size() < 2) {
+          throw new IllegalArgumentException(
+                  "Invalid LIKE rule - expected two parameters but received "
+                          + stack.size());
+      }
+
+      String p2 = stack.pop().toString();
+      String p1 = stack.pop().toString();
+      return getRule(p1, p2);
+  }
+
+    /**
+     * Create new instance.
+     * @param field field
+     * @param pattern pattern
+     * @return new instance
+     */
+  public static Rule getRule(final String field, final String pattern) {
+    try {
+        return new LikeRule(field, Pattern.compile(pattern, 
Pattern.CASE_INSENSITIVE));
+    } catch (PatternSyntaxException e) {
+        throw new IllegalArgumentException(
+                "Invalid LIKE rule - " + e.getMessage());
+    }
+  }
+
+    /** {@inheritDoc} */
+  public boolean evaluate(final LoggingEvent event, Map matches) {
+    //no need to figure out what part of the string matched, just set the 
entire string as a match
+    Object input = RESOLVER.getValue(field, event);
+    if((input != null) && (pattern != null)) {
+        if (matcher == null) {
+            matcher = pattern.matcher(input.toString());
+        } else {
+            matcher.reset(input.toString());
+        }
+        boolean result = matcher.matches();
+        if (result && matches != null) {
+            Set entries = (Set) matches.get(field.toUpperCase());
+            if (entries == null) {
+                entries = new HashSet();
+                matches.put(field.toUpperCase(), entries);
+            }
+            entries.add(input);
+        }
+        return result;
+    }
+    return false;
+  }
+
+  /**
+    * Deserialize the state of the object.
+    *
+    * @param in object input stream
+    *
+    * @throws IOException if IOException during deserialization
+    * @throws ClassNotFoundException if class not found.
+    */
+   private void readObject(final java.io.ObjectInputStream in)
+     throws IOException, ClassNotFoundException {
+         try {
+           field = (String) in.readObject();
+           String patternString = (String) in.readObject();
+           pattern = Pattern.compile(patternString, Pattern.CASE_INSENSITIVE);
+         } catch (PatternSyntaxException e) {
+             throw new IOException("Invalid LIKE rule - " + e.getMessage());
+         }
+   }
+
+   /**
+    * Serialize the state of the object.
+    *
+    * @param out object output stream
+    *
+    * @throws IOException if IOException during serialization
+    */
+   private void writeObject(final java.io.ObjectOutputStream out)
+     throws IOException {
+     out.writeObject(field);
+     out.writeObject(pattern.pattern());
+   }
+}
diff --git a/src/main/java/org/apache/log4j/rule/NotEqualsRule.java 
b/src/main/java/org/apache/log4j/rule/NotEqualsRule.java
new file mode 100644
index 0000000..aea2792
--- /dev/null
+++ b/src/main/java/org/apache/log4j/rule/NotEqualsRule.java
@@ -0,0 +1,121 @@
+/*
+ * 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.log4j.rule;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.LoggingEventFieldResolver;
+
+
+/**
+ * A Rule class implementing not equals against two strings.
+ *
+ * @author Scott Deboy ([email protected])
+ */
+public class NotEqualsRule extends AbstractRule {
+    /**
+     * Serialization ID.
+     */
+  static final long serialVersionUID = -1135478467213793211L;
+    /**
+     * Resolver.
+     */
+  private static final LoggingEventFieldResolver RESOLVER =
+    LoggingEventFieldResolver.getInstance();
+    /**
+     * Field.
+     */
+  private final String field;
+    /**
+     * Value.
+     */
+  private final String value;
+
+    /**
+     * Create new instance.
+     * @param field field
+     * @param value value
+     */
+  private NotEqualsRule(final String field, final String value) {
+    super();
+    if (!RESOLVER.isField(field)) {
+      throw new IllegalArgumentException(
+        "Invalid NOT EQUALS rule - " + field + " is not a supported field");
+    }
+
+    this.field = field;
+    this.value = value;
+  }
+
+    /**
+     * Get new instance.
+     * @param field field
+     * @param value value
+     * @return new instance.
+     */
+  public static Rule getRule(final String field, final String value) {
+    if (field.equalsIgnoreCase(LoggingEventFieldResolver.LEVEL_FIELD)) {
+        return NotLevelEqualsRule.getRule(value);
+    } else {
+        return new NotEqualsRule(field, value);
+    }
+  }
+
+    /**
+     * Get new instance from top two elements of stack.
+     * @param stack stack.
+     * @return new instance.
+     */
+  public static Rule getRule(final Stack stack) {
+    if (stack.size() < 2) {
+      throw new IllegalArgumentException(
+        "Invalid NOT EQUALS rule - expected two parameters but received "
+        + stack.size());
+    }
+
+    String p2 = stack.pop().toString();
+    String p1 = stack.pop().toString();
+
+    if (p1.equalsIgnoreCase(LoggingEventFieldResolver.LEVEL_FIELD)) {
+        return NotLevelEqualsRule.getRule(p2);
+    } else {
+        return new NotEqualsRule(p1, p2);
+    }
+  }
+
+    /** {@inheritDoc} */
+  public boolean evaluate(final LoggingEvent event, Map matches) {
+    Object p2 = RESOLVER.getValue(field, event);
+
+    boolean result = (p2 != null) && !(p2.toString().equals(value));
+    if (result && matches != null) {
+        //not equals - add the text that isn't equal (p2)
+        Set entries = (Set) matches.get(field.toUpperCase());
+        if (entries == null) {
+            entries = new HashSet();
+            matches.put(field.toUpperCase(), entries);
+        }
+        entries.add(value);
+    }
+    return result;
+  }
+}
diff --git a/src/main/java/org/apache/log4j/rule/NotLevelEqualsRule.java 
b/src/main/java/org/apache/log4j/rule/NotLevelEqualsRule.java
new file mode 100644
index 0000000..336b767
--- /dev/null
+++ b/src/main/java/org/apache/log4j/rule/NotLevelEqualsRule.java
@@ -0,0 +1,149 @@
+/*
+ * 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.log4j.rule;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.helpers.UtilLoggingLevel;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.LoggingEventFieldResolver;
+
+/**
+ * A Rule class implementing not equals against two levels.
+ *
+ * @author Scott Deboy ([email protected])
+ */
+public class NotLevelEqualsRule extends AbstractRule {
+    /**
+     * Serialization ID.
+     */
+    static final long serialVersionUID = -3638386582899583994L;
+
+    /**
+     * Level.
+     */
+    private transient Level level;
+
+    /**
+     * List of levels.
+     */
+    private static List levelList = new LinkedList();
+
+    static {
+        populateLevels();
+    }
+
+    /**
+     * Create new instance.
+     * @param level level.
+     */
+    private NotLevelEqualsRule(final Level level) {
+        super();
+        this.level = level;
+    }
+
+    /**
+     * Populate list of levels.
+     */
+    private static void populateLevels() {
+        levelList = new LinkedList();
+
+        levelList.add(Level.FATAL.toString());
+        levelList.add(Level.ERROR.toString());
+        levelList.add(Level.WARN.toString());
+        levelList.add(Level.INFO.toString());
+        levelList.add(Level.DEBUG.toString());
+               Level trace = Level.toLevel(5000, null);
+               if (trace != null) {
+                       levelList.add(trace.toString());
+           }
+    }
+
+    /**
+     * Create new rule.
+     * @param value name of level.
+     * @return instance of NotLevelEqualsRule.
+     */
+    public static Rule getRule(final String value) {
+        Level thisLevel;
+        if (levelList.contains(value.toUpperCase())) {
+            thisLevel = Level.toLevel(value.toUpperCase());
+          } else {
+            thisLevel = UtilLoggingLevel.toLevel(value.toUpperCase());
+        }
+
+        return new NotLevelEqualsRule(thisLevel);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean evaluate(final LoggingEvent event, Map matches) {
+        //both util.logging and log4j contain 'info' - use the int values 
instead of equality
+        //info level set to the same value for both levels
+        Level eventLevel = event.getLevel();
+        boolean result = level.toInt() != eventLevel.toInt();
+        if (result && matches != null) {
+            Set entries = (Set) 
matches.get(LoggingEventFieldResolver.LEVEL_FIELD);
+            if (entries == null) {
+                entries = new HashSet();
+                matches.put(LoggingEventFieldResolver.LEVEL_FIELD, entries);
+            }
+            entries.add(eventLevel);
+        }
+        return result;
+    }
+
+    /**
+     * Deserialize the state of the object.
+     *
+     * @param in object input stream.
+     *
+     * @throws IOException if error in reading stream for deserialization.
+     */
+    private void readObject(final java.io.ObjectInputStream in)
+            throws IOException {
+        populateLevels();
+        boolean isUtilLogging = in.readBoolean();
+        int levelInt = in.readInt();
+        if (isUtilLogging) {
+            level = UtilLoggingLevel.toLevel(levelInt);
+        } else {
+            level = Level.toLevel(levelInt);
+        }
+    }
+
+    /**
+     * Serialize the state of the object.
+     *
+     * @param out object output stream.
+     *
+     * @throws IOException if error in writing stream during serialization.
+     */
+    private void writeObject(final java.io.ObjectOutputStream out)
+            throws IOException {
+        out.writeBoolean(level instanceof UtilLoggingLevel);
+        out.writeInt(level.toInt());
+    }
+}
diff --git a/src/main/java/org/apache/log4j/rule/NotRule.java 
b/src/main/java/org/apache/log4j/rule/NotRule.java
new file mode 100644
index 0000000..5e264e3
--- /dev/null
+++ b/src/main/java/org/apache/log4j/rule/NotRule.java
@@ -0,0 +1,104 @@
+/*
+ * 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.log4j.rule;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+/**
+ * A Rule class implementing logical not.
+ *
+ * @author Scott Deboy ([email protected])
+ */
+public class NotRule extends AbstractRule {
+    /**
+     * Serialization ID.
+     */
+  static final long serialVersionUID = -6827159473117969306L;
+    /**
+     * Enclosed rule.
+     */
+  private final Rule rule;
+
+    /**
+     * Create new instance.
+     * @param rule enclosed rule.
+     */
+  private NotRule(final Rule rule) {
+    super();
+    this.rule = rule;
+  }
+
+    /**
+     * Create new instance.
+     * @param rule enclosed rule.
+     * @return new rule.
+     */
+  public static Rule getRule(final Rule rule) {
+      return new NotRule(rule);
+  }
+
+    /**
+     * Create new instance from top element of stack.
+     * @param stack stack
+     * @return new rule.
+     */
+  public static Rule getRule(final Stack stack) {
+      if (stack.size() < 1) {
+          throw new IllegalArgumentException(
+                  "Invalid NOT rule - expected one rule but received "
+                          + stack.size());
+      }
+      Object o1 = stack.pop();
+      if (o1 instanceof Rule) {
+        Rule p1 = (Rule) o1;
+        return new NotRule(p1);
+      }
+      throw new IllegalArgumentException(
+              "Invalid NOT rule: - expected rule but received " + o1);
+  }
+
+    /** {@inheritDoc} */
+  public boolean evaluate(final LoggingEvent event, Map matches) {
+    if (matches == null) {
+      return !(rule.evaluate(event, null));
+    }
+    Map tempMatches = new HashMap();
+    boolean result = !(rule.evaluate(event, tempMatches));
+    if (result) {
+        for (Iterator iter = 
tempMatches.entrySet().iterator();iter.hasNext();) {
+            Map.Entry entry = (Map.Entry)iter.next();
+            Object key = entry.getKey();
+            Set value = (Set)entry.getValue();
+            Set mainSet = (Set) matches.get(key);
+            if (mainSet == null) {
+                mainSet = new HashSet();
+                matches.put(key, mainSet);
+            }
+            mainSet.addAll(value);
+        }
+    }
+    return result;
+  }
+}
diff --git a/src/main/java/org/apache/log4j/rule/OrRule.java 
b/src/main/java/org/apache/log4j/rule/OrRule.java
new file mode 100644
index 0000000..ecfa46f
--- /dev/null
+++ b/src/main/java/org/apache/log4j/rule/OrRule.java
@@ -0,0 +1,127 @@
+/*
+ * 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.log4j.rule;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+/**
+ * A Rule class implementing logical or.
+ *
+ * @author Scott Deboy ([email protected])
+ */
+public class OrRule extends AbstractRule {
+    /**
+     * Serialization ID.
+     */
+  static final long serialVersionUID = 2088765995061413165L;
+    /**
+     * rule 1.
+     */
+  private final Rule rule1;
+    /**
+     * Rule 2.
+     */
+  private final Rule rule2;
+
+    /**
+     * Create new instance.
+     * @param firstParam first rule
+     * @param secondParam second rule
+     */
+  private OrRule(final Rule firstParam, final Rule secondParam) {
+    super();
+    this.rule1 = firstParam;
+    this.rule2 = secondParam;
+  }
+
+    /**
+     * Create new instance.
+     * @param firstParam first rule
+     * @param secondParam second rule
+     * @return new instance
+     */
+  public static Rule getRule(final Rule firstParam, final Rule secondParam) {
+      return new OrRule(firstParam, secondParam);
+  }
+
+    /**
+     * Create new instance from top two elements of stack.
+     * @param stack stack
+     * @return new instance
+     */
+  public static Rule getRule(final Stack stack) {
+      if (stack.size() < 2) {
+          throw new IllegalArgumentException(
+                  "Invalid OR rule - expected two rules but received "
+                          + stack.size());
+      }
+      Object o2 = stack.pop();
+      Object o1 = stack.pop();
+      if ((o2 instanceof Rule) && (o1 instanceof Rule)) {
+          Rule p2 = (Rule) o2;
+          Rule p1 = (Rule) o1;
+          return new OrRule(p1, p2);
+      }
+      throw new IllegalArgumentException("Invalid OR rule: " + o2 + "..." + 
o1);
+  }
+
+  /** {@inheritDoc} */
+  public boolean evaluate(final LoggingEvent event, Map matches) {
+      if (matches == null) {
+        return (rule1.evaluate(event, null) || rule2.evaluate(event, null));
+      }
+      Map tempMatches1 = new HashMap();
+      Map tempMatches2 = new HashMap();
+      //not short-circuiting because we want to build the matches list
+      boolean result1 = rule1.evaluate(event, tempMatches1);
+      boolean result2 = rule2.evaluate(event, tempMatches2);
+      boolean result = result1 || result2;
+      if (result) {
+          for (Iterator iter = 
tempMatches1.entrySet().iterator();iter.hasNext();) {
+              Map.Entry entry = (Map.Entry)iter.next();
+              Object key = entry.getKey();
+              Set value = (Set)entry.getValue();
+              Set mainSet = (Set) matches.get(key);
+              if (mainSet == null) {
+                  mainSet = new HashSet();
+                  matches.put(key, mainSet);
+              }
+              mainSet.addAll(value);
+          }
+          for (Iterator iter = 
tempMatches2.entrySet().iterator();iter.hasNext();) {
+              Map.Entry entry = (Map.Entry)iter.next();
+              Object key = entry.getKey();
+              Set value = (Set)entry.getValue();
+              Set mainSet = (Set) matches.get(key);
+              if (mainSet == null) {
+                  mainSet = new HashSet();
+                  matches.put(key, mainSet);
+              }
+              mainSet.addAll(value);
+          }
+      }
+      return result;
+  }
+}
diff --git a/src/main/java/org/apache/log4j/rule/PartialTextMatchRule.java 
b/src/main/java/org/apache/log4j/rule/PartialTextMatchRule.java
new file mode 100644
index 0000000..da56258
--- /dev/null
+++ b/src/main/java/org/apache/log4j/rule/PartialTextMatchRule.java
@@ -0,0 +1,112 @@
+/*
+ * 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.log4j.rule;
+
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.LoggingEventFieldResolver;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+
+/**
+ * A Rule class implementing case-insensitive
+ * partial-text matches against two strings.
+ *
+ * @author Scott Deboy ([email protected])
+ */
+public class PartialTextMatchRule extends AbstractRule {
+    /**
+     * Serialization ID.
+     */
+  static final long serialVersionUID = 6963284773637727558L;
+    /**
+     * Resolver.
+     */
+  private static final LoggingEventFieldResolver RESOLVER =
+    LoggingEventFieldResolver.getInstance();
+    /**
+     * Field.
+     */
+  private final String field;
+    /**
+     * Value.
+     */
+  private final String value;
+
+    /**
+     * Create new instance.
+     * @param field field
+     * @param value value
+     */
+  private PartialTextMatchRule(final String field, final String value) {
+    super();
+    if (!RESOLVER.isField(field)) {
+      throw new IllegalArgumentException(
+        "Invalid partial text rule - " + field + " is not a supported field");
+    }
+
+    this.field = field;
+    this.value = value;
+  }
+
+    /**
+     * Create new instance.
+     * @param field field
+     * @param value value
+     * @return new instance
+     */
+  public static Rule getRule(final String field, final String value) {
+    return new PartialTextMatchRule(field, value);
+  }
+
+    /**
+     * Create new instance from top two elements of stack.
+     * @param stack stack
+     * @return new instance
+     */
+  public static Rule getRule(final Stack stack) {
+    if (stack.size() < 2) {
+      throw new IllegalArgumentException(
+        "invalid partial text rule - expected two parameters but received "
+        + stack.size());
+    }
+
+    String p2 = stack.pop().toString();
+    String p1 = stack.pop().toString();
+
+    return new PartialTextMatchRule(p1, p2);
+  }
+
+    /** {@inheritDoc} */
+  public boolean evaluate(final LoggingEvent event, Map matches) {
+    Object p2 = RESOLVER.getValue(field, event);
+    boolean result = ((p2 != null) && (value != null) && 
(p2.toString().toLowerCase().indexOf(value.toLowerCase()) > -1));
+    if (result && matches != null) {
+        Set entries = (Set) matches.get(field.toUpperCase());
+        if (entries == null) {
+            entries = new HashSet();
+            matches.put(field.toUpperCase(), entries);
+        }
+        entries.add(value);
+    }
+    return result;
+  }
+}
diff --git a/src/main/java/org/apache/log4j/rule/Rule.java 
b/src/main/java/org/apache/log4j/rule/Rule.java
new file mode 100644
index 0000000..b7c3ee8
--- /dev/null
+++ b/src/main/java/org/apache/log4j/rule/Rule.java
@@ -0,0 +1,64 @@
+/*
+ * 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.log4j.rule;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.beans.PropertyChangeListener;
+import java.util.Map;
+
+
+/**
+ * A Rule evaluates to true of false given a LoggingEvent object, and can 
notify
+ * listeners when the underlying implementation of this Rule has it's
+ * criteria changed by using the standard PropertyChangeListener 
infrastructure.
+ *
+ * @author Paul Smith ([email protected])
+ * @author Scott Deboy ([email protected])
+ */
+public interface Rule {
+  /**
+   * Returns true if this implementation of the rule accepts the LoggingEvent,
+   * or false if not.
+   *
+   * <p>What True/False means can be client-specific.
+   *
+   * @param e LoggingEvent this instance will evaluate
+   * @param matches a Map of event field keys to Sets of matching strings (may 
be null) which will be
+   * updated during execution of this method to include field and string 
matches based on the rule
+   * evaluation results 
+   * @return true if this Rule instance accepts the event, otherwise false.
+   */
+  boolean evaluate(LoggingEvent e, Map matches);
+
+  /**
+   * Adds a PropertyChangeListener to this instance, which is notified when
+   * underlying Rule information has changed.
+   * (there are no specific property name events).
+   * @param listener listener
+   */
+  void addPropertyChangeListener(PropertyChangeListener listener);
+
+  /**
+   * Removes a known PropertyChangeListener from this Rule.
+   * @param listener listener
+   */
+  void removePropertyChangeListener(PropertyChangeListener listener);
+}
diff --git a/src/main/java/org/apache/log4j/rule/RuleFactory.java 
b/src/main/java/org/apache/log4j/rule/RuleFactory.java
new file mode 100644
index 0000000..afc6668
--- /dev/null
+++ b/src/main/java/org/apache/log4j/rule/RuleFactory.java
@@ -0,0 +1,187 @@
+/*
+ * 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.log4j.rule;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.Locale;
+import java.util.Stack;
+
+/**
+ * A Factory class which, given a string representation of the rule,
+ * and a context stack, will
+ * return a Rule ready for evaluation against events.
+ * If an operator is requested that isn't supported, 
+ * an IllegalArgumentException is thrown.
+ *
+ * @author Scott Deboy ([email protected])
+ */
+public final class RuleFactory {
+    /**
+     * Singleton instance.
+     */
+  private static final RuleFactory FACTORY = new RuleFactory();
+    /**
+     * Rules.
+     */
+  private static final Collection RULES = new LinkedList();
+    /**
+     * AND operator literal.
+     */
+  private static final String AND_RULE = "&&";
+    /**
+     * OR operator literal.
+     */
+  private static final String OR_RULE = "||";
+    /**
+     * NOT operator literal.
+     */
+  private static final String NOT_RULE = "!";
+    /**
+     * Inequality operator literal.
+     */
+  private static final String NOT_EQUALS_RULE = "!=";
+    /**
+     * Equality operator literal.
+     */
+  private static final String EQUALS_RULE = "==";
+    /**
+     * Partial match operator literal.
+     */
+  private static final String PARTIAL_TEXT_MATCH_RULE = "~=";
+    /**
+     * Like operator literal.
+     */
+  private static final String LIKE_RULE = "like";
+    /**
+     * Exists operator literal.
+     */
+  private static final String EXISTS_RULE = "exists";
+    /**
+     * Less than operator literal.
+     */
+  private static final String LESS_THAN_RULE = "<";
+    /**
+     * Greater than operator literal.
+     */
+  private static final String GREATER_THAN_RULE = ">";
+    /**
+     * Less than or equal operator literal.
+     */
+  private static final String LESS_THAN_EQUALS_RULE = "<=";
+    /**
+     * Greater than or equal operator literal.
+     */
+  private static final String GREATER_THAN_EQUALS_RULE = ">=";
+
+  static {
+    RULES.add(AND_RULE);
+    RULES.add(OR_RULE);
+    RULES.add(NOT_RULE);
+    RULES.add(NOT_EQUALS_RULE);
+    RULES.add(EQUALS_RULE);
+    RULES.add(PARTIAL_TEXT_MATCH_RULE);
+    RULES.add(LIKE_RULE);
+    RULES.add(EXISTS_RULE);
+    RULES.add(LESS_THAN_RULE);
+    RULES.add(GREATER_THAN_RULE);
+    RULES.add(LESS_THAN_EQUALS_RULE);
+    RULES.add(GREATER_THAN_EQUALS_RULE);
+  }
+
+    /**
+     * Create instance.
+     */
+  private RuleFactory() {
+        super();
+    }
+
+    /**
+     * Get instance.
+     * @return rule factory instance.
+     */
+  public static RuleFactory getInstance() {
+      return FACTORY;
+  }
+
+    /**
+     * Determine if specified string is a known operator.
+     * @param symbol string
+     * @return true if string is a known operator
+     */
+  public boolean isRule(final String symbol) {
+    return ((symbol != null) && 
(RULES.contains(symbol.toLowerCase(Locale.ENGLISH))));
+  }
+
+    /**
+     * Create rule from applying operator to stack.
+     * @param symbol symbol
+     * @param stack stack
+     * @return new instance
+     */
+  public Rule getRule(final String symbol, final Stack stack) {
+    if (AND_RULE.equals(symbol)) {
+      return AndRule.getRule(stack);
+    }
+
+    if (OR_RULE.equals(symbol)) {
+      return OrRule.getRule(stack);
+    }
+
+    if (NOT_RULE.equals(symbol)) {
+      return NotRule.getRule(stack);
+    }
+
+    if (NOT_EQUALS_RULE.equals(symbol)) {
+      return NotEqualsRule.getRule(stack);
+    }
+
+    if (EQUALS_RULE.equals(symbol)) {
+      return EqualsRule.getRule(stack);
+    }
+
+    if (PARTIAL_TEXT_MATCH_RULE.equals(symbol)) {
+      return PartialTextMatchRule.getRule(stack);
+    }
+
+    if (RULES.contains(LIKE_RULE) && LIKE_RULE.equalsIgnoreCase(symbol)) {
+      return LikeRule.getRule(stack);
+    }
+
+    if (EXISTS_RULE.equalsIgnoreCase(symbol)) {
+      return ExistsRule.getRule(stack);
+    }
+
+    if (LESS_THAN_RULE.equals(symbol)) {
+      return InequalityRule.getRule(LESS_THAN_RULE, stack);
+    }
+
+    if (GREATER_THAN_RULE.equals(symbol)) {
+      return InequalityRule.getRule(GREATER_THAN_RULE, stack);
+    }
+
+    if (LESS_THAN_EQUALS_RULE.equals(symbol)) {
+      return InequalityRule.getRule(LESS_THAN_EQUALS_RULE, stack);
+    }
+
+    if (GREATER_THAN_EQUALS_RULE.equals(symbol)) {
+      return InequalityRule.getRule(GREATER_THAN_EQUALS_RULE, stack);
+    }
+    throw new IllegalArgumentException("Invalid rule: " + symbol);
+  }
+}
diff --git a/src/main/java/org/apache/log4j/rule/TimestampEqualsRule.java 
b/src/main/java/org/apache/log4j/rule/TimestampEqualsRule.java
new file mode 100644
index 0000000..f43f36f
--- /dev/null
+++ b/src/main/java/org/apache/log4j/rule/TimestampEqualsRule.java
@@ -0,0 +1,121 @@
+/*
+ * 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.log4j.rule;
+
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.LoggingEventFieldResolver;
+
+/**
+ * A Rule class implementing equality evaluation for timestamps.
+ *
+ * @author Scott Deboy ([email protected])
+ */
+public class TimestampEqualsRule extends AbstractRule {
+    /**
+     * Serialization ID.
+     */
+  static final long serialVersionUID = 1639079557187790321L;
+    /**
+     * Resolver.
+     */
+  private static final LoggingEventFieldResolver RESOLVER =
+          LoggingEventFieldResolver.getInstance();
+    /**
+     * Date format.
+     */
+  private static final DateFormat DATE_FORMAT =
+          new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
+
+    /**
+     * time stamp.
+     */
+  private long timeStamp;
+
+    /**
+     * Create new instance.
+     * @param value string representation of date.
+     */
+  private TimestampEqualsRule(final String value) {
+    super();
+    //expects value to be a timestamp value represented as a long
+    try {
+        timeStamp = DATE_FORMAT.parse(value).getTime();
+    } catch (ParseException pe) {
+        throw new IllegalArgumentException("Could not parse date: " + value);
+    }
+  }
+
+    /**
+     * Create new instance.
+     * @param value string representation of date.
+     * @return new instance
+     */
+  public static Rule getRule(final String value) {
+      return new TimestampEqualsRule(value);
+  }
+
+    /** {@inheritDoc} */
+  public boolean evaluate(final LoggingEvent event, Map matches) {
+    String eventTimeStampString = 
RESOLVER.getValue(LoggingEventFieldResolver.TIMESTAMP_FIELD, event).toString();
+    long eventTimeStamp = Long.parseLong(eventTimeStampString) / 1000 * 1000;
+    boolean result = (eventTimeStamp == timeStamp);
+    if (result && matches != null) {
+        Set entries = (Set) 
matches.get(LoggingEventFieldResolver.TIMESTAMP_FIELD);
+        if (entries == null) {
+            entries = new HashSet();
+            matches.put(LoggingEventFieldResolver.TIMESTAMP_FIELD, entries);
+        }
+        entries.add(eventTimeStampString);
+    }
+    return result;
+  }
+
+  /**
+    * Deserialize the state of the object.
+    *
+    * @param in object input stream
+    *
+    * @throws IOException if IO error during deserialization
+    * @throws ClassNotFoundException if class not found during
+   *   deserialization
+    */
+   private void readObject(final java.io.ObjectInputStream in)
+     throws IOException, ClassNotFoundException {
+     timeStamp = in.readLong();
+   }
+
+   /**
+    * Serialize the state of the object.
+    *
+    * @param out object output stream
+    *
+    * @throws IOException if IO error during serialization
+    */
+   private void writeObject(final java.io.ObjectOutputStream out)
+     throws IOException {
+     out.writeLong(timeStamp);
+   }
+}
diff --git a/src/main/java/org/apache/log4j/rule/TimestampInequalityRule.java 
b/src/main/java/org/apache/log4j/rule/TimestampInequalityRule.java
new file mode 100644
index 0000000..44c5776
--- /dev/null
+++ b/src/main/java/org/apache/log4j/rule/TimestampInequalityRule.java
@@ -0,0 +1,142 @@
+/*
+ * 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.log4j.rule;
+
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.LoggingEventFieldResolver;
+
+/**
+ * A Rule class implementing inequality evaluation for timestamps.
+ *
+ * @author Scott Deboy ([email protected])
+ */
+public class TimestampInequalityRule extends AbstractRule {
+    /**
+     * Serialization ID.
+     */
+  static final long serialVersionUID = -4642641663914789241L;
+    /**
+     * Resolver.
+     */
+  private static final LoggingEventFieldResolver RESOLVER =
+            LoggingEventFieldResolver.getInstance();
+    /**
+     * Date format.
+     */
+  private static final DateFormat DATE_FORMAT =
+          new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
+    /**
+     * Inequality symbol.
+     */
+  private transient String inequalitySymbol;
+    /**
+     * Timestamp.
+     */
+  private long timeStamp;
+
+    /**
+     * Create new instance.
+     * @param inequalitySymbol inequality symbol.
+     * @param value string representation of date.
+     */
+  private TimestampInequalityRule(
+    final String inequalitySymbol, final String value) {
+    super();
+    this.inequalitySymbol = inequalitySymbol;
+    try {
+        timeStamp = DATE_FORMAT.parse(value).getTime();
+    } catch (ParseException pe) {
+        throw new IllegalArgumentException("Could not parse date: " + value);
+    }
+  }
+
+    /**
+     * Create new instance.
+     * @param inequalitySymbol inequality symbol
+     * @param value string representation of date
+     * @return new instance
+     */
+  public static Rule getRule(final String inequalitySymbol,
+                             final String value) {
+      return new TimestampInequalityRule(inequalitySymbol, value);
+  }
+
+    /** {@inheritDoc} */
+  public boolean evaluate(final LoggingEvent event, Map matches) {
+    String eventTimeStampString = 
RESOLVER.getValue(LoggingEventFieldResolver.TIMESTAMP_FIELD, event).toString();
+    long eventTimeStamp = Long.parseLong(
+                eventTimeStampString) / 1000 * 1000;
+    boolean result = false;
+    long first = eventTimeStamp;
+    long second = timeStamp;
+
+    if ("<".equals(inequalitySymbol)) {
+      result = first < second;
+    } else if (">".equals(inequalitySymbol)) {
+      result = first > second;
+    } else if ("<=".equals(inequalitySymbol)) {
+      result = first <= second;
+    } else if (">=".equals(inequalitySymbol)) {
+      result = first >= second;
+    }
+    if (result && matches != null) {
+        Set entries = (Set) 
matches.get(LoggingEventFieldResolver.TIMESTAMP_FIELD);
+        if (entries == null) {
+            entries = new HashSet();
+            matches.put(LoggingEventFieldResolver.TIMESTAMP_FIELD, entries);
+        }
+        entries.add(eventTimeStampString);
+    }
+    return result;
+  }
+
+  /**
+    * Deserialize the state of the object.
+    *
+    * @param in object input stream
+    *
+    * @throws IOException if IO error during deserialization
+    * @throws ClassNotFoundException if class not found
+    */
+   private void readObject(final java.io.ObjectInputStream in)
+     throws IOException, ClassNotFoundException {
+     inequalitySymbol = (String) in.readObject();
+     timeStamp = in.readLong();
+   }
+
+   /**
+    * Serialize the state of the object.
+    *
+    * @param out object output stream
+    *
+    * @throws IOException if IO error during serialization
+    */
+   private void writeObject(final java.io.ObjectOutputStream out)
+     throws IOException {
+     out.writeObject(inequalitySymbol);
+     out.writeLong(timeStamp);
+   }
+}
diff --git a/src/main/java/org/apache/log4j/spi/LoggingEventFieldResolver.java 
b/src/main/java/org/apache/log4j/spi/LoggingEventFieldResolver.java
new file mode 100644
index 0000000..f1f4ab0
--- /dev/null
+++ b/src/main/java/org/apache/log4j/spi/LoggingEventFieldResolver.java
@@ -0,0 +1,275 @@
+/*
+ * 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.log4j.spi;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Locale;
+
+import org.apache.log4j.rule.InFixToPostFix;
+
+
+/**
+ * A singleton helper utility which accepts a field name
+ * and a LoggingEvent and returns the value of that field.
+ *
+ * This class defines a grammar used in creation of an expression-based Rule.
+ *
+ * The only available method is
+ * Object getField(String fieldName, LoggingEvent event).
+ *
+ * Here is a description of the mapping of field names in the grammar
+ * to fields on the logging event.  While the getField method returns an 
Object,
+ * the individual types returned per field are described here:
+ *
+ * Field Name    Field value (String representation        Return type
+ * LOGGER        category name (logger)                    String
+ * LEVEL         level                                     Level
+ * CLASS         locationInformation's class name          String
+ * FILE          locationInformation's file name           String
+ * LINE          locationInformation's line number         String
+ * METHOD        locationInformation's method name         String
+ * MSG           message                                   Object
+ * NDC           NDC                                       String
+ * EXCEPTION     throwable string representation           ThrowableInformation
+ * TIMESTAMP     timestamp                                 Long
+ * THREAD        thread                                    String
+ * PROP.keyName  entry in the Property hashtable           String
+ *               mapped to the key [keyName]
+
+ * NOTE:  the values for the 'keyName' portion of the MDC and PROP mappings 
must
+ * be an exact match to the key in the hashTable (case sensitive).
+ *
+ * If the passed-in field is null or doesn't match an entry
+ * in the above-described mapping, an exception is thrown.
+ *
+ * @author Scott Deboy ([email protected])
+ * @author Paul Smith ([email protected])
+ *
+ */
+public final class LoggingEventFieldResolver {
+    /**
+     * Keyword list.
+     */
+  public static final List KEYWORD_LIST = new ArrayList();
+    /**
+     * LOGGER string literal.
+     */
+  public static final String LOGGER_FIELD = "LOGGER";
+    /**
+     * LEVEL string literal.
+     */
+  public static final String LEVEL_FIELD = "LEVEL";
+    /**
+     * CLASS string literal.
+     */
+  public static final String CLASS_FIELD = "CLASS";
+    /**
+     * FILE string literal.
+     */
+  public static final String FILE_FIELD = "FILE";
+    /**
+     * LINE string literal.
+     */
+  public static final String LINE_FIELD = "LINE";
+    /**
+     * METHOD string literal.
+     */
+  public static final String METHOD_FIELD = "METHOD";
+    /**
+     * MSG string literal.
+     */
+  public static final String MSG_FIELD = "MSG";
+    /**
+     * NDC string literal.
+     */
+  public static final String NDC_FIELD = "NDC";
+    /**
+     * EXCEPTION string literal.
+     */
+  public static final String EXCEPTION_FIELD = "EXCEPTION";
+    /**
+     * TIMESTAMP string literal.
+     */
+  public static final String TIMESTAMP_FIELD = "TIMESTAMP";
+    /**
+     * THREAD string literal.
+     */
+  public static final String THREAD_FIELD = "THREAD";
+    /**
+     * PROP. string literal.
+     */
+  public static final String PROP_FIELD = "PROP.";
+    /**
+     * empty string literal.
+     */
+  public static final String EMPTY_STRING = "";
+    /**
+     * LOGGER string literal.
+     */
+  private static final LoggingEventFieldResolver RESOLVER =
+    new LoggingEventFieldResolver();
+
+    /**
+     * Create new instance.
+     */
+  private LoggingEventFieldResolver() {
+    super();
+    KEYWORD_LIST.add(LOGGER_FIELD);
+    KEYWORD_LIST.add(LEVEL_FIELD);
+    KEYWORD_LIST.add(CLASS_FIELD);
+    KEYWORD_LIST.add(FILE_FIELD);
+    KEYWORD_LIST.add(LINE_FIELD);
+    KEYWORD_LIST.add(METHOD_FIELD);
+    KEYWORD_LIST.add(MSG_FIELD);
+    KEYWORD_LIST.add(NDC_FIELD);
+    KEYWORD_LIST.add(EXCEPTION_FIELD);
+    KEYWORD_LIST.add(TIMESTAMP_FIELD);
+    KEYWORD_LIST.add(THREAD_FIELD);
+    KEYWORD_LIST.add(PROP_FIELD);
+  }
+
+    /**
+     * Apply fields.
+     * @param replaceText replacement text.
+     * @param event logging event.
+     * @return evaluted expression
+     */
+  public String applyFields(final String replaceText,
+                            final LoggingEvent event) {
+      if (replaceText == null) {
+        return null;
+      }
+      InFixToPostFix.CustomTokenizer tokenizer = new 
InFixToPostFix.CustomTokenizer(replaceText);
+      StringBuffer result = new StringBuffer();
+      boolean found = false;
+
+      while (tokenizer.hasMoreTokens()) {
+          String token = tokenizer.nextToken();
+          if (isField(token) || 
token.toUpperCase(Locale.US).startsWith(PROP_FIELD)) {
+              result.append(getValue(token, event).toString());
+              found = true;
+          } else {
+              result.append(token);
+          }
+      }
+      if (found) {
+        return result.toString();
+      }
+      return null;
+  }
+
+    /**
+     * Get singleton instance.
+     * @return singleton instance
+     */
+  public static LoggingEventFieldResolver getInstance() {
+    return RESOLVER;
+  }
+
+    /**
+     * Determines if specified string is a recognized field.
+     * @param fieldName field name
+     * @return true if recognized field.
+     */
+  public boolean isField(final String fieldName) {
+    if (fieldName != null) {
+        return (KEYWORD_LIST.contains(
+                fieldName.toUpperCase(Locale.US))
+                || fieldName.toUpperCase().startsWith(PROP_FIELD));
+    }
+    return false;
+  }
+
+    /**
+     * Get value of field.
+     * @param fieldName field
+     * @param event event
+     * @return value of field
+     */
+  public Object getValue(final String fieldName,
+                         final LoggingEvent event) {
+    String upperField = fieldName.toUpperCase(Locale.US);
+    if (LOGGER_FIELD.equals(upperField)) {
+      return event.getLoggerName();
+    } else if (LEVEL_FIELD.equals(upperField)) {
+      return event.getLevel();
+    } else if (MSG_FIELD.equals(upperField)) {
+      return event.getMessage();
+    } else if (NDC_FIELD.equals(upperField)) {
+      String ndcValue = event.getNDC();
+      return ((ndcValue == null) ? EMPTY_STRING : ndcValue);
+    } else if (EXCEPTION_FIELD.equals(upperField)) {
+        String[] throwableRep = event.getThrowableStrRep();
+        if (throwableRep == null) {
+            return EMPTY_STRING;
+        } else {
+            return getExceptionMessage(throwableRep);
+        }
+    } else if (TIMESTAMP_FIELD.equals(upperField)) {
+      return new Long(event.timeStamp);
+    } else if (THREAD_FIELD.equals(upperField)) {
+      return event.getThreadName();
+    } else if (upperField.startsWith(PROP_FIELD)) {
+      //note: need to use actual fieldname since case matters
+      Object propValue = event.getMDC(fieldName.substring(5));
+      if (propValue == null) {
+          //case-specific match didn't work, try case insensitive match
+          String lowerPropKey = fieldName.substring(5).toLowerCase();
+          Set entrySet = event.getProperties().entrySet();
+          for (Iterator iter = entrySet.iterator();iter.hasNext();) {
+              Map.Entry thisEntry = (Map.Entry) iter.next();
+              if 
(thisEntry.getKey().toString().equalsIgnoreCase(lowerPropKey)) {
+                  propValue = thisEntry.getValue();
+              }
+          }
+      }
+      return ((propValue == null) ? EMPTY_STRING : propValue.toString());
+    } else {
+        LocationInfo info = event.getLocationInformation();
+        if (CLASS_FIELD.equals(upperField)) {
+            return ((info == null) ? EMPTY_STRING : info.getClassName());
+        } else if (FILE_FIELD.equals(upperField)) {
+            return ((info == null) ? EMPTY_STRING : info.getFileName());
+        } else if (LINE_FIELD.equals(upperField)) {
+            return ((info == null) ? EMPTY_STRING : info.getLineNumber());
+        } else if (METHOD_FIELD.equals(upperField)) {
+            return ((info == null) ? EMPTY_STRING : info.getMethodName());
+        }
+    }
+
+    //there wasn't a match, so throw a runtime exception
+    throw new IllegalArgumentException("Unsupported field name: " + fieldName);
+  }
+
+    /**
+     * Get message from throwable representation.
+     * @param exception exception
+     * @return message
+     */
+    private static String getExceptionMessage(final String[] exception) {
+        StringBuffer buff = new StringBuffer();
+        for (int i = 0; i < exception.length; i++) {
+            buff.append(exception[i]);
+        }
+        return buff.toString();
+    }
+}

Reply via email to