472781 - GzipHandler isMimeTypeGzipable() bad logic

Added utility classes:

  IncludeExclude  - handles standard include exclude set
  RegexSet        - A set of regular expressions that have a combined compiled pattern
  PathMap.PathSet - A set of standard path mappings
This commit is contained in:
Greg Wilkins 2015-07-17 16:28:48 +10:00
parent 9ee4b64e89
commit edc8bae556
7 changed files with 509 additions and 120 deletions

View File

@ -26,8 +26,10 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.function.BiFunction;
import org.eclipse.jetty.util.ArrayTernaryTrie;
import org.eclipse.jetty.util.RegexSet;
import org.eclipse.jetty.util.Trie;
import org.eclipse.jetty.util.URIUtil;
@ -592,6 +594,7 @@ public class PathMap<O> extends HashMap<String,O>
public static class PathSet extends AbstractSet<String>
{
public static final BiFunction<PathSet,String,Boolean> MATCHER=(s,e)->{return s.containsMatch(e);};
private final PathMap<Boolean> _map = new PathMap<>();
@Override
@ -623,5 +626,13 @@ public class PathMap<O> extends HashMap<String,O>
{
return _map.containsKey(o);
}
public boolean containsMatch(String s)
{
return _map.containsMatch(s);
}
}
}

View File

