public FilterDef.Source asSource()
 	throws IOException, FilterDefFormatException
 {
  final Map<FilterDTO.Type, List<String>> patternsByType = new HashMap<FilterDTO.Type, List<String>>();
  LineNumberReader input = new LineNumberReader(new InputStreamReader(getInputStream(), TEXT_CHARSET));
  // read lines until eof
  for (FilterDTO.Type currentType = null;;)
  {
   String line = input.readLine();
   if (null == line)
    break;
   // if a line is non-empty and not a comment
   if (0 < line.length() && '#' != line.charAt(0))
   {
    // outside a typed section - report an error
    if (null == currentType)
     throw new FilterDefFormatException("unexpected text \"" + line
       + "\" outside of filter type specifiers", toString(), input.getLineNumber());
    // within a typed section - add to the list for that type
    else
    {
     List<String> patterns = patternsByType.get(currentType);
     if (null == patterns)
      patternsByType.put(currentType, patterns = new ArrayList<String>());
     patterns.add(line);
    }
   }
   // check for a typed section tag
   else if (line.startsWith("#:"))
   {
    // find the end of the tag and ignore the rest
    int cut = line.indexOf(':', 2);
    if (1 < cut)
    {
     line = line.substring(2, cut).toUpperCase();
     // read the type, case-insensitive
     try
     {
      currentType = FilterDTO.Type.valueOf(line);
     } catch (IllegalArgumentException badType)
     {
      throw new FilterDefFormatException(
	"invalid section type \"" + line + '"', toString(), input.getLineNumber(), badType);
     }
    }
   }
  }
  // wrap the result into a Source
  return new FilterDef.Source()
  {
   public List<String> patternStringList(FilterDTO.Type type)
   {
    return patternsByType.get(type);
   }
  };
 }