Writing Your Own Filter

Obviously, you want to extend the system as soon as possible. This section will give a simple guide to get you started. A good place to begin would be the system's elementary filters which are in the org.egothor.crusher.connectors package. You may ask, why "connectors". When this system was first developed, there was no filtering system, and everything was hard-coded by the programmer (user). Hard-coding is a somewhat (to understate massively) inelegant way of managing all the crap and all combinations of filters, so the system was rethought and rewritten. The old pieces of code were reused and connected to the new machinery via wrappers - "connectors". Thus, if you want to write your new filters just for the filtering system, it remains possible to do. However, it is better if you write your filter in the old fashion and then add the connector, . Why? Because such an extension can be also used without this filtering system, that is, if someone wants to prepare the Path by another way, your extension can still be used.

First of all, we must decide what objects will go into the filter and what will come out. Above, we talked about "something", here we must use something more real. We could use the core JAVA Object as the representative of "something". Unfortunately, sometimes it is not necessary to have any object in hand, because it is just a virtual "something" (e.g., an enumeration of Barrels) and we do not want to extend the original object (i.e., that enumeration) to represent the virtual "something" (in this case the enumeration of Barrels). As will be seen later, we could realize this with flags (see below), but we decided instead to use Strings which represent those "somethings". The Strings may represent almost anything as they are not as restrictive as Objects. The previous example could be solved, if we used "java.util.Enumeration(Barrel)" as the representative of the virtual object.

The source and destination "somethings" are then returned by Edge's functions IN and OUT. The weight is returned by getWeight. This function also requires one parameter of Path type that represents the previous path up to this Edge.

The flags mentioned before are very simple mechanisms enabling one to achieve better granularity of the filtering system. Every path has assigned to it a set of flags which can characterize it. The flags are cleared and set by active Edges as we show in the following example.

We will begin with the simplest filter - the filter that opens a filename, given as a String. The result of applying this filter is a java.io.Reader. The Edge that represents this can be realized as follows:

public class DefineReader extends Edge {
    public DefineReader() { }

    public static String IN() {
        return "java.lang.String"; 1

    public static String OUT() {
        return "java.io.Reader"; 2

    public int getWeight(Path on) {
        return 1; 3

    public Path apply(Path on) {
        if (on.isSet("FILENAME") == false) { 4
            return null;
        return new ReaderPath(on,getWeight(on)); 5

Input "something" is "java.lang.String"


Output is "java.io.Reader"


The price of the transformation is the lowest possible: 1


If the flag "FILENAME" is not set, we cannot apply this Edge, so null is returned.


If everything is fine, we can construct the Path that is produced when this Edge is applied at the end of the original Path "on".

Here we see the new function apply that constructs the new Path which is the product of appending this Edge to a Path. This function must (would) return null, if the edge is not applicable. We need not care about the correctness of entry (see IN), it is checked out elsewhere (as we will see later). Nevertheless, we must determine if the flags are correct. It is a good idea to ensure that "FILENAME" is set up, as it simply distinguishes that the String, which can be almost anything, is a filename. You can choose not to use it, but it may happen that the filtering system will try to use this Edge to a String that represents a file you can never touch or you will be done in by your sysop. Let your conscience be your guide.


Instead of the flag "FILENAME", we could simply represent the origin as "java.lang.String(FILENAME)".

The new Path must be also defined. It is quite simple (the compile function will be described below the listing):

class ReaderPath extends Path {
    public ReaderPath(Path prev,int incr) {
        setPrev(prev); 1
        increaseWeight(incr); 2
        setOutput(DefineReader.OUT()); 3
        setFlag("BUFFERED"); 4

    public Mill compile() {
        Mill m = getPrev().compile(); 5
        return new ReaderMill(m); 6

Set the previous part of this path (our addition to this is the object itself).


The new weight of the Path is increased by the given increment that is, in this case, calculated as Edge.getWeight(prev).


Set the product of the filter that is represented by this new Path.


The new flag "BUFFERED" is set for this new Path.


Compilation of this Path to Mill is achieved by compilation of the previous part of the Path and enveloping this mean product by ReaderMill.


This constructs the Mill for the desired output, in this case a Reader.

The binary representation of the Path (Mill) is then defined as:

class ReaderMill implements Mill {

    Mill entrance = null; 1
    java.io.Reader r = null; 2

    ReaderMill(Mill entrance) {
        this.entrance = entrance; 3

    public void initialize(Object input) {
        entrance.initialize(input); 4
        if (r != null) { 5
            try {
            } catch (java.io.IOException x) {}
        r = null;

    public Object process() throws Exception {
        String i = (String) entrance.process(); 6
        return r = new java.io.BufferedReader(new java.io.FileReader(i)); 7

    public void harvest(java.util.Hashtable prop) {
        entrance.harvest(prop); 8

    public void sow(java.util.Hashtable prop) {
        entrance.sow(prop); 9

The previous Mill.


Our internal object that does the elementary "transformation" from "something" to "something".


Just init this Mill.


When we initialize this, we send it to the Mill that is in action before we are, because we do not process this object - it would be processed by someone who would give us our "String". These initializations are handled by the IniPath of the filtering system, so we do not care about this.


This starts the code that uses the "Tip" given above. When the input is null, we reset out elementary filter, that is, we close the file we may have opened before - see the process function.


Here is the action that is done by the elementary filter. We first read our input from the previous Path (a chain, if you will). If the previous Path lies about its output or a misconfiguration (or collision in naming) happens, the type cast fails. Then it is a bug and it would be solved by inspection of previous Path.


More often, everything is fine and we can produce the result we desired - the java.io.Reader.


No information is collected, so we just send the request up. We could count the number of read bytes, and append this value to the Hashtable under our special key (that is then read by someone who waits for our value, because he is sure that we will be in action - it can be ensured when he uses the proper flags) - i.e. prop.put("file.reader.byte.count",value).


We also do not read any initialization attributes of this Mill.

Prev Up Next
Filter Home Registration and Use of Filters
© 2003-2004 Egothor Developers