@ -20,9 +20,7 @@ package org.eclipse.jetty.server.handler.gzip;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.zip.Deflater;
import javax.servlet.ServletContext;
@ -39,8 +37,8 @@ import org.eclipse.jetty.http.PathMap;
import org.eclipse.jetty.server.HttpOutput;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.util.ConcurrentHashSet;
import org.eclipse.jetty.util.IncludeExclude;
import org.eclipse.jetty.util.RegexSet;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -73,16 +71,13 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
// non-static, as other GzipHandler instances may have different configurations
private final ThreadLocal<Deflater> _deflater = new ThreadLocal<Deflater>();
private final Set<Pattern> _excludedAgentPatterns=new HashSet<>();
private final IncludeExclude<String> _agentPatterns=new IncludeExclude<>(RegexSet.class,RegexSet.MATCHER);
private final IncludeExclude<String> _methods = new IncludeExclude<>();
private final IncludeExclude<String> _paths = new IncludeExclude<>(PathMap.PathSet.class);
private final IncludeExclude<String> _paths = new IncludeExclude<>(PathMap.PathSet.class,PathMap.PathSet.MATCHER);
private final IncludeExclude<String> _mimeTypes = new IncludeExclude<>();
private HttpField _vary;
private final Set<String> _uaCache = new ConcurrentHashSet<>();
private int _uaCacheSize = 1024;
/* ------------------------------------------------------------ */
/**
* Instantiates a new gzip handler.
@ -110,7 +105,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
_mimeTypes.exclude("application/x-rar-compressed");
LOG.debug("{} mime types {}",this,_mimeTypes);
_excludedAgentPatterns.add(Pattern.compile(".*MSIE 6.0.*"));
_agentPatterns.exclude(".*MSIE 6.0.*");
}
/* ------------------------------------------------------------ */
@ -119,8 +114,17 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
*/
public void addExcludedAgentPatterns(String... patterns)
{
for (String s : patterns)
_excludedAgentPatterns.add(Pattern.compile(s));
_agentPatterns.exclude(patterns);
}
/* ------------------------------------------------------------ */
/**
* @param methods The methods to exclude in compression
*/
public void addExcludedMethods(String... methods)
{
for (String m : methods)
_methods.exclude(m);
}
/* ------------------------------------------------------------ */
@ -144,6 +148,15 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
_paths.exclude(pathspecs);
}
/* ------------------------------------------------------------ */
/**
* @param patterns Regular expressions matching user agents to exclude
*/
public void addIncludedAgentPatterns(String... patterns)
{
_agentPatterns.include(patterns);
}
/* ------------------------------------------------------------ */
/**
* @param methods The methods to include in compression
@ -154,16 +167,6 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
_methods.include(m);
}
/* ------------------------------------------------------------ */
/**
* @param methods The methods to exclude in compression
*/
public void addExcludedMethods(String... methods)
{
for (String m : methods)
_methods.exclude(m);
}
/* ------------------------------------------------------------ */
/**
* Add included mime types. Inclusion takes precedence over
@ -187,6 +190,14 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
_paths.include(pathspecs);
}
/* ------------------------------------------------------------ */
@Override
protected void doStart() throws Exception
{
_vary=(_agentPatterns.size()>0)?GzipHttpOutputInterceptor.VARY_ACCEPT_ENCODING_USER_AGENT:GzipHttpOutputInterceptor.VARY_ACCEPT_ENCODING;
super.doStart();
}
/* ------------------------------------------------------------ */
public boolean getCheckGzExists()
{
@ -204,7 +215,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
public Deflater getDeflater(Request request, long content_length)
{
String ua = request.getHttpFields().get(HttpHeader.USER_AGENT);
if (ua!=null && isExcludedAgent(ua))
if (ua!=null && !isAgentGzipable(ua))
{
LOG.debug("{} excluded user agent {}",this,request);
return null;
@ -247,43 +258,10 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
/* ------------------------------------------------------------ */
public String[] getExcludedAgentPatterns()
{
Pattern[] ps = _excludedAgentPatterns.toArray(new Pattern[_excludedAgentPatterns.size()]);
String[] s = new String[ps.length];
int i=0;
for (Pattern p: ps)
s[i++]=p.toString();
return s;
}
/* ------------------------------------------------------------ */
public String[] getExcludedMimeTypes()
{
Set<String> excluded=_mimeTypes.getExcluded();
Set<String> excluded=_agentPatterns.getExcluded();
return excluded.toArray(new String[excluded.size()]);
}
/* ------------------------------------------------------------ */
public String[] getIncludedMimeTypes()
{
Set<String> includes=_mimeTypes.getIncluded();
return includes.toArray(new String[includes.size()]);
}
/* ------------------------------------------------------------ */
public String[] getExcludedPaths()
{
Set<String> excluded=_paths.getExcluded();
return excluded.toArray(new String[excluded.size()]);
}
/* ------------------------------------------------------------ */
public String[] getIncludedPaths()
{
Set<String> includes=_paths.getIncluded();
return includes.toArray(new String[includes.size()]);
}
/* ------------------------------------------------------------ */
public String[] getExcludedMethods()
{
@ -291,6 +269,28 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
return excluded.toArray(new String[excluded.size()]);
}
/* ------------------------------------------------------------ */
public String[] getExcludedMimeTypes()
{
Set<String> excluded=_mimeTypes.getExcluded();
return excluded.toArray(new String[excluded.size()]);
}
/* ------------------------------------------------------------ */
public String[] getExcludedPaths()
{
Set<String> excluded=_paths.getExcluded();
return excluded.toArray(new String[excluded.size()]);
}
/* ------------------------------------------------------------ */
public String[] getIncludedAgentPatterns()
{
Set<String> includes=_agentPatterns.getIncluded();
return includes.toArray(new String[includes.size()]);
}
/* ------------------------------------------------------------ */
public String[] getIncludedMethods()
{
@ -298,6 +298,20 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
return includes.toArray(new String[includes.size()]);
}
/* ------------------------------------------------------------ */
public String[] getIncludedMimeTypes()
{
Set<String> includes=_mimeTypes.getIncluded();
return includes.toArray(new String[includes.size()]);
}
/* ------------------------------------------------------------ */
public String[] getIncludedPaths()
{
Set<String> includes=_paths.getIncluded();
return includes.toArray(new String[includes.size()]);
}
/* ------------------------------------------------------------ */
@Deprecated
public String[] getMethods()
@ -316,14 +330,6 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
return _minGzipSize;
}
/* ------------------------------------------------------------ */
@Override
protected void doStart() throws Exception
{
_vary=(_excludedAgentPatterns.size()>0)?GzipHttpOutputInterceptor.VARY_ACCEPT_ENCODING_USER_AGENT:GzipHttpOutputInterceptor.VARY_ACCEPT_ENCODING;
super.doStart();
}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.server.handler.HandlerWrapper#handle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
@ -350,7 +356,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
}
// If not a supported method - no Vary because no matter what client, this URI is always excluded
if (!_methods.contains(baseRequest.getMethod()))
if (!_methods.matches(baseRequest.getMethod()))
{
LOG.debug("{} excluded by method {}",this,request);
_handler.handle(target,baseRequest, request, response);
@ -418,37 +424,19 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
* @param ua the user agent
* @return boolean true if excluded
*/
protected boolean isExcludedAgent(String ua)
protected boolean isAgentGzipable(String ua)
{
if (ua == null)
return false;
if (_excludedAgentPatterns != null)
{
if (_uaCache.contains(ua))
return true;
for (Pattern pattern : _excludedAgentPatterns)
{
if (pattern.matcher(ua).matches())
{
if (_uaCache.size()>_uaCacheSize)
_uaCache.clear();
_uaCache.add(ua);
return true;
}
}
}
return false;
return _agentPatterns.matches(ua);
}
/* ------------------------------------------------------------ */
@Override
public boolean isMimeTypeGzipable(String mimetype)
{
return _mimeTypes.contains(mimetype);
return _mimeTypes.matches(mimetype);
}
/* ------------------------------------------------------------ */
@ -464,7 +452,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
if (requestURI == null)
return true;
return _paths.contains(requestURI);
return _paths.matches(requestURI);
}
/* ------------------------------------------------------------ */
@ -502,10 +490,20 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
*/
public void setExcludedAgentPatterns(String... patterns)
{
_excludedAgentPatterns.clear();
_agentPatterns.getExcluded().clear();
addExcludedAgentPatterns(patterns);
}
/* ------------------------------------------------------------ */
/**
* @param method to exclude
*/
public void setExcludedMethods(String... method)
{
_methods.getExcluded().clear();
_methods.exclude(method);
}
/* ------------------------------------------------------------ */
/**
* Set the mime types.
@ -531,12 +529,12 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
/* ------------------------------------------------------------ */
/**
* @param method to exclude
* @param patterns Regular expressions matching user agents to include
*/
public void setExcludedMethods(String... method)
public void setIncludedAgentPatterns(String... patterns)
{
_methods.getExcluded().clear();
_methods.exclude(method);
_agentPatterns.getIncluded().clear();
addIncludedAgentPatterns(patterns);
}
/* ------------------------------------------------------------ */

View File

@ -674,12 +674,13 @@ public class GzipDefaultTest
GzipTester tester = new GzipTester(testingdir,compressionType);
// Configure Gzip Handler
tester.getGzipHandler().setExcludedPaths("*.txt");
tester.getGzipHandler().setIncludedPaths("/file.txt");
tester.getGzipHandler().setExcludedPaths("/bad.txt");
tester.getGzipHandler().setIncludedPaths("*.txt");
// Prepare server file
int filesize = tester.getOutputBufferSize() * 4;
tester.prepareServerFile("file.txt",filesize);
tester.prepareServerFile("bad.txt",filesize);
// Set content servlet
tester.setContentServlet(DefaultServlet.class);
@ -693,6 +694,16 @@ public class GzipDefaultTest
{
tester.stop();
}
try
{
tester.start();
assertIsResponseNotGzipCompressed(tester,"GET","bad.txt",filesize,HttpStatus.OK_200);
}
finally
{
tester.stop();
}
}
public HttpTester.Response assertIsResponseNotGzipCompressed(GzipTester tester, String method, String filename, int expectedFilesize, int status)

