Visitor Pattern

For quite some time now it has puzzled me that the standard java Collections require the caller to iterate over the contents of the colleciton rathr than simply pass in function as a paramert to a "forEach" method on the collection. You know, like the Visitor Pattern?

Here is a real life example of where I have used this sort of thing to keep the users from having to mess around with the details of iterating over a the contents of a jar file.

import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public class JarUtil extends Files {
    public static interface Visitor {
        void go(String name, InputStream is);

        void log(Throwable t);
    }

    private ZipInputStream zis;

    public JarUtil(File jarFile) throws FileNotFoundException {
        this(new FileInputStream(jarFile));
    }

    public JarUtil(InputStream is) {
        this.zis = new ZipInputStream(is);
    }

    public void forEach(Visitor visitor) {

        try {
            while (true) {
                ZipEntry ze = zis.getNextEntry();
                if (ze == null) {
                    break;
                } else if (ze.isDirectory()) {
                    continue;
                }
                visitor.go(ze.getName(), zis);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            close(visitor, zis);
        }
    }

    private void close(Visitor visitor, Closeable closeMe) {
        if (closeMe != null) {
            try {
                closeMe.close();
            } catch (Throwable e) {
                visitor.log(e);
            }
        }
    }
}

And to give an idea of how this is used, here is a very bogus test, but hopefully it gets the point across:

import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;

import junit.framework.TestCase;

public class JarUtilTest extends TestCase {

    public void testRead() throws Exception {
        File javaLibDir = new File(System.getProperty("java.home"), "lib");
        File jarFile = new File(javaLibDir, "jce.jar");

        assertTrue(jarFile.exists());
        assertTrue(jarFile.length() > 0);

        JarUtil ju = new JarUtil(jarFile);

        final Map<String, String> textFiles = new HashMap<String, String>();

        JarUtil.Visitor visitor = new JarUtil.Visitor() {
            static final int EOF = -1;

            char[] buf = new char[4 * 1024];

            public void go(String name, InputStream is) {
                try {
                    StringBuffer sBuf = new StringBuffer(buf.length);
                    Reader reader = new InputStreamReader(is);
                    int len;
                    while ((len = reader.read(buf)) != EOF) {
                        sBuf.append(new String(buf, 0, len));
                    }
                    String text = sBuf.toString();

                    textFiles.put(name, text);
                } catch (Throwable t) {
                    log(t);
                }
            }

            public void log(Throwable t) {
                t.printStackTrace();
            }
        };

        ju.forEach(visitor);

        if (false) {
            for (Map.Entry<String, String> entry : textFiles.entrySet()) {
                System.out.println("Name: " + entry.getKey());
                System.out.println(" Size: " + entry.getValue().length());
            }
        }
    }
}

Cheers!

--Eric Herman
eric@igps.org

Wish us luck!

Valid XHTML 1.1