Recientemente me disponía a poner orden en la sección de descargas y software de este sitio donde no hay más que vaporware, pruebas de concepto y algunas ideas disparatadas. Casi todas ellas están dejadas ahí por si a alguien le servía algo como idea o por si algún trozo de código podía ser útil para uno mismo: cómo leer un fichero properties, cómo hacer el tema de locales en php, cómo hacer un bot de irc en perl para tomar el pelo a los salidos, en fin un cajón desastre.

Uno de las tonterías que tenía por ahí era una vieja idea de montar un motor de inferencia propio. La inteligencia artificial fue de las últimas asignaturas de la carrera que se me atravesó un poco con uno de los exámenes más jodidos de todos al menos en la facultad de Donosti: mezclaba programación funcional en lisp, programación lógica con prolog y programar una especie de sistemas expertos sencillos en clisp, y no recuerdo qué cosas más -oscuras o mejor dicho difusas ;)-. Un horror. Es una asignatura que bien dada puede ser fascinante pero tal cual te la dan lo que acabas haciendo es procurar preparar el examen, más que aprender algo útil.

El caso es que el tema de los motores de inferencia, los sistemas expertos me parecía algo más interesante para aplicar a problemas reales. Por ejemplo tenía la idea de crear un administrador de sistemas virtual que en función del estado del sistema fuera capaz de tomar decisiones y aplicar los  comandos oportunos.Esto quizá vino inspirado por una peli de serie B llamada Webmaster donde sale algo parecido: un sysadmin que transmite sus ordenes a un ente virtual llamado EGO que va aprendiendo de su amo y mantiene y supervisa los servidores.

Comencé desarrollando una especie de motor de inferencia en perl cuyo código debe andar por sourceforge desde al año 2000 (madre del amor hermoso) y que lo mismo ya está desaparecido. Si lo viera sería incapaz de reconocer la paternidad del mismo ya que perl es un lenguaje que por mucho que te esfuerces en ponerlo claro queda inevitablemente sucio (demasiados caracteres especiales).

En el 2001 la humanidad todavía no había llegado a Jupiter como predijo Clarke, así que me dediqué a esa misma idea y la traté de pasar a Java manteniendo en mismo nombre, inferenczy,evidente para mí pero sin duda rebuscado para cualquiera que no haya leído la saga del Necroscopio de Brian Lumley, o sea la gran mayoría de la gente normal informática o no. Justo cuando estaba revisando ese código me encontré que inferenczy estaba montado sobre otro proyecto que pensaba que estaba perdido sin remedio: Ozone.

OOO = O3 = Ozone
Era por las tres O, de Object Oriented Operative System. Tras ese nombre tan pretendidamente molón lo que trataba de hacer es un sistema operativo hecho enteramente en java, todo de consola claro. En su día ya existía alguna cosa así JavaOS se me ocurre y seguramente ahora habrá miles de ellos. Me ha sorprendido un poco cómo compliqué el desarrollo de ese sistema seguramente inspirado en el entorno de trabajo que me rodeaba en esa época: en Madrid trabajando para una una de las más célebres cárnicas (AXPE) que a su vez me tenía en la mítica picadora de carne llamada Andersen (¡¡toma combo breaker!!). Ese era un proyecto en el que los pérfidos gerentes de Andersen le habían vendido a Safei un proyecto monstruoso con un framework del mal donde TODO era XML y donde se metía lo típico de la época: Java, Oracle, EJB, Unix (no linux) y ante todo XML everywhere: los formularios web estaban definidos en xml. Las consultas a BBDD estaban definidas en xml, etc... una época en la que, te lo creas o no, todavía se fumaba en la oficina.

Así que Ozone e inferenczy iban a usar XML. Y por supuesto JDBC, que siempre queda más profesional. Como digo hace unas semanas me bajé el código y eché un ojo a los ficheros de log que generaba el sistema y esto fue lo que me encontré:
Wed Sep 05 11:30:06 CEST 2001> <Kernel> Kernel loaded.
Wed Sep 05 11:30:06 CEST 2001> <Kernel> starting init...
Wed Sep 05 11:30:06 CEST 2001> <Init> Init started. [pid: 1]
Wed Sep 05 11:30:06 CEST 2001> <Init> Init completed succesfully.
Wed Sep 05 11:30:06 CEST 2001> <DBConnection> starting...
Wed Sep 05 11:30:06 CEST 2001> <DBConnection> Connected to DB
Wed Sep 05 11:30:06 CEST 2001> <DBConnection> DB Operations interface set to: SQL
Wed Sep 05 11:30:06 CEST 2001> <DBOperationsSQL> SQL SELECT successful: select * from personas
Wed Sep 05 11:30:06 CEST 2001> <DBConnection> started. [pid: 2]

Madre mía, 5 de septiembre de 2001. Y al poco se ve esto:

Thu Sep 06 08:19:06 CEST 2001> <Kernel> Kernel loaded.
Thu Sep 06 08:19:06 CEST 2001> <Kernel> starting init...
Thu Sep 06 08:19:06 CEST 2001> <Init> Init started. [pid: 1]
Thu Sep 06 08:19:06 CEST 2001> <Init> Init completed succesfully.
Thu Sep 06 08:19:06 CEST 2001> <DBConnection> starting...
Thu Sep 06 08:19:07 CEST 2001> <DBConnection> Connected to DB
Thu Sep 06 08:19:07 CEST 2001> <DBConnection> DB Operations interface set to: SQL
Thu Sep 06 08:19:07 CEST 2001> <DBConnection> started. [pid: 2]
Wed Sep 12 11:35:06 CEST 2001> <Kernel> Kernel loaded.
Wed Sep 12 11:35:07 CEST 2001> <Kernel> starting init...
Wed Sep 12 11:35:07 CEST 2001> <Init> Init started. [pid: 1]
Wed Sep 12 11:35:07 CEST 2001> <Init> Init completed succesfully.
Wed Sep 12 11:35:07 CEST 2001> <DBConnection> starting...
Wed Sep 12 11:35:07 CEST 2001> <DBConnection> Can't connect to DB
Wed Sep 12 11:35:07 CEST 2001> <DBConnection> DB Operations interface set to: SQL
Wed Sep 12 11:35:07 CEST 2001> <DBConnection> started. [pid: 2]

Parece que tras el 11 de septiembre el mundo se derrumbaba pero yo seguía a mi rollo. ¡¡Oh cielos no! ¡¡No me conectaba a la BBDD!!

El caso es que, años después, lo volvía a ejecutar tirando de un script BAT que había para tal efecto. Y esto sucedió:
Tue Sep 18 09:26:00 CEST 2001> <Kernel> Kernel loaded.
Tue Sep 18 09:26:00 CEST 2001> <Kernel> starting init...
Tue Sep 18 09:26:00 CEST 2001> <Init> Init started. [pid: 1]
Tue Sep 18 09:26:00 CEST 2001> <SystemXMLReader> Init Data/etc/init.xml parsing.
Tue Sep 18 09:26:00 CEST 2001> <SystemXMLReader> parsed.
Tue Sep 18 09:26:00 CEST 2001> <Init> Loading com.javamercenary.ai.inferenczy.core.DBConnection...
Tue Sep 18 09:26:00 CEST 2001> <Init> Init completed succesfully.
Tue Sep 18 09:26:00 CEST 2001> <DBConnection> starting...
Tue Sep 18 09:26:00 CEST 2001> <DBConnection> Can't connect to DB
Tue Sep 18 09:26:00 CEST 2001> <DBConnection> DB Operations interface set to: SQL
Tue Sep 18 09:26:00 CEST 2001> <DBConnection> started. [pid: 2]
<LOG><DATE>Tue Sep 18 09:28:26 CEST 2001</DATE><MESSAGE><Kernel> Kernel loaded.</MESSAGE></LOG>
<LOG><DATE>Tue Sep 18 09:28:26 CEST 2001</DATE><MESSAGE><Kernel> starting init...</MESSAGE></LOG>
<LOG><DATE>Tue Sep 18 09:28:26 CEST 2001</DATE><MESSAGE><Init> Init started. [pid: 1]</MESSAGE></LOG>
<LOG><DATE>Tue Sep 18 09:28:26 CEST 2001</DATE><MESSAGE><SystemXMLReader> Init Data/etc/init.xml parsing.</MESSAGE></LOG>
<LOG><DATE>Tue Sep 18 09:28:27 CEST 2001</DATE><MESSAGE><SystemXMLReader> parsed.</MESSAGE></LOG>
<LOG><DATE>Tue Sep 18 09:28:27 CEST 2001</DATE><MESSAGE><Init> Loading com.javamercenary.ai.inferenczy.core.DBConnection... </MESSAGE></LOG>
<LOG><DATE>Tue Sep 18 09:28:27 CEST 2001</DATE><MESSAGE><Init> Init completed succesfully.</MESSAGE></LOG>
<LOG><DATE>Tue Sep 18 09:28:27 CEST 2001</DATE><MESSAGE><DBConnection> starting...</MESSAGE></LOG>
<LOG><DATE>Tue Sep 18 09:28:27 CEST 2001</DATE><MESSAGE><DBConnection> Can't connect to DB</MESSAGE></LOG>
<LOG><DATE>Tue Sep 18 09:28:27 CEST 2001</DATE><MESSAGE><DBConnection> DB Operations interface set to: SQL</MESSAGE></LOG>
<LOG><DATE>Tue Sep 18 09:28:27 CEST 2001</DATE><MESSAGE><DBConnection> started. [pid: 2]</MESSAGE></LOG>
<LOG><DATE>Fri May 31 23:47:48 CEST 2013</DATE><MESSAGE><Kernel> Kernel loaded.</MESSAGE></LOG>
<LOG><DATE>Fri May 31 23:47:48 CEST 2013</DATE><MESSAGE><Kernel> starting init...</MESSAGE></LOG>
<LOG><DATE>Fri May 31 23:47:48 CEST 2013</DATE><MESSAGE><Init> Init started. [pid: 1]</MESSAGE></LOG>
<LOG><DATE>Fri May 31 23:47:48 CEST 2013</DATE><MESSAGE><SystemXMLReader> Init Data/etc/init.xml parsing.</MESSAGE></LOG>
<LOG><DATE>Fri May 31 23:47:49 CEST 2013</DATE><MESSAGE><SystemXMLReader> parsed.</MESSAGE></LOG>
<LOG><DATE>Fri May 31 23:47:49 CEST 2013</DATE><MESSAGE><Init> Loading com.javamercenary.ai.inferenczy.core.DBConnection... </MESSAGE></LOG>
<LOG><DATE>Fri May 31 23:47:49 CEST 2013</DATE><MESSAGE><DBConnection> starting...</MESSAGE></LOG>
<LOG><DATE>Fri May 31 23:47:49 CEST 2013</DATE><MESSAGE><Init> Init completed succesfully.</MESSAGE></LOG>
<LOG><DATE>Fri May 31 23:47:49 CEST 2013</DATE><MESSAGE><DBConnection> Can't connect to DB</MESSAGE></LOG>
<LOG><DATE>Fri May 31 23:47:49 CEST 2013</DATE><MESSAGE><DBConnection> DB Operations interface set to: SQL</MESSAGE></LOG>
<LOG><DATE>Fri May 31 23:47:49 CEST 2013</DATE><MESSAGE><DBConnection> started. [pid: 2]</MESSAGE></LOG>

Hay dos cosas: se ve que el 18 de septiembre, inmerso en la locura de XML cambié el formato del fichero de log usando tags (MAL, en mayúsculas) con la vaga idea de que quizá en algún momento podría interesar parsear los ficheros de log para facilitar su análisis. Cosa que sí es útil. La revisión y procesamient de logs puede servir para sacar estadísticas por ejemplo como hacen algunos analizadores web como webalizer.

La otra cosa, algo más anecdótica es el salto temporal en el fichero de log desde:
Tue Sep 18 09:28:27 CEST 2001
Hasta
Fri May 31 23:47:48 CEST 2013

Madre mía, 12 años nos contemplan. Y el código seguía funcionando en una máquina virtual mucho más moderna.

Bueno, en lugar de empezar poniendo un diagrama de clases, voy a ir a haciendo algo de ingeniería inversa para poder entender cómo narices funcionaba esto. Y todo parte de dos ficheros *.bat, uno para compilar y el otro para ejecutar. Efectivamente, ignoraba la existencia de herramientas como ANT, si es que existían entonces que no lo sé. El caso es que el fichero de compilación hacía lo siguiente:
@echo off
@REM Copyright (c) 2000 Pedro Alcazar, Inc. All Rights Reserved.

@REM Adjust these variables to match your environment
set CLASSES=src/core/*.java src/api/*.java src/ie/*.java

set PATH=%PATH%;c:\jbuilder4\jdk1.3\bin
if "" == "%JAVA_HOME%" set JAVA_HOME=c:\jbuilder4\jdk1.3

set MYCLASSPATH=%JAVA_HOME%\lib\classes.zip;.;c:\weblogic\lib\parser.jar

echo  Build %CLASSES% ...
javac -d .  -classpath %MYCLASSPATH% %CLASSES%
echo  [ DONE ]
pause


Madreeee! ¡El Jbuilder! ¡El jdk1.3! ¡El weblogic!  ¿Y quién era Pedro Alcazar? ¡Era yo! Si quieres saber por qué... pues en fin.

A ver el de ejecución:

@echo off
@REM Copyright (c) 2000 Pedro Alcazar, Inc. All Rights Reserved.

@REM Adjust these variables to match your environment
set STARTCLASS=com.javamercenary.ai.inferenczy.core.Boot

set PATH=%PATH%;c:\jbuilder4\jdk1.3\bin
if "" == "%JAVA_HOME%" set JAVA_HOME=c:\jbuilder4\jdk1.3

set MYCLASSPATH=%JAVA_HOME%\lib\classes.zip;./lib/parser.jar;.

echo running %STARTCLASS%
java -classpath %MYCLASSPATH% %STARTCLASS%

pause

Bueno vale, parecido pero ya vemos un inicio:
com.javamercenary.ai.inferenczy.core.Boot

Cómo mola lo de javamercenary, y lo de core, jajaja, qué pretencioso es todo. Bueno, a ver qué narices hacía la clase Boot:

package com.javamercenary.ai.inferenczy.core;
 
/**
 * Título:       Inference Engine
 * Descripcion:
 * Copyright:    Copyright (c) 2001
 * Empresa:
 * @author P. Al.
 * @version 1.0
 */
 
 public class Boot  {

     
     /**
     * method
     */
    Boot () {
    }
        
    
    public static void main (String args[]) {
        System.out.println("INFeReNCZy (c) P. Al. Madrid September 2001");
        System.out.println("INFeReNCZy Bootstrap...");
        new Kernel().start();
    }

 }

Los comentarios javadoc son lo que te metía el jbuilder. Vale, se ve hay un Kernel quese inicia, y tiene pinta de ser un hilo. Vamos a verlo:

package com.javamercenary.ai.inferenczy.core;

/**
 * Título:       Inference Engine
 * Descripcion:
 * Copyright:    Copyright (c) 2001
 * Empresa:
 * @author P. Al.
 * @version 1.0
 */
 import java.util.Hashtable;
 import java.util.Vector;
 import java.sql.Connection;
 
 public class Kernel extends Thread {
     
     /**
     * KERNEL ATTRIBUTES
     */
     // process table
    private final static Hashtable _PROCESSES = new Hashtable();
    // system properties: loglevel, multiuser,...
    private InferenczyProperties _SYSTEM_PROPERTIES = new InferenczyProperties();
    // system interface
    private SystemInterface _SI = null;
    // log service
    private static final Log _LOG = new Log();
    // console output
    private static final Printer _PRINTER = new Printer();
    // XML Reader
    private static final SystemXMLReader _XML_READER = new SystemXMLReader();
    // database connection
    private Connection _DBCONNECTION = null;
    // database operations interface
    private DBOperationsInterface _DBOPERATIONS_INTERFACE = null;
     
     /**
     * method
     */
    Kernel () {
    }
        
    /**
    * run the Kernel
    */
    public void run () {
        // first of all, load system properties.
        if (!this.loadProperties()) {
            System.err.println("Failed to load Properties file.");    
        }
        _SI = new SystemInterface(this);
        //setting properties.
        _LOG.setProperties(_SI);
        _PRINTER.setProperties(_SI);    
        _XML_READER.setProperties(_SI);    
        _LOG.log(1,"<Kernel> Kernel loaded.");
        // now run first process: init (pid:1)
        _LOG.log(1,"<Kernel> starting init...");
        startProcess("com.javamercenary.ai.inferenczy.core.Init",1,1,1,1,1,"Init program");
    }
    
    /**
    * loadProperties
    */
    private boolean loadProperties () {
        return _SYSTEM_PROPERTIES.loadProperties();
    }
    
    /**
    * getProperties
    */
    public InferenczyProperties getProperties () {
        return  _SYSTEM_PROPERTIES;
    }
    
    /**
    * startProcess
    * return 0 if everything works well
    */
    public int startProcess (String name,int PID,int UID,int GID,int STATE,int PRIORITY, String P_INFO) {
        Object o = null;
        try {
        o = Class.forName(name).newInstance();    
        } catch (ClassNotFoundException cnfe) {
            _LOG.log(1,"Error on instantiation. Class not Found."+cnfe.getMessage());
            return -1;
        } catch (IllegalAccessException iae) {
            _LOG.log(1,"Error on instantiation.Illegal Access"+iae.getMessage());
            return -2;
        } catch (InstantiationException ie) {
            _LOG.log(1,"Error on instantiation."+ie.getMessage());
            return -3;
        }
        try {
        if (((Process) o).setAttributes(PID,UID,GID,STATE, PRIORITY,P_INFO,this._SI)) {
            addProcess(((Process)o));
            ((Process) o).start();            
        }
        return 0;
        } catch (Exception e) {
            removeProcess(new Integer(((Process)o).getPID()));
            _LOG.log(1,"Error on instantiation."+e.getMessage());
            return -4;
        }
        
    }

/**
* addProcess
*/
 private boolean addProcess (Process p) {
     try {
    _PROCESSES.put(new Integer(p.getPID()),p);     
        return true;
    } catch (Exception e) {
        _PRINTER.print(-1,"Error starting process.\n");
        return false;
    }
}

/**
* removeProcess
*/
 public boolean removeProcess (Integer pid) {
     try {
    _PROCESSES.remove(pid);     
        return true;
    } catch (Exception e) {
        _PRINTER.print(-1,"Error deleting process.\n");
        return false;
    }
}

/**
* getProcessTable
*/
 public Hashtable getProcessTable () {     
    return _PROCESSES;     
 }

/**
* log
*/
public boolean log (int loglevel, String message) {
    return _LOG.log(loglevel,message);
}

/**
* print
*/
public void print (int flag, String message) {
     _PRINTER.print(flag,message);
}


/**
* setDBConnection
*/
public void setDBConnection (Connection conn) {
    this._DBCONNECTION = conn;    
}

/**
* getDBConnection
*/
public Connection getDBConnection () {
    return this._DBCONNECTION;    
}

/**
* setDBOperations
*/
public void setDBOperationsInterface (DBOperationsInterface dbo) {
    this._DBOPERATIONS_INTERFACE = dbo;    
}

/**
* getDBOperations
*/
public DBOperationsInterface getDBOperationsInterface () {
    return this._DBOPERATIONS_INTERFACE;    
}

/**
* readData
* @param String filename
* @param String[] elements
* @return Hashtable
*/
public Hashtable readData (String filename, String[] elements) {
    return this._XML_READER.initParse(filename,elements);
}

/**
* readData
* @param String filename
* @param String field
* @return Hashtable
*/
public Vector readData (String filename,String field) {
    return this._XML_READER.initParse(filename,field);
}
}//end kernel


Vaya vaya, si es una especie de sistema operativo.
Tiene properties, un sistema de logs, un printer que será para sacar mensajes por algún lado, una tabla de procesos, y un método para crear nuevos procesos que hace uso del Reflection. Y aparte de getters y setters tiene como peculiar un interfaz para una BBDD y un lector de XML.

Básicamente el Kernel pone en marcha nuevas instancias de programas que heredan de la clase Process, la cual tiene esta pinta:
package com.javamercenary.ai.inferenczy.core;

/**
 * Título:       Inference Engine
 * Descripcion:
 * Copyright:    Copyright (c) 2001
 * Empresa:
 * @author P. Al.
 * @version 1.0
 */
 
 public abstract class Process extends Thread {
 
     /**
     * attributes
     */
     private int PID = -1 ;
     private int UID = 0;
     private int GID = 0;
     private int STATE = 0;
     private int PRIORITY = 0;
     private String P_INFO = "";
     private long START_DATE;
     private SystemInterface _SI = null;
     
     /**
     * constructors
     */
     public Process () {
         this.START_DATE = System.currentTimeMillis();
    }

     public Process (int PID,int UID,int GID,int STATE,int PRIORITY, String P_INFO, SystemInterface _SI) {
         this.PID = PID;
         this.UID = UID;
         this.GID = GID;
         this.PID = PID;
         this.PRIORITY = PRIORITY;
         this.P_INFO = P_INFO;
         this._SI = _SI;
    }

     public boolean setAttributes (int PID,int UID,int GID,int STATE,int PRIORITY, String P_INFO, SystemInterface _SI) {
         if (PID == -1) return false;
         
         this.PID = PID;
         this.UID = UID;
         this.GID = GID;
         this.PID = PID;
         this.PRIORITY = PRIORITY;
         this.P_INFO = P_INFO;
         this._SI = _SI;
         return true;
    }

     public  int getPID (){
         return PID;
     }
          
     public  int getUID () {
         return UID;
     }
     
     public  int getGID () {
         return GID;
     }
     
     public  int getSTATE () {
          return STATE;
    }
    
     public  int setSTATE (int state) {
         return STATE = state;
     }
     
    public  int getPRIORITY () {
         return PRIORITY;
    }
    
    public  int setPRIORITY(int priority) {
        return PRIORITY = priority;
    }
    
    public  long getSTART_DATE() {
        return START_DATE;
    }
    
    public  String getP_INFO () {
        return P_INFO;
    }
    
    public  SystemInterface get_SI () {
        return _SI;
    }
        
    public  String setP_INFO(String p_info) {
        return P_INFO = p_info;    
    }
 }

Un Thread.

El primer proceso que inicia el kernel es, como no podía ser menos Init. Y este hereda de process y lo que hace básicamente es cargar en el kernel una serie de procesos que tiene indicado dentro de init.d:

package com.javamercenary.ai.inferenczy.core;

/**
 * Título:       Inference Engine
 * Descripcion:
 * Copyright:    Copyright (c) 2001
 * Empresa:
 * @author P. Al.
 * @version 1.0
 */
 import java.util.Vector;

 public class Init extends Process {

     
     /**
     * method
     */
     public Init () {
         super();
     }

    public Init (int PID,int UID,int GID,int STATE,int PRIORITY, String P_INFO, SystemInterface _SI) {
        super(PID,UID,GID,STATE,PRIORITY,P_INFO, _SI);
    }
    
    /**
    * run  method
    */
    public void run () {
        get_SI().log(1,"<Init> Init started. [pid: "+this.getPID()+"]");
        loadProcesses();
        while (true) {
        }
    }

    /**
    * loadProcesses
    * load processes from init.d
    */
    private boolean loadProcesses () {
        String line = "";
        int cont = 2;
        boolean errors = false;
        Vector vclasses = null;
        
        try {
        vclasses = get_SI().readData(get_SI().getProperty("init.d"),"CLASSNAME");
        for (int i = 0;i<vclasses.size();i++) {
            get_SI().log(1,"<Init> Loading "+vclasses.elementAt(i)+"... ");
            if (get_SI().startProcess((String)vclasses.elementAt(i),cont++,1,1,1,1,line) < 0)
                errors = true;
        }
        if (errors)
            get_SI().log(1,"<Init> Init finished with errors. Check init.d file.");
        else
            get_SI().log(1,"<Init> Init completed succesfully.");
        return true;    
        } catch (Exception e) {
                get_SI().log(1,"Failed loadin properties .Error: "+e.getMessage());
            return false;
        }
    }
 }
El sistema gira en torno al kernel. Y en resumen los procesos como el init y los recursos del sistema se comunican con el kernel a través de un intermediario llamado SystemInterface. En el diagrama UML puedes hacerte una vaga idea de cómo está montado, y verás que las clases se acoplan y que todo es un poco infernal. Pero como digo... era joven e incosciente. Ahora ya soy simplemente joven y  plenamente inconsciente. ¿o era simplemente inconsciente?

Diagrama

Delirio total

Ojo que lo del sistema operativo no era más que una base para montar el motor de inferencia. Y lo pretencioso del tema es que quería crear programas escritos en XML. Es decir, era una especie de lenguaje de programación genérico, al que por ejemplo se le podría hipotéticamente aplicar un XSLT y convertir en un lenguaje concreto. Por ejemplo de un programa.xml podriamos tener un Programa.java y un Programa.cpp.

Todo el proyecto está ahora subido a google code. Echa un ojo a la carpeta Data donde encontrarás el DTD: http://code.google.com/p/ozone/

Para descargar por git:
git clone https://code.google.com/p/ozone/

O puedes descargar el zip original:

http://pello.info/filez/inf.zip

aunque existe otro original en alguna parte con otros procesos como el login y un shell... pero quién sabe dónde.