View File

@ -20,30 +20,44 @@ package org.eclipse.jetty.util;
import java.util.HashSet;
import java.util.Set;
import java.util.function.BiFunction;
/** Utility class to maintain a set of inclusions and exclusions.
* <p>Maintains a set of included and excluded elements. The method {@link #matches(Object)}
* will return true IFF the passed object is not in the excluded set AND ( either the
* included set is empty OR the object is in the included set)
* <p>The type of the underlying {@link Set} used may be passed into the
* constructor, so special sets like Servlet PathMap may be used.
* @param <E>
* <p>
* @param <ITEM> The type of element
*/
public class IncludeExclude<E>
public class IncludeExclude<ITEM>
{
private final Set<E> _includes;
private final Set<E> _excludes;
private final Set<ITEM> _includes;
private final Set<ITEM> _excludes;
private final BiFunction<Set<ITEM>,ITEM, Boolean> _matcher;
/**
* Default constructor over {@link HashSet}
*/
public IncludeExclude()
{
_includes = new HashSet<>();
_excludes = new HashSet<>();
this(HashSet.class,null);
}
public IncludeExclude(Class<? extends Set<E>> setClass)
/**
* Construct an IncludeExclude
* @param setClass The type of {@link Set} to using internally
* @param matcher A function to test if a passed ITEM is matched by the passed SET, or null to use {@link Set#contains(Object)}
*/
public <SET extends Set<ITEM>> IncludeExclude(Class<SET> setClass, BiFunction<SET,ITEM, Boolean> matcher)
{
try
{
_includes = setClass.newInstance();
_excludes = setClass.newInstance();
_matcher = (BiFunction<Set<ITEM>,ITEM, Boolean>)matcher;
}
catch (InstantiationException | IllegalAccessException e)
{
@ -51,47 +65,52 @@ public class IncludeExclude<E>
}
}
public IncludeExclude(Set<E> includes, Set<E> excludes)
{
_includes = includes;
_excludes = excludes;
}
public void include(E element)
public void include(ITEM element)
{
_includes.add(element);
}
public void include(E... element)
public void include(ITEM... element)
{
for (E e: element)
for (ITEM e: element)
_includes.add(e);
}
public void exclude(E element)
public void exclude(ITEM element)
{
_excludes.add(element);
}
public void exclude(E... element)
public void exclude(ITEM... element)
{
for (E e: element)
for (ITEM e: element)
_excludes.add(e);
}
public boolean contains(E e)
public boolean matches(ITEM e)
{
if (_matcher==null)
{
if (_includes.size()>0 && !_includes.contains(e))
return false;
return !_excludes.contains(e);
}
if (_includes.size()>0 && !_matcher.apply(_includes,e))
return false;
return !_matcher.apply(_excludes,e);
}
public Set<E> getIncluded()
public int size()
{
return _includes.size()+_excludes.size();
}
public Set<ITEM> getIncluded()
{
return _includes;
}
public Set<E> getExcluded()
public Set<ITEM> getExcluded()
{
return _excludes;
}
@ -102,4 +121,9 @@ public class IncludeExclude<E>
_excludes.clear();
}
@Override
public String toString()
{
return String.format("%s@%x{i=%s,e=%s,m=%s}",this.getClass().getSimpleName(),hashCode(),_includes,_excludes,_matcher);
}
}

View File

@ -0,0 +1,106 @@
//
// ========================================================================
// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.util;
import java.util.AbstractSet;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.regex.Pattern;
/**
* A Set of Regular expressions strings.
* <p>
* Provides the efficient {@link #matches(String)} method to check for a match against all the combined Regex's
*/
public class RegexSet extends AbstractSet<String>
{
public static final BiFunction<RegexSet,String,Boolean> MATCHER=(rs,p)->{return rs.matches(p);};
private final Set<String> _patterns=new HashSet<String>();
private final Set<String> _unmodifiable=Collections.unmodifiableSet(_patterns);
private Pattern _pattern;
@Override
public Iterator<String> iterator()
{
return _unmodifiable.iterator();
}
@Override
public int size()
{
return _patterns.size();
}
@Override
public boolean add(String pattern)
{
boolean added = _patterns.add(pattern);
if (added)
updatePattern();
return added;
}
@Override
public boolean remove(Object pattern)
{
boolean removed = _patterns.remove(pattern);
if (removed)
updatePattern();
return removed;
}
@Override
public boolean isEmpty()
{
return _patterns.isEmpty();
}
@Override
public void clear()
{
_patterns.clear();
_pattern=null;
}
private void updatePattern()
{
StringBuilder builder = new StringBuilder();
builder.append("^(");
for (String pattern: _patterns)
{
if (builder.length()>2)
builder.append('|');
builder.append('(');
builder.append(pattern);
builder.append(')');
}
builder.append(")$");
_pattern = Pattern.compile(builder.toString());
}
public boolean matches(String s)
{
return _pattern!=null && _pattern.matcher(s).matches();
}
}

View File

@ -0,0 +1,153 @@
//
// ========================================================================
// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.util;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class IncludeExcludeTest
{
@Test
public void testEmpty()
{
IncludeExclude<String> ie = new IncludeExclude<>();
assertEquals(0,ie.size());
assertEquals(true,ie.matches("foo"));
}
@Test
public void testIncludeOnly()
{
IncludeExclude<String> ie = new IncludeExclude<>();
ie.include("foo");
ie.include("bar");
assertEquals(2,ie.size());
assertEquals(false,ie.matches(""));
assertEquals(true,ie.matches("foo"));
assertEquals(true,ie.matches("bar"));
assertEquals(false,ie.matches("foobar"));
}
@Test
public void testExcludeOnly()
{
IncludeExclude<String> ie = new IncludeExclude<>();
ie.exclude("foo");
ie.exclude("bar");
assertEquals(2,ie.size());
assertEquals(false,ie.matches("foo"));
assertEquals(false,ie.matches("bar"));
assertEquals(true,ie.matches(""));
assertEquals(true,ie.matches("foobar"));
assertEquals(true,ie.matches("wibble"));
}
@Test
public void testIncludeExclude()
{
IncludeExclude<String> ie = new IncludeExclude<>();
ie.include("foo");
ie.include("bar");
ie.exclude("bar");
ie.exclude("xxx");
assertEquals(4,ie.size());
assertEquals(true,ie.matches("foo"));
assertEquals(false,ie.matches("bar"));
assertEquals(false,ie.matches(""));
assertEquals(false,ie.matches("foobar"));
assertEquals(false,ie.matches("xxx"));
}
@Test
public void testEmptyRegex()
{
IncludeExclude<String> ie = new IncludeExclude<>(RegexSet.class,RegexSet.MATCHER);
assertEquals(0,ie.size());
assertEquals(true,ie.matches("foo"));
}
@Test
public void testIncludeRegex()
{
IncludeExclude<String> ie = new IncludeExclude<>(RegexSet.class,RegexSet.MATCHER);
ie.include("f..");
ie.include("b((ar)|(oo))");
assertEquals(2,ie.size());
assertEquals(false,ie.matches(""));
assertEquals(true,ie.matches("foo"));
assertEquals(true,ie.matches("far"));
assertEquals(true,ie.matches("bar"));
assertEquals(true,ie.matches("boo"));
assertEquals(false,ie.matches("foobar"));
assertEquals(false,ie.matches("xxx"));
}
@Test
public void testExcludeRegex()
{
IncludeExclude<String> ie = new IncludeExclude<>(RegexSet.class,RegexSet.MATCHER);
ie.exclude("f..");
ie.exclude("b((ar)|(oo))");
assertEquals(2,ie.size());
assertEquals(false,ie.matches("foo"));
assertEquals(false,ie.matches("far"));
assertEquals(false,ie.matches("bar"));
assertEquals(false,ie.matches("boo"));
assertEquals(true,ie.matches(""));
assertEquals(true,ie.matches("foobar"));
assertEquals(true,ie.matches("xxx"));
}
@Test
public void testIncludeExcludeRegex()
{
IncludeExclude<String> ie = new IncludeExclude<>(RegexSet.class,RegexSet.MATCHER);
ie.include(".*[aeiou].*");
ie.include("[AEIOU].*");
ie.exclude("f..");
ie.exclude("b((ar)|(oo))");
assertEquals(4,ie.size());
assertEquals(false,ie.matches("foo"));
assertEquals(false,ie.matches("far"));
assertEquals(false,ie.matches("bar"));
assertEquals(false,ie.matches("boo"));
assertEquals(false,ie.matches(""));
assertEquals(false,ie.matches("xxx"));
assertEquals(true,ie.matches("foobar"));
assertEquals(true,ie.matches("Ant"));
}
}

View File

@ -0,0 +1,86 @@
//
// ========================================================================
// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.util;
import org.junit.Test;
import org.junit.Assert;
public class RegexSetTest
{
@Test
public void testEmpty()
{
RegexSet set = new RegexSet();
Assert.assertEquals(false,set.contains("foo"));
Assert.assertEquals(false,set.matches("foo"));
Assert.assertEquals(false,set.matches(""));
}
@Test
public void testSimple()
{
RegexSet set = new RegexSet();
set.add("foo.*");
Assert.assertEquals(true,set.contains("foo.*"));
Assert.assertEquals(true,set.matches("foo"));
Assert.assertEquals(true,set.matches("foobar"));
Assert.assertEquals(false,set.matches("bar"));
Assert.assertEquals(false,set.matches(""));
}
@Test
public void testSimpleTerminated()
{
RegexSet set = new RegexSet();
set.add("^foo.*$");
Assert.assertEquals(true,set.contains("^foo.*$"));
Assert.assertEquals(true,set.matches("foo"));
Assert.assertEquals(true,set.matches("foobar"));
Assert.assertEquals(false,set.matches("bar"));
Assert.assertEquals(false,set.matches(""));
}
@Test
public void testCombined()
{
RegexSet set = new RegexSet();
set.add("^foo.*$");
set.add("bar");
set.add("[a-z][0-9][a-z][0-9]");
Assert.assertEquals(true,set.contains("^foo.*$"));
Assert.assertEquals(true,set.matches("foo"));
Assert.assertEquals(true,set.matches("foobar"));
Assert.assertEquals(true,set.matches("bar"));
Assert.assertEquals(true,set.matches("c3p0"));
Assert.assertEquals(true,set.matches("r2d2"));
Assert.assertEquals(false,set.matches("wibble"));
Assert.assertEquals(false,set.matches("barfoo"));
Assert.assertEquals(false,set.matches("2b!b"));
Assert.assertEquals(false,set.matches(""));
}
}