/** * Helios, OpenSource Monitoring * Brought to you by the Helios Development Group * * Copyright 2007, Helios Development Group and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. * */ package org.helios.apmrouter.router; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; import java.util.regex.Pattern; import org.helios.apmrouter.collections.ConcurrentLongSortedSet; /** * <p>Title: PatternMatch</p> * <p>Description: A regex pattern matcher that caches hit and miss patterns for improved performance</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.router.PatternMatch</code></p> */ public class PatternMatch { /** The long hashcodes of routing keys that are known to match this PatternMatch */ protected final ConcurrentLongSortedSet hits = new ConcurrentLongSortedSet(); /** The long hashcodes of routing keys that are known to NOT match this PatternMatch */ protected final ConcurrentLongSortedSet misses = new ConcurrentLongSortedSet(); /** The pattern string for this pattern match */ protected final String patternValue; /** The regex pattern for this pattern match */ protected final Pattern pattern; /** A cache of created patterns */ private static final Map<String, PatternMatch> PATTERNS = new ConcurrentHashMap<String, PatternMatch>(); /** * Returns the PatternMatch for the passed pattern * @param patternValue The pattern for the PattrnMatch * @return the PatternMatch for the passed pattern */ public static PatternMatch getInstance(String patternValue) { if(patternValue==null) throw new IllegalArgumentException("The passed pattern was null", new Throwable()); PatternMatch pm = PATTERNS.get(patternValue); if(pm==null) { synchronized(PATTERNS) { pm = PATTERNS.get(patternValue); if(pm==null) { pm = new PatternMatch(patternValue); PATTERNS.put(patternValue, pm); } } } return pm; } /** * Creates a new PatternMatch * @param patternValue The pattern string for this pattern match */ private PatternMatch(String patternValue) { this.patternValue = patternValue; pattern = Pattern.compile(patternValue); } /** * Indicates if the passed string matches this PatternMatch * @param toMatch The value to match * @return true if there is a match, false if there is no match or if the passed value is null */ public boolean matches(CharSequence toMatch) { if(toMatch==null) return false; long key = longHashCode(toMatch.toString()); if(hits.contains(key)) return true; if(misses.contains(key)) return false; if(pattern.matcher(toMatch).matches()) { hits.add(key); return true; } misses.add(key); return false; } /** * Returns the number of cached hits * @return the number of cached hits */ public int getHitCount() { return hits.size(); } /** * Returns the number of cached misses * @return the number of cached misses */ public int getMissCount() { return misses.size(); } public static final PatternMatchGroup newPatternMatchGroup(CharSequence...patternValues) { PatternMatchGroup pmg = new PatternMatchGroup(); for(CharSequence cs: patternValues) { if(cs==null) continue; pmg.add(PatternMatch.getInstance(cs.toString())); } return pmg; } public static class PatternMatchGroup { private final Set<PatternMatch> patternMatches = new CopyOnWriteArraySet<PatternMatch>(); public boolean add(PatternMatch pm) { return patternMatches.add(pm); } public boolean add(CharSequence cs) { if(cs!=null) { return add(PatternMatch.getInstance(cs.toString())); } return false; } public String[] getPatterns() { Set<String> set = new HashSet<String>(); for(Iterator<PatternMatch> iter = patternMatches.iterator(); iter.hasNext();) { set.add(iter.next().patternValue); } return set.toArray(new String[set.size()]); } public boolean remove(CharSequence cs) { if(cs==null) return false; String s = cs.toString(); for(Iterator<PatternMatch> iter = patternMatches.iterator(); iter.hasNext();) { if(iter.next().patternValue.equals(s)) { iter.remove(); return true; } } return false; } public int size() { return patternMatches.size(); } public boolean matches(CharSequence cs) { if(cs==null) return false; for(PatternMatch pm: patternMatches) { if(pm.matches(cs)) return true; } return false; } } /** * Calculates a low collision hash code for the passed string * @param s The string to calculate the hash code for * @return the long hashcode */ public static long longHashCode(String s) { long h = 0; int len = s.length(); int off = 0; int hashPrime = s.hashCode(); char val[] = s.toCharArray(); for (int i = 0; i < len; i++) { h = (31*h + val[off++] + (hashPrime*h)); } return h; } // public static void main(String[] args) { // log("PatternMatch Test"); // PatternMatchGroup pmg = newPatternMatchGroup(".*foo", ".*bar", ".*snafu", ".*haha"); // log("PM Size:" + pmg.size()); // log("Match for HeeHeefoo:" + pmg.matches("HeeHeefoo")); // log("Match for HeeHee:" + pmg.matches("HeeHee")); // } // // public static void log(Object msg) { // System.out.println(msg); // } /** * {@inheritDoc} * @see java.lang.Object#hashCode() */ @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((patternValue == null) ? 0 : patternValue.hashCode()); return result; } /** * {@inheritDoc} * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } PatternMatch other = (PatternMatch) obj; if (patternValue == null) { if (other.patternValue != null) { return false; } } else if (!patternValue.equals(other.patternValue)) { return false; } return true; } /** * Returns the pattern string for this pattern match * @return the patternValue */ public String getPatternValue() { return patternValue; } }