Custom ClassLoader example to allow two Singletons

For another thread I created an example ClassLoader to demonstrate how you can have two different instances of a static field running in the same JVM. There is a zip file with code at: http://flick.nerdc.ufl.edu/jgo/ClassLoaderNamespaces.zip but incase the server is offline I’ll include the code below.

File: /game/foo/Bar.java

package foo;

public class Bar {
    private static int count = 0;

    public String toString() {
        return String.valueOf(++count);
    }
}

File: /loader/Test.java

import java.io.File;

public class Test {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        String myPackage = "foo.";
        String baseDir = ".." + File.separator + "game" + File.separator; // ../game/ on UNIX or ..\game\ on DOS

        // Create two ClassLoaders so we have two different namespaces
        ClassLoader cl1 = new SimpleClassLoader(myPackage, baseDir);
        ClassLoader cl2 = new SimpleClassLoader(myPackage, baseDir);

        // Create three Objects, two of which share the same ClassLoader
        Object o1 = cl1.loadClass("foo.Bar").newInstance();
        Object o2 = cl2.loadClass("foo.Bar").newInstance();
        Object o3 = cl1.loadClass("foo.Bar").newInstance(); // Same ClassLoader as o1

        // Print the output
        System.out.println("o1: " + o1.toString());
        System.out.println("o2: " + o2.toString());
        System.out.println("=======");
        System.out.println("o1: " + o1.toString());
        System.out.println("o2: " + o2.toString());
        System.out.println("=======");
        System.out.println("o1: " + o1.toString());
        System.out.println("o2: " + o2.toString());
        System.out.println("=======");
        System.out.println("o1: " + o1.toString());
        System.out.println("o2: " + o2.toString());
        System.out.println("o3: " + o3.toString());
        System.out.println("=======");
        System.out.println("o1: " + o1.toString());
        System.out.println("o2: " + o2.toString()); // Will be different than o1 because of o3 above
        System.out.println("o3: " + o3.toString());
        System.out.println("=======");
        System.out.println("o1: " + o1.toString());
        System.out.println("o2: " + o2.toString());
        System.out.println("o3: " + o3.toString());
    }
}

File: /loader/SimpleClassLoader.java which is based on: http://www.javaworld.com/javaworld/jw-10-1996/indepth/indepth.src.html

/*
 * SimpleClassLoader.java - a bare bones class loader.
 *
 * Copyright (c) 1996 Chuck McManis, All Rights Reserved.
 *
 * Permission to use, copy, modify, and distribute this software
 * and its documentation for NON-COMMERCIAL purposes and without
 * fee is hereby granted provided that this copyright notice
 * appears in all copies.
 *
 * CHUCK MCMANIS MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE
 * SUITABILITY OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING
 * BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. CHUCK MCMANIS
 * SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT
 * OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
 */

// EDIT: Whatever was here before was a mess
import java.io.FileInputStream;
import java.io.File;
import java.util.Hashtable;

public class SimpleClassLoader extends ClassLoader {
    private Hashtable classes = new Hashtable();

    // NEW
    private String myPackage;
    private String codeBase;

    public SimpleClassLoader(String myPackage, String codeBase) {
        // NEW
        this.myPackage = myPackage;
        this.codeBase = codeBase;
    }

    /**
     * This sample function for reading class implementations reads
     * them from the local file system
     */
    private byte getClassImplFromDataBase(String className)[] {
        System.err.println("        >>>>>> Fetching the implementation of " + className);
        byte result[];


        // NEW: convert package seperators '.' to directory seperators
        StringBuffer sb = new StringBuffer(className);
        for (int i=0, n=sb.length(); i < n; i++) {
            if (sb.charAt(i) == '.') {
                sb.setCharAt(i, File.separatorChar);
            }
        }
        className = sb.append(".class").toString();
        System.err.println("New className: " + className); // to double check

        try {
            // EDIT: Moved some stuff up a few lines
            // FileInputStream fi = new FileInputStream("store\\"+className+".impl");
            FileInputStream fi = new FileInputStream(codeBase + className);
            result = new byte[fi.available()];
            fi.read(result);
            return result;
        } catch (Exception e) {

            /*
             * If we caught an exception, either the class wasnt found or it
             * was unreadable by our process.
             */
            return null;
        }
    }

    /**
     * This is a simple version for external clients since they
     * will always want the class resolved before it is returned
     * to them.
     */
    public Class loadClass(String className) throws ClassNotFoundException {
        return (loadClass(className, true));
    }

    /**
     * This is the required version of loadClass which is called
     * both from loadClass above and from the internal function
     * FindClassFromClass.
     */
    public synchronized Class loadClass(String className, boolean resolveIt) throws ClassNotFoundException {
        Class result;
        byte classData[];

        System.err.println("        >>>>>> Load class : " + className);

        /* Check our local cache of classes */
        result = (Class)classes.get(className);
        if (result != null) {
            System.err.println("        >>>>>> returning cached result.");
            return result;
        }

        // NEW: Added the if startsWith
        if (!className.startsWith(myPackage)) {
            /* Check with the primordial class loader */
            try {
                result = super.findSystemClass(className);
                System.err.println("        >>>>>> returning system class (in CLASSPATH).");
                return result;
            } catch (ClassNotFoundException e) {
                System.err.println("        >>>>>> Not a system class.");
            }
        }

        /* Try to load it from our repository */
        classData = getClassImplFromDataBase(className);
        if (classData == null) {
            throw new ClassNotFoundException();
        }

        /* Define it (parse the class file) */
        result = defineClass(classData, 0, classData.length);
        if (result == null) {
            throw new ClassFormatError();
        }

        if (resolveIt) {
            resolveClass(result);
        }

        classes.put(className, result);
        System.err.println("        >>>>>> Returning newly loaded class.");
        return result;
    }
}

And when you run java Test you get output something like:

[quote]o1: 1
o2: 1

o1: 2
o2: 2

o1: 3
o2: 3

o1: 4
o2: 4
o3: 5

o1: 6
o2: 5
o3: 7

o1: 8
o2: 6
o3: 9
[/quote]
Hope this is useful.