First batch of the tools merged in...
authorPeter Mount <peter@retep.org.uk>
Mon, 5 Mar 2001 09:15:38 +0000 (09:15 +0000)
committerPeter Mount <peter@retep.org.uk>
Mon, 5 Mar 2001 09:15:38 +0000 (09:15 +0000)
31 files changed:
contrib/retep/CHANGELOG
contrib/retep/Implementation
contrib/retep/README
contrib/retep/build.xml
contrib/retep/retep.jpx
contrib/retep/uk/org/retep/dtu/DCollection.java [new file with mode: 0644]
contrib/retep/uk/org/retep/dtu/DConstants.java [new file with mode: 0644]
contrib/retep/uk/org/retep/dtu/DElement.java [new file with mode: 0644]
contrib/retep/uk/org/retep/dtu/DEnvironment.java [new file with mode: 0644]
contrib/retep/uk/org/retep/dtu/DModule.java [new file with mode: 0644]
contrib/retep/uk/org/retep/dtu/DModuleXML.java [new file with mode: 0644]
contrib/retep/uk/org/retep/dtu/DNode.java [new file with mode: 0644]
contrib/retep/uk/org/retep/dtu/DProcessor.java [new file with mode: 0644]
contrib/retep/uk/org/retep/dtu/DTransform.java [new file with mode: 0644]
contrib/retep/uk/org/retep/tools.properties [new file with mode: 0644]
contrib/retep/uk/org/retep/tools/Tool.java [new file with mode: 0644]
contrib/retep/uk/org/retep/util/ExceptionDialog.java [new file with mode: 0644]
contrib/retep/uk/org/retep/util/Globals.java [new file with mode: 0644]
contrib/retep/uk/org/retep/util/Logger.java [new file with mode: 0644]
contrib/retep/uk/org/retep/util/Main.java [new file with mode: 0644]
contrib/retep/uk/org/retep/util/StandaloneApp.java [new file with mode: 0644]
contrib/retep/uk/org/retep/util/hba/Editor.java [new file with mode: 0644]
contrib/retep/uk/org/retep/util/hba/Main.java [new file with mode: 0644]
contrib/retep/uk/org/retep/util/hba/Record.java [new file with mode: 0644]
contrib/retep/uk/org/retep/util/misc/IPAddress.java [new file with mode: 0644]
contrib/retep/uk/org/retep/util/misc/PropertiesIO.java [new file with mode: 0644]
contrib/retep/uk/org/retep/util/misc/WStringTokenizer.java [new file with mode: 0644]
contrib/retep/uk/org/retep/util/models/HBATableModel.java [new file with mode: 0644]
contrib/retep/uk/org/retep/util/models/PropertiesTableModel.java [new file with mode: 0644]
contrib/retep/uk/org/retep/util/proped/Main.java [new file with mode: 0644]
contrib/retep/uk/org/retep/util/proped/PropertyEditor.java [new file with mode: 0644]

index 59f7c335cb9b5353e6db97725ba2d1bb51a5fbff..188c40129e20cfcd2c13d9f2f3ed688e2b04d997 100644 (file)
@@ -1,4 +1,7 @@
+Fri Mar 02 16:08:00 GMT 2001 peter@retep.org.uk
+    - Started importing in the rest of the retep tools.
+
 Tue Jan 23 10:19:00 GMT 2001 peter@retep.org.uk
-        - Finished the XML Export classes
+    - Finished the XML Export classes
    - First of the test data suite now in CVS.
 
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b3125acf0edeb2f94639df1aea407be2d77d6928 100644 (file)
@@ -0,0 +1,116 @@
+Retep Tools Implementation
+--------------------------
+
+
+The tools are designed to be put into a single jar file, but each one is
+executable either individually or part of one single application.
+
+To run the big application, you can either:
+
+  java -jar retepTools.jar
+
+or with the retepTools.jar in the classpath run:
+
+  java uk.org.retep.tools.Main
+
+Windows users: For you you can also double click the retepTools.jar as windows
+will automatically run javac for you.
+
+To run the individual tools, you must have the .jar file in your classpath and
+then run the relevant Main class.
+
+Tool                          Type        Class
+------------------------------------------------------------------------------
+pg_hba.conf Editor/repairer   Editor      uk.org.retep.util.hba.Main
+Properties Editor             Editor      uk.org.retep.util.proped.Main
+
+
+Layout of the classes
+---------------------
+
+Simply, tools that work on property files (Java properties, resource files,
+configuration settings - pg_hba.conf for example) go under uk.org.retep.util in
+their own package. Other utility classes (like PropertyIO) go in to the
+uk.org.retep.util.misc package except for certain ones where they are related.
+
+ie: TableModels. In swing you have JTable which uses a TableModel to display
+(and possibly update) some data. These go under uk.org.retep.util.models where
+you will find PropertiesTableModel for example. This one allows a Properties
+object to be displayed & updated.
+
+Come core classes like Logger, ExceptionDialog etc go into the main
+uk.org.retep.util package.
+
+Directory/Package                   Contents
+------------------------------------------------------------------------------
+uk.org.retep                        Home of the tools.properties file
+uk.org.retep.tools                  The main all-in-one application
+uk.org.retep.dtu                    The Data Transform Unit
+uk.org.retep.util                   Core utility classes
+uk.org.retep.util.hba               pg_hba.conf editor/repairer
+uk.org.retep.util.misc              Misc utility classes
+uk.org.retep.util.models            Swing table models
+uk.org.retep.util.proped            Property Editor
+uk.org.retep.util.xml.core          Basic XML Factory
+uk.org.retep.util.xml.jdbc          JDBC/XML interface
+uk.org.retep.util.xml.parser        Simple SAX parser
+
+Structure of a tool
+-------------------
+
+Each tool has at least 2 base classes, and an entry in the tools.properties
+file. For this example, I'll show you the Properties Editor:
+
+Base package      uk.org.retep.util.proped
+Main tool class   uk.org.retep.util.proped.PropertyEditor
+Standalone class  uk.org.retep.util.proped.Main
+
+The main tool class is the entry point used by the main application. Because
+they are used in a GUI, this class must extend javax.swing.JComponent and
+implement the uk.org.retep.tools.Tool interface. (NB: You will find I always
+use JPanel, but JComponent is used here so that any swing class can be used
+you are not limited to JPanel.)
+
+The standalone class is a basic static class that implements the main method.
+It should extend the uk.org.retep.misc.StandaloneApp class and be written along
+the lines of the following example:
+
+      import uk.org.retep.util.StandaloneApp;
+      import javax.swing.JComponent;
+
+      public class Main extends StandaloneApp
+      {
+        public Main(String[] args)
+        throws Exception
+        {
+          super(args);
+        }
+
+        public JComponent init()
+        throws Exception
+        {
+          // Your initialisation here. In this case the PropertyEditor
+          PropertyEditor panel = new PropertyEditor();
+
+          // do stuff here, ie load a file if supplied
+
+          // return the tool
+          return panel;
+        }
+
+        public static void main(String[] args)
+        throws Exception
+        {
+          Main main = new Main(args);
+          main.pack();
+          main.setVisible(true);
+        }
+      }
+
+you will find a template in the uk.org.retep.util.Main class. Simply copy this
+classes source, as it gives you the basic stub. Just add your own implementation
+if init() like the one above. Look at the full Main class for the
+PropertiesEditor to see how to get at the command line args.
+
+By convention, the standalone class is named Main.
+
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5355c9d99f3c00bb51861e427128f16d24f5d6e4 100644 (file)
@@ -0,0 +1,35 @@
+Before you ask what retepTools are, they are my personal suite of utilities.
+About 90% of them are JDBC related (either they use JDBC, or I use them in
+developing the JDBC driver).
+
+Now, because of various reasons I won't go into now, in January 2001 I decided
+to release the entire lot to the public. I could have used something like
+SourceForge, but as they are mainly JDBC related I thought here is the best
+place.
+
+Now all (bar retepPDF, see end-note) will over the next few months be going
+into the /contrib/retep directory. They range from simple XML Inport/Export
+classes to entire sub-systems that can be plugged into applications.
+
+All this lot were never released, so I'm placing them under PostgreSQL's
+licence.
+
+Please refer to Implementation for details of what package does what.
+
+It all requires Java2SE (JDK1.2) as a minimum. I do have some plans for some
+EJB tools later, so those will need Java2EE, but not yet ;-)
+
+Peter Mount
+peter@retep.org.uk
+March 2 2001
+
+retepPDF: This is not included for two reasons:
+
+1: It's big and not really related in any way to PostgreSQL
+2: More importantly, I (may be foolishly) released it some 3 years ago under
+   the LGPL. As a few people have added to it, it's not really possible to
+   change the licence, and I don't want to polute PostgreSQL's source tree ;-)
+
+retepGraph: This was an old graphics library. It's been obsolete for 3 years
+now, so it's not going in.
+
index 019903bc798efb25a1fbe730fd217dd6031ef27a..78b45e3bf45e40230de9271d5bd80eafbb82332b 100644 (file)
@@ -2,7 +2,7 @@
 
   build file to build the donated retep tools packages
 
-  $Id: build.xml,v 1.2 2001/01/23 10:22:18 peter Exp $
+  $Id: build.xml,v 1.3 2001/03/05 09:15:35 peter Exp $
 
 -->
 
   </target>
 
   <!-- Builds the XML Tools -->
-  <target name="xml" depends="checks,prepare" if="xml">
+  <target name="compile" depends="checks,prepare">
     <javac srcdir="${src}" destdir="${dest}">
-      <include name="${package}/xml/**" />
+      <include name="${package}/**" />
     </javac>
   </target>
 
   <!-- Builds the various jar files -->
-  <target name="jar" depends="xml">
+  <target name="jar" depends="compile">
     <jar jarfile="${jars}/retepTools.jar" basedir="${dest}">
-      <include name="${package}/xml/**" if="xml" />
+      <include name="${package}/**" />
     </jar>
   </target>
 
index 640df105046bfda860cb46f3e36d5229b2864e6a..746137dd3066736bf2c347bb5495dbf2e0985c9b 100644 (file)
@@ -4,6 +4,12 @@
 <project>\r
   <property category="idl" name="ProcessIDL" value="false" />\r
   <property category="runtime.0" name="RunnableType" value="com.borland.jbuilder.runtime.ApplicationRunner" />\r
+  <property category="runtime.0" name="application.class" value="uk.org.retep.util.hba.Main" />\r
+  <property category="runtime.0" name="application.parameters" value="-d2 pg_hba.conf" />\r
+  <property category="runtime.0" name="appserver.ejbJarsSaved" value="1" />\r
+  <property category="runtime.0" name="appserver.parameters" value="-jts -jns -jss -jdb" />\r
+  <property category="runtime.0" name="appserver.servername" value="ejbcontainer" />\r
+  <property category="runtime.0" name="appserver.vmparameters" value="" />\r
   <property category="runtime.0" name="jsprunner.docbase" value="." />\r
   <property category="runtime.0" name="jsprunner.jspfile" value="E%|/docs/java/xml/example6" />\r
   <property category="sys" name="AuthorLabel" value="@author" />\r
@@ -15,7 +21,7 @@
   <property category="sys" name="CompanyLabel" value="Company:" />\r
   <property category="sys" name="Copyright" value="Copyright (c) 2001" />\r
   <property category="sys" name="CopyrightLabel" value="Copyright:" />\r
-  <property category="sys" name="DefaultPackage" value="uk.org.retep.xml.jdbc" />\r
+  <property category="sys" name="DefaultPackage" value="uk.org.retep.util.misc" />\r
   <property category="sys" name="Description" value="" />\r
   <property category="sys" name="DescriptionLabel" value="Description:" />\r
   <property category="sys" name="DocPath" value="doc" />\r
@@ -25,7 +31,7 @@
   <property category="sys" name="InstanceVisibility" value="0" />\r
   <property category="sys" name="JDK" value="java 1.3.0-C" />\r
   <property category="sys" name="LastTag" value="0" />\r
-  <property category="sys" name="Libraries" value="JAXP;Oracle JDBC" />\r
+  <property category="sys" name="Libraries" value="JAXP;Oracle JDBC;JDK1.3 JRE" />\r
   <property category="sys" name="MakeStable" value="0" />\r
   <property category="sys" name="OutPath" value="build" />\r
   <property category="sys" name="SourcePath" value="." />\r
   <property category="sys" name="Version" value="1.0" />\r
   <property category="sys" name="VersionLabel" value="@version" />\r
   <property category="sys" name="WorkingDirectory" value="." />\r
-  <node type="Folder" name="core" />\r
+  <node type="Package" name="uk.org.retep.app" />\r
+  <node type="Package" name="uk.org.retep.dtu" />\r
+  <node type="Package" name="uk.org.retep.tools" />\r
+  <node type="Package" name="uk.org.retep.util" />\r
   <node type="Package" name="uk.org.retep.xml.core" />\r
   <node type="Package" name="uk.org.retep.xml.jdbc" />\r
   <node type="Package" name="uk.org.retep.xml.parser" />\r
   <file path="build.xml" />\r
   <file path="CHANGELOG" />\r
   <file path="Implementation" />\r
+  <file path="uk/org/retep/util/models/PropertiesTableModel.java" />\r
   <file path="README" />\r
 </project>\r
 \r
diff --git a/contrib/retep/uk/org/retep/dtu/DCollection.java b/contrib/retep/uk/org/retep/dtu/DCollection.java
new file mode 100644 (file)
index 0000000..e97fc06
--- /dev/null
@@ -0,0 +1,228 @@
+package uk.org.retep.dtu;
+
+import uk.org.retep.xml.core.XMLFactory;
+import uk.org.retep.xml.core.XMLFactoryException;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Iterator;
+
+public class DCollection implements Collection
+{
+  protected int num,max,inc;
+
+  protected DElement elements[];
+
+  public DCollection()
+  {
+    this(10);
+  }
+
+  public DCollection(int aIncrement)
+  {
+    num=0;
+    max=0;
+    inc=aIncrement;
+    elements=null;
+  }
+
+  protected void resize()
+  {
+    if(num>=max) {
+      max+=inc;
+      DElement n[] = new DElement[max];
+      if(elements!=null) {
+        System.arraycopy(elements,0,n,0,elements.length);
+      }
+      elements=n;
+    }
+  }
+
+  public int size()
+  {
+    return num;
+  }
+
+  public boolean isEmpty()
+  {
+    return (num==0);
+  }
+
+  /**
+   * Checks the list using it's XML id.
+   */
+  public synchronized boolean contains(Object parm1)
+  {
+    if(parm1 instanceof DElement) {
+      DElement e = (DElement) parm1;
+      int ei = e.getID();
+
+      // out of range?
+      if(ei<0 || ei>=num)
+        return false;
+
+      return elements[ei].equals(e);
+    }
+
+    return false;
+  }
+
+  public Iterator iterator()
+  {
+    return new iterator(this);
+  }
+
+  /**
+   * Inner class to implement an Iterator
+   */
+  protected class iterator implements Iterator
+  {
+    protected DCollection c;
+    protected int i;
+
+    public iterator(DCollection aCollection)
+    {
+      c=aCollection;
+      i=0;
+    }
+
+    public boolean hasNext()
+    {
+      return i<c.size();
+    }
+
+    public Object next() {
+      return c.getElement(i++);
+    }
+
+    public void remove() {
+    }
+  }
+
+  public synchronized Object[] toArray()
+  {
+    Object o[] = new Object[num];
+    System.arraycopy(elements,0,o,0,num);
+    return o;
+  }
+
+  public Object[] toArray(Object[] parm1)
+  {
+    /**@todo: Implement this java.util.Collection method*/
+    throw new java.lang.UnsupportedOperationException("Method toArray() not yet implemented.");
+  }
+
+  /**
+   * Adds a node to the Collection, and sets it's ID to its position in the Collection
+   */
+  public synchronized boolean add(Object parm1)
+  {
+    if(parm1 instanceof DElement) {
+      DElement e = (DElement) parm1;
+
+      // Do nothing if it's already in a Collection
+      if(e.getID()>-1) {
+        return false;
+      }
+
+      // Add to the Collection
+      resize();
+      e.setID(num);
+      elements[num++] = e;
+      return true;
+    }
+    return false;
+  }
+
+  public synchronized boolean remove(Object parm1)
+  {
+    if(parm1 instanceof DElement) {
+      DElement e = (DElement) parm1;
+      int ei = e.getID();
+      if(ei<0 || ei>=num)
+        return false;
+
+      // Mark the node as parentless
+      e.setID(-1);
+
+      // Now remove from the array by moving latter nodes, fixing their ids
+      // in the process
+      for(int j=ei,k=ei+1;k<num;j++,k++) {
+        elements[j]=elements[k];
+        elements[j].setID(j);
+      }
+      num--;
+      return true;
+    }
+
+    return false;
+  }
+
+  public boolean containsAll(Collection parm1)
+  {
+    /**@todo: Implement this java.util.Collection method*/
+    throw new java.lang.UnsupportedOperationException("Method containsAll() not yet implemented.");
+  }
+
+  public boolean addAll(Collection parm1)
+  {
+    /**@todo: Implement this java.util.Collection method*/
+    throw new java.lang.UnsupportedOperationException("Method addAll() not yet implemented.");
+  }
+
+  public boolean removeAll(Collection parm1)
+  {
+    /**@todo: Implement this java.util.Collection method*/
+    throw new java.lang.UnsupportedOperationException("Method removeAll() not yet implemented.");
+  }
+
+  public boolean retainAll(Collection parm1)
+  {
+    /**@todo: Implement this java.util.Collection method*/
+    throw new java.lang.UnsupportedOperationException("Method retainAll() not yet implemented.");
+  }
+
+  public synchronized void clear()
+  {
+    // Mark each node as parentless
+    for(int i=0;i<num;i++) {
+      elements[i].setID(-1);
+    }
+
+    // dispose the array
+    num=0;
+    max=0;
+    elements=null;
+  }
+
+  /**
+   * Returns the element with supplied id.
+   * @return element or null
+   */
+  public synchronized DElement getElement(int id)
+  {
+    if(id<0 || id>=num)
+      return null;
+
+    return elements[id];
+  }
+
+  /**
+   * Repairs the collection, ensuring all id's are correct
+   */
+  public synchronized void repair()
+  {
+    for(int i=0;i<num;i++) {
+      elements[i].setID(i);
+    }
+  }
+
+  public synchronized void saveXML(XMLFactory aFactory)
+  throws IOException, XMLFactoryException
+  {
+    for(int i=0;i<num;i++) {
+      elements[i].saveXML(aFactory);
+    }
+  }
+
+}
\ No newline at end of file
diff --git a/contrib/retep/uk/org/retep/dtu/DConstants.java b/contrib/retep/uk/org/retep/dtu/DConstants.java
new file mode 100644 (file)
index 0000000..fb825d7
--- /dev/null
@@ -0,0 +1,43 @@
+package uk.org.retep.dtu;
+
+public class DConstants
+{
+  /**
+   * A global version number
+   */
+  public static final String XML_VERSION_ID = "V7.1-2001-02-26";
+
+  /**
+   * XML Tag names
+   */
+  public static final String XML_DISPLAYNAME= "DISPLAYNAME";
+  public static final String XML_FROM       = "FROM";
+  public static final String XML_ID         = "ID";
+  public static final String XML_MODULE     = "MODULE";
+  public static final String XML_NODE       = "NODE";
+  public static final String XML_TO         = "TO";
+  public static final String XML_TRANSFORM  = "TRANSFORM";
+  public static final String XML_TYPE       = "TYPE";
+  public static final String XML_VERSION    = "VERSION";
+  public static final String XML_X          = "X";
+  public static final String XML_Y          = "Y";
+
+  public static final int NOP       = 0;      // No operation or always run transform
+  public static final int SUCCESS   = 1;      // Run transform only if DNode.OK
+  public static final int ERROR     = 2;      // Run transform only if DNode.ERROR
+
+  /**
+   * Node types 20-39 reserved for Transformation types
+   */
+  public static final int TRANSFORMBASE = 20;
+
+  /**
+   * Node types 20-99 reserved for Internal Node implementations
+   */
+  public static final int INTERNALBASE = 50;
+
+  /**
+   * Node types 100+ are for user extensions
+   */
+  public static final int USERBASE  = 100;
+}
\ No newline at end of file
diff --git a/contrib/retep/uk/org/retep/dtu/DElement.java b/contrib/retep/uk/org/retep/dtu/DElement.java
new file mode 100644 (file)
index 0000000..73fa7e8
--- /dev/null
@@ -0,0 +1,31 @@
+package uk.org.retep.dtu;
+
+import uk.org.retep.xml.core.XMLFactory;
+import uk.org.retep.xml.core.XMLFactoryException;
+
+import java.io.IOException;
+
+public interface DElement
+{
+  /**
+   * Fetch the unique ID of this Element
+   */
+  public int getID();
+
+  /**
+   * Sets the unique id - normally set by DCollection
+   */
+  public void setID(int id);
+
+  /**
+   * @return the type of the Element
+   */
+  public int getType();
+
+  /**
+   * Set's the element type
+   */
+  public void setType(int aType);
+
+  public void saveXML(XMLFactory aFactory) throws IOException, XMLFactoryException;
+}
\ No newline at end of file
diff --git a/contrib/retep/uk/org/retep/dtu/DEnvironment.java b/contrib/retep/uk/org/retep/dtu/DEnvironment.java
new file mode 100644 (file)
index 0000000..2efcfe8
--- /dev/null
@@ -0,0 +1,30 @@
+package uk.org.retep.dtu;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+
+public class DEnvironment
+{
+  protected HashMap dsrc;
+
+  public DEnvironment()
+  {
+    dsrc=new HashMap();
+  }
+
+  public void addDataSource(String aKey,Object aObject)
+  {
+    dsrc.put(aKey,aObject);
+  }
+
+  public Object getDataSource(String aKey)
+  {
+    return dsrc.get(aKey);
+  }
+
+  public Iterator getDataSources()
+  {
+    return dsrc.values().iterator();
+  }
+}
\ No newline at end of file
diff --git a/contrib/retep/uk/org/retep/dtu/DModule.java b/contrib/retep/uk/org/retep/dtu/DModule.java
new file mode 100644 (file)
index 0000000..21d037f
--- /dev/null
@@ -0,0 +1,97 @@
+package uk.org.retep.dtu;
+
+import uk.org.retep.xml.core.XMLFactory;
+import uk.org.retep.xml.core.XMLFactoryException;
+import uk.org.retep.xml.parser.TagListener;
+import uk.org.retep.util.Logger;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Iterator;
+
+/**
+ * DModule represents a programatic module of steps used within the DTU
+ */
+public class DModule implements Serializable
+{
+  // The nodes and transitions between them
+  protected DCollection nodes;
+
+  protected String displayName;
+
+  public static final String DEFAULT_DISPLAYNAME = "unnamed module";
+
+  public DModule()
+  {
+    nodes=new DCollection();
+    displayName=DEFAULT_DISPLAYNAME;
+    Logger.log(Logger.DEBUG,"new DModule",this);
+  }
+
+  // Expensive!
+  public DNode getNode(int id)
+  {
+    return (DNode) nodes.getElement(id);
+  }
+
+  public DNode addNode(DNode aNode)
+  {
+    Logger.log(Logger.DEBUG,"DModule.addNode",aNode);
+    nodes.add(aNode);
+    return aNode;
+  }
+
+  public void removeNode(DNode aNode)
+  {
+    Logger.log(Logger.DEBUG,"DModule.removeNode",aNode);
+    nodes.remove(aNode);
+  }
+
+  public void clear()
+  {
+    Logger.log(Logger.DEBUG,"DModule.clear",this);
+    nodes.clear();
+  }
+
+  public void setDisplayName(String aName)
+  {
+    Logger.log(Logger.DEBUG,"DModule.setDisplayName",aName);
+    displayName = aName;
+  }
+
+  public String getDisplayName()
+  {
+    return displayName;
+  }
+
+  public Iterator iterator()
+  {
+    return nodes.iterator();
+  }
+
+  /**
+   * Writes an XML representation of this module to an XMLFactory. The caller
+   * must close the factory after use!
+   */
+  public synchronized void saveXML(XMLFactory aFactory)
+  throws IOException, XMLFactoryException
+  {
+    Logger.log(Logger.DEBUG,"DModule.saveXML start",this);
+    Iterator it;
+
+    aFactory.startTag(DConstants.XML_MODULE);
+    aFactory.addAttribute(DConstants.XML_DISPLAYNAME,displayName);
+    aFactory.addAttribute(DConstants.XML_VERSION,DConstants.XML_VERSION_ID);
+
+    // The nodes
+    nodes.saveXML(aFactory);
+
+    // The transforms
+    //trans.saveXML(aFactory);
+
+    aFactory.endTag(); // MODULE
+    Logger.log(Logger.DEBUG,"DModule.saveXML end",this);
+  }
+
+}
\ No newline at end of file
diff --git a/contrib/retep/uk/org/retep/dtu/DModuleXML.java b/contrib/retep/uk/org/retep/dtu/DModuleXML.java
new file mode 100644 (file)
index 0000000..31552cd
--- /dev/null
@@ -0,0 +1,233 @@
+package uk.org.retep.dtu;
+
+import uk.org.retep.xml.core.XMLFactory;
+import uk.org.retep.xml.core.XMLFactoryException;
+import uk.org.retep.xml.parser.TagHandler;
+import uk.org.retep.xml.parser.TagListener;
+import uk.org.retep.util.Logger;
+
+import java.io.CharArrayWriter;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.ArrayList;
+
+import org.xml.sax.InputSource;
+import org.xml.sax.Parser;
+import org.xml.sax.SAXException;
+import javax.xml.parsers.ParserConfigurationException;
+
+public class DModuleXML implements TagListener
+{
+  protected TagHandler handler;
+
+  protected DModule module = null;
+  protected DNode node = null;
+  protected DTransform trans = null;
+
+  protected ArrayList txmap;
+
+  public DModuleXML()
+  {
+    handler = new TagHandler();
+    handler.addTagListener(this);
+
+    txmap = new ArrayList();
+
+    Logger.log(Logger.DEBUG,"DModuleXML initialised");
+  }
+
+  public TagHandler getTagHandler()
+  {
+    return handler;
+  }
+
+  /**
+   * Used to optimise the switch handling in tagStart.
+   *
+   * The values of each T_* constant must match the corresponding element no
+   * in the tags static array.
+   */
+  private static final int T_DEFAULT=-1;
+  private static final int T_MODULE =0;
+  private static final int T_NODE   =1;
+  private static final int T_TRANS  =2;
+  private static final String tags[] = {
+    DConstants.XML_MODULE,
+    DConstants.XML_NODE,
+    DConstants.XML_TRANSFORM
+  };
+
+  /**
+   * This is called when a tag has just been started.
+   * <p><b>NB:</b> args is volatile, so if you use it beyond the lifetime of
+   * this call, then you must make a copy of the HashMap (and not use simply
+   * store this HashMap).
+   * @param level The number of tags above this
+   * @param tag The tag name
+   * @param args A HashMap of any arguments
+   */
+  public void tagStart(int level,String tag,HashMap args)
+  {
+    Logger.log(Logger.DEBUG,"DModuleXML.tagStart",tag);
+
+    // Prefetch some common attributes
+    String sType = (String) args.get(DConstants.XML_TYPE);
+    String sX = (String) args.get(DConstants.XML_X);
+    String sY = (String) args.get(DConstants.XML_Y);
+
+    int type=-1,x=-1,y=-1;
+
+    if(sType!=null) {
+      type = Integer.parseInt(sType);
+    }
+
+    if(sX!=null) {
+      y = Integer.parseInt(sX);
+    }
+
+    if(sY!=null) {
+      x = Integer.parseInt(sY);
+    }
+
+    // Match the tag against the tags array (used for switch() )
+    int tagID=T_DEFAULT;
+    for(int i=0;i<tags.length;i++) {
+      if(tag.equals(tags[i])) {
+        tagID=i;
+      }
+    }
+
+    switch(tagID)
+      {
+          // The main module tag
+        case T_MODULE:
+          module = new DModule();
+
+          String sDisplayName = (String) args.get(DConstants.XML_DISPLAYNAME);
+          if(sDisplayName!=null) {
+            module.setDisplayName(sDisplayName);
+          }
+          break;
+
+          // Basic nodes
+        case T_NODE:
+          node = new DNode();
+          node.setType(type);
+          module.addNode(node);
+          break;
+
+          // Basic transforms
+        case T_TRANS:
+          trans = new DTransform();
+          trans.setType(type);
+
+          // When finished we fix the transforms
+          int to = Integer.parseInt((String) args.get(DConstants.XML_TO));
+          txmap.add(new tx(node,trans,to));
+
+          break;
+
+        default:
+          // ignore unknown tags for now
+          break;
+      }
+  }
+
+  protected class tx
+  {
+    public DNode node;
+    public DTransform transform;
+    public int toID;
+
+    public tx(DNode aNode,DTransform aTransform,int aID)
+    {
+      node=aNode;
+      transform=aTransform;
+      toID=aID;
+    }
+  }
+
+  /**
+   * This method is called by ContHandler to process a tag once it has been
+   * fully processed.
+   * <p><b>NB:</b> content is volatile, so you must copy its contents if you use
+   * it beyond the lifetime of this call.
+   * @param content CharArrayWriter containing the content of the tag.
+   */
+  public void tagContent(CharArrayWriter content)
+  {
+    // Ignore
+  }
+
+  public void fixTransforms()
+  {
+    DNode     to;
+    Iterator  it = txmap.iterator();
+
+    while(it.hasNext()) {
+      tx x = (tx) it.next();
+
+      //Logger.log(Logger.DEBUG,"Fixing transform "+x.toID,x.transform,Integer.toString(x.node.getID()),Integer.toString(module.getNode(x.toID).getID()));
+      to    = module.getNode(x.toID);
+
+      x.transform.setFrom(x.node);
+      x.transform.setTo(to);
+      //to.setFrom(x.transform);
+    }
+
+  }
+
+  /**
+   * Parse an InputSource and return the contained module.
+   * @return DModule loaded, null if the xml file does not contain a module.
+   */
+  public DModule parse(InputSource is)
+  throws IOException,SAXException
+  {
+    getTagHandler().parse(is);
+    fixTransforms();
+    return module;
+  }
+
+  /**
+   * Parse an uri and return the contained module.
+   * @return DModule loaded, null if the xml file does not contain a module.
+   */
+  public DModule parse(String uri)
+  throws IOException,SAXException
+  {
+    getTagHandler().parse(uri);
+    fixTransforms();
+    return module;
+  }
+
+  /**
+   * Debug test - read xml from one file and save to another.
+   */
+  public static void main(String args[]) throws Exception
+  {
+    if(args.length!=2) {
+      System.err.println("Syntax: java DModuleXML in-file out-file");
+      System.exit(1);
+    }
+
+    Logger.setLevel(Logger.DEBUG);
+
+    Logger.log(Logger.INFO,"DModuleXML Read test1.xml");
+    DModuleXML dm = new DModuleXML();
+    DModule module = dm.parse(new InputSource(new FileInputStream(args[0])));
+
+    Logger.log(Logger.INFO,"Parse complete");
+
+    Logger.log(Logger.INFO,"DModuleXML Write XML");
+    FileWriter fw = new FileWriter(args[1]);
+    module.saveXML(new XMLFactory(fw));
+    fw.close();
+    Logger.log(Logger.INFO,"Write complete");
+
+    DProcessor.run(module);
+  }
+}
\ No newline at end of file
diff --git a/contrib/retep/uk/org/retep/dtu/DNode.java b/contrib/retep/uk/org/retep/dtu/DNode.java
new file mode 100644 (file)
index 0000000..7a83217
--- /dev/null
@@ -0,0 +1,233 @@
+package uk.org.retep.dtu;
+
+import uk.org.retep.util.Logger;
+import uk.org.retep.xml.core.XMLFactory;
+import uk.org.retep.xml.core.XMLFactoryException;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.Iterator;
+
+/**
+ * This is the base class for all nodes.
+ */
+public class DNode implements DElement, Serializable
+{
+  // The id of this node
+  protected int id;
+
+  // The type of this node
+  protected int type;
+
+  protected int x,y;
+
+  public static final int OK      = 0;  // Node last ran fine
+  public static final int ERROR   = 1;  // Node failed on last run
+
+  /**
+   * This type of node does nothing
+   */
+  public static int NOP   = 0; // No action
+
+  public DNode()
+  {
+    this(NOP);
+  }
+
+  public DNode(int aType)
+  {
+    id=-1;
+    type=aType;
+
+    // Init the transform linkage
+    mf=mt=5;
+    nf=nt=0;
+    fn = new DTransform[mf];
+    tn = new DTransform[mt];
+
+    Logger.log(Logger.DEBUG,"new DNode");
+  }
+
+  public int getID()
+  {
+    return id;
+  }
+
+  public void setID(int aID)
+  {
+    id=aID;
+    Logger.log(Logger.DEBUG,"DNode.setID",aID);
+  }
+
+  public int getType()
+  {
+    return type;
+  }
+
+  public void setType(int aType)
+  {
+    type=aType;
+    Logger.log(Logger.DEBUG,"DNode.setType",aType);
+  }
+
+  /**
+   */
+  public void saveXML(XMLFactory aFactory)
+  throws IOException, XMLFactoryException
+  {
+    Logger.log(Logger.DEBUG,"DNode.saveXML start",this);
+    Iterator it;
+
+    aFactory.startTag(DConstants.XML_NODE);
+    aFactory.addAttribute(DConstants.XML_ID,new Integer(getID()));
+    aFactory.addAttribute(DConstants.XML_TYPE,new Integer(getType()));
+
+    // used for display only
+    aFactory.addAttribute(DConstants.XML_X,new Integer(getX()));
+    aFactory.addAttribute(DConstants.XML_Y,new Integer(getY()));
+
+    // Save the transforms here (only the from list required)
+    for(int i=0;i<nf;i++) {
+      fn[i].saveXML(aFactory);
+    }
+
+    aFactory.endTag(); // NODE
+    Logger.log(Logger.DEBUG,"DNode.saveXML finish",this);
+  }
+
+  public void setPosition(int aX,int aY)
+  {
+    x=aX;
+    y=aY;
+  }
+
+  public int getX()
+  {
+    return x;
+  }
+
+  public int getY()
+  {
+    return y;
+  }
+
+  public void setX(int aX)
+  {
+    x=aX;
+  }
+
+  public void setY(int aY)
+  {
+    y=aY;
+  }
+
+  /**
+   * This must be overidden to do something
+   * @return Return status
+   */
+  public int run(DEnvironment env)
+  {
+    return OK;
+  }
+
+  /**
+   * Node Transforms...
+   */
+  protected int nf,mf,nt,mt;
+  protected DTransform fn[],tn[];
+
+  /**
+   * Executes the transform
+   */
+  public DTransform getTransform(int aID)
+  {
+    return tn[aID];
+  }
+
+  /**
+   * @return number of transforms
+   */
+  public int getFromTransforms()
+  {
+    return nf;
+  }
+
+  /**
+   * @return number of transforms
+   */
+  public int getToTransforms()
+  {
+    return nt;
+  }
+
+  /**
+   * Adds a transform to this node (called by DTransform)
+   */
+  protected synchronized void setFrom(DTransform aTransform)
+  {
+    for(int i=0;i<nf;i++) {
+      if(fn[i].equals(aTransform)) {
+        return;
+      }
+    }
+    if(nf>=mf) {
+      mf+=5;
+      DTransform nn[] = new DTransform[mf];
+      System.arraycopy(fn,0,nn,0,nf);
+      fn=nn;
+    }
+    fn[nf++]=aTransform;
+  }
+
+  /**
+   * Adds a transform to this node (called by DTransform)
+   */
+  protected synchronized void setTo(DTransform aTransform)
+  {
+    for(int i=0;i<nt;i++) {
+      if(tn[i].equals(aTransform)) {
+        return;
+      }
+    }
+    if(nt>=mt) {
+      mt+=5;
+      DTransform nn[] = new DTransform[mt];
+      System.arraycopy(tn,0,nn,0,nt);
+      tn=nn;
+    }
+    tn[nt++]=aTransform;
+  }
+
+  /**
+   * Removes a transform (called by DTransform)
+   */
+  protected synchronized void removeFrom(DTransform aTransform)
+  {
+    for(int i=0;i<nf;i++) {
+      if(tn[i].equals(aTransform)) {
+        for(int j=i+1;j<nf;j++,i++) {
+          fn[i]=fn[j];
+        }
+        nf--;
+        return;
+      }
+    }
+  }
+
+  /**
+   * Removes a transform (called by DTransform)
+   */
+  protected synchronized void removeTo(DTransform aTransform)
+  {
+    for(int i=0;i<nt;i++) {
+      if(tn[i].equals(aTransform)) {
+        for(int j=i+1;j<nt;j++,i++) {
+          tn[i]=tn[j];
+        }
+        nt--;
+        return;
+      }
+    }
+  }
+
+}
diff --git a/contrib/retep/uk/org/retep/dtu/DProcessor.java b/contrib/retep/uk/org/retep/dtu/DProcessor.java
new file mode 100644 (file)
index 0000000..7745a18
--- /dev/null
@@ -0,0 +1,191 @@
+package uk.org.retep.dtu;
+
+import uk.org.retep.util.Logger;
+
+import java.util.Iterator;
+
+/**
+ * This class processes a Module. It's implemented as a Thread and there can
+ * be many threads running on a single module
+ */
+public class DProcessor
+{
+  /**
+   * This starts a module
+   */
+  public static DProcessor run(DModule aModule) {
+    // 3600000 is 1 hour in milliseconds
+    return run(aModule,3600000);
+  }
+
+  /**
+   * This starts a module
+   */
+  public static DProcessor run(DModule aModule,long timeout) {
+    return new DProcessor(aModule,timeout);
+  }
+
+  protected DProcessor(DModule aModule,long timeout) {
+    ThreadGroup group = new ThreadGroup(aModule.getDisplayName()+" DProcessor");
+
+    // Setup the environment
+    DEnvironment env = new DEnvironment();
+
+    // loop for any nodes without a transform pointing _to_ it.
+    Iterator it = aModule.iterator();
+    while(it.hasNext()) {
+      DNode node = (DNode) it.next();
+
+      // Only start if we have no predecessor
+      if(node.getFromTransforms()==0) {
+        proc proc = new proc(group,aModule,node,env);
+        proc.start();
+      }
+    }
+
+    // Now wait until all the threads have finished
+    boolean running=true;
+    try {
+      int cnt=1; // must loop at least once!
+
+      while(cnt>0) {
+        int numThreads = group.activeCount();
+        Thread threads[] = new Thread[numThreads];
+        cnt = group.enumerate(threads,false);
+
+        //Logger.log(Logger.DEBUG,"Waiting on threads",cnt);
+        while(cnt>0) {
+          //Logger.log(Logger.DEBUG,"Waiting on thread",cnt);
+          threads[--cnt].join(timeout);
+        }
+
+        Logger.log(Logger.DEBUG,"All threads appear to have died, retesting");
+      }
+    } catch(InterruptedException ie) {
+      Logger.log(Logger.ERROR,"DProcessor, exception caught while waiting for threads to die",ie);
+    }
+
+    // finally close any open datasources
+    Logger.log(Logger.DEBUG,"DProcessor cleanup");
+
+    Logger.log(Logger.DEBUG,"DProcessor finished");
+  }
+
+  class proc implements Runnable
+  {
+    protected DModule module; // The module being run
+    protected DNode   pc;     // current Program Counter
+
+    protected DEnvironment env; // Shared environment
+
+    // Used when launching new threads only
+    protected DTransform trans; // If not null, a transform to run first
+    protected int status;
+
+    protected Thread thread;
+
+    /**
+     * Start processing from DNode aNode. This is called by DProcessor at
+     * initialisation only.
+     */
+    protected proc(ThreadGroup aGroup,DModule aModule,DNode aNode,DEnvironment aEnv)
+    {
+      // aGroup will be null when forking...
+      if(aGroup==null) {
+        thread = new Thread(this);
+      } else {
+        thread = new Thread(aGroup,this);
+      }
+
+      module = aModule;
+      pc = aNode;
+      env = aEnv;
+    }
+
+    /**
+     * Start processing the DTransform aTransform from aNode (does not execute
+     * the node). This is called by this inner class itself when forking new
+     * threads.
+     */
+    protected proc(DModule aModule,DNode aNode,DEnvironment aEnv,DTransform aTransform,int aStatus)
+    {
+      this(null,aModule,aNode,aEnv);
+      trans = aTransform;
+      status = aStatus;
+    }
+
+    /**
+     * Start this thread
+     */
+    public void start()
+    {
+      thread.start();
+    }
+
+    public void run()
+    {
+      // Handle an initial transform first. It's used when a new Thread was created.
+      if(trans!=null) {
+        transform(trans,false,status);
+        trans=null;
+      }
+
+      while(pc!=null) {
+        //Logger.log(Logger.DEBUG,"running node ",pc.getID());
+
+        // Process the node
+        int status = pc.run(env);
+        //Logger.log(Logger.DEBUG,"      status ",status);
+
+        // Now the transforms. This thread continues with the first one that runs,
+        // but any others that will also run will do so in their own thread.
+        // If no transform runs (or there are none), then the thread will die.
+        int numTrans = pc.getToTransforms();
+        boolean fork=false;
+        for(int i=0;i<numTrans;i++) {
+          fork = transform(pc.getTransform(i),fork,status);
+          //Logger.log(Logger.DEBUG,"fork",fork?"true":"false");
+        }
+        //Logger.log(Logger.DEBUG,"fork",fork?"true":"false");
+        if(!fork) {
+          // No transforms ran, so we quit this thread
+          pc=null;
+        }
+
+        // This lets the other threads a chance to run
+        Thread.yield();
+      }
+    }
+
+    /**
+     * This executes a transform
+     * @param aTransform DTransform to execute
+     * @param fork true then a new process is triggered
+     * @param status The return status of the previous node
+     * @return true if the transform ran or a fork occured.
+     */
+    public boolean transform(DTransform aTransform,boolean fork,int status)
+    {
+      // Check to see if the transform will run (based on the calling nodes return
+      // status
+      if(!aTransform.willRun(status,env)) {
+        return false;
+      }
+
+      if(fork) {
+        // Create the new processor but this time we want a transform to run
+        proc proc = new proc(module,pc,env,aTransform,status);
+        return true;
+      }
+
+      if(aTransform.run(env)) {
+        pc=aTransform.getTo();
+        return true;
+      }
+
+      return false;
+    }
+
+  } // class proc
+
+}
\ No newline at end of file
diff --git a/contrib/retep/uk/org/retep/dtu/DTransform.java b/contrib/retep/uk/org/retep/dtu/DTransform.java
new file mode 100644 (file)
index 0000000..88aac19
--- /dev/null
@@ -0,0 +1,133 @@
+package uk.org.retep.dtu;
+
+import uk.org.retep.xml.core.XMLFactory;
+import uk.org.retep.xml.core.XMLFactoryException;
+
+import java.io.IOException;
+
+/**
+ * This manages the links between two nodes.
+ */
+public class DTransform
+{
+  protected DNode from,to;
+  protected int type;
+
+  public DTransform()
+  {
+    this(null,null);
+  }
+
+  public DTransform(DNode aFrom,DNode aTo)
+  {
+    from=aFrom;
+    to=aTo;
+  }
+
+  public int getType()
+  {
+    return type;
+  }
+
+  public void setType(int aType)
+  {
+    type=aType;
+  }
+
+  public void setFrom(DNode aNode)
+  {
+    if(from!=null) {
+      from.removeTo(this);
+    }
+
+    from=aNode;
+    from.setTo(this);
+  }
+
+  public DNode getFrom()
+  {
+    return from;
+  }
+
+  public void setTo(DNode aNode)
+  {
+    if(to!=null) {
+      to.removeFrom(this);
+    }
+
+    to=aNode;
+    aNode.setFrom(this);
+  }
+
+  public DNode getTo()
+  {
+    return to;
+  }
+
+  /**
+   * This ensures the minimum tag/attributes are generated.
+   * To extend, extend saveCustomXML() which is called by this method
+   * appropriately.
+   */
+  public final void saveXML(XMLFactory aFactory)
+  throws IOException, XMLFactoryException
+  {
+    // Bare minimum is the tag type, and the destination node's id
+    aFactory.startTag(DConstants.XML_TRANSFORM);
+    aFactory.addAttribute(DConstants.XML_TYPE,Integer.toString(getType()));
+    aFactory.addAttribute(DConstants.XML_TO,Integer.toString(to.getID()));
+    saveCustomXML(aFactory);
+    aFactory.endTag();
+  }
+
+  /**
+   * Custom transformations must overide this method and write direct to the
+   * supplied XMLFactory. A tag is currently open when the method is called, but
+   * is closed immediately this method exits.
+   */
+  public void saveCustomXML(XMLFactory aFactory)
+  throws IOException, XMLFactoryException
+  {
+    // Default method does nothing...
+  }
+
+  /**
+   * Checks to see if we should run based on the calling nodes status. Overide
+   * this to add this sort of checking.
+   *
+   * @param status The return status of the calling node
+   * @param env DEnvironment we are using
+   * @return true if we will run.
+   */
+  public boolean willRun(int status,DEnvironment env)
+  {
+    switch(getType())
+    {
+      // NOP is the generic link - always run
+      case DConstants.NOP:
+        return true;
+
+      // SUCCESS only runs if the previous node was OK
+      case DConstants.SUCCESS:
+        return status==DNode.OK;
+
+      case DConstants.ERROR:
+        return status==DNode.ERROR;
+
+      // Default - always run. Overide the method if you need to change this
+      default:
+        return true;
+    }
+  }
+
+  /**
+   * Overide this for a transform to do something.
+   * @param env DEnvironment we are using
+   * @return true if we actually ran. DProcessor will jump to the getTo() node if so.
+   */
+  public boolean run(DEnvironment env)
+  {
+    return true;
+  }
+
+}
\ No newline at end of file
diff --git a/contrib/retep/uk/org/retep/tools.properties b/contrib/retep/uk/org/retep/tools.properties
new file mode 100644 (file)
index 0000000..38dc668
--- /dev/null
@@ -0,0 +1,8 @@
+#Written by Retep PropertyEditor\r
+#Sat Mar 03 16:29:44 GMT+00:00 2001\r
+tool.hba=pg_hba.conf editor\r
+tool.hba.class=uk.org.retep.util.hba.Editor\r
+tool.proped.class=uk.org.retep.util.proped.PropertyEditor\r
+tool.hba.type=Misc\r
+tool.proped.type=Misc\r
+tool.proped=Properties Editor\r
diff --git a/contrib/retep/uk/org/retep/tools/Tool.java b/contrib/retep/uk/org/retep/tools/Tool.java
new file mode 100644 (file)
index 0000000..ffd19ae
--- /dev/null
@@ -0,0 +1,33 @@
+package uk.org.retep.tools;
+
+import javax.swing.JMenuBar;
+
+/**
+ * Tools can implement this interface to provide the parent manager (the big
+ * application or the StandaloneApp class) enough details to display them.
+ *
+ * If a tool does not implement this class, it gets basic treatment.
+ *
+ * @author
+ * @version 1.0
+ */
+
+public interface Tool
+{
+  /**
+   * @return the JMenuBar for this tool, null if none.
+   */
+  public JMenuBar getMenuBar();
+
+  /**
+   * @return the title string to go into the JFrame/JInternalFrame's title bar.
+   */
+  public String getTitle();
+
+  /**
+   * Called by StandaloneApp to indicate this is within a StandaloneApp.
+   * You should assume you are not in standalone mode until this is called.
+   */
+  public void setStandaloneMode(boolean aMode);
+
+}
\ No newline at end of file
diff --git a/contrib/retep/uk/org/retep/util/ExceptionDialog.java b/contrib/retep/uk/org/retep/util/ExceptionDialog.java
new file mode 100644 (file)
index 0000000..ea3d5c4
--- /dev/null
@@ -0,0 +1,141 @@
+package uk.org.retep.util;
+
+import java.awt.*;
+import javax.swing.*;
+import java.awt.event.*;
+
+/**
+ * Display an Exception to the user
+ * @author
+ * @version 1.0
+ */
+
+public class ExceptionDialog extends JDialog
+{
+  // This is used to store the parent frame.
+  // Classes like StandaloneApp set's this so that the
+  // displayException() method can work without knowing/finding out
+  // the parent Frame/JFrame.
+  private static Frame globalFrame;
+
+  private static ExceptionDialog globalDialog;
+
+  JPanel panel1 = new JPanel();
+  BorderLayout borderLayout1 = new BorderLayout();
+  JTextArea message = new JTextArea();
+  JPanel jPanel1 = new JPanel();
+  JButton jButton1 = new JButton();
+  GridLayout gridLayout1 = new GridLayout();
+  JButton jButton2 = new JButton();
+  JLabel jLabel1 = new JLabel();
+
+  public ExceptionDialog(Frame frame, String title, boolean modal)
+  {
+    super(frame, title, modal);
+    try
+    {
+      jbInit();
+      pack();
+    }
+    catch(Exception ex)
+    {
+      ex.printStackTrace();
+    }
+  }
+
+  public ExceptionDialog()
+  {
+    this(null, "", false);
+  }
+  void jbInit() throws Exception
+  {
+    panel1.setLayout(borderLayout1);
+    message.setBorder(BorderFactory.createLoweredBevelBorder());
+    message.setText("jTextArea1");
+    message.setBackground(Color.lightGray);
+    message.setEditable(false);
+    jButton1.setText("Close");
+    jButton1.addActionListener(new java.awt.event.ActionListener()
+    {
+      public void actionPerformed(ActionEvent e)
+      {
+        jButton1_actionPerformed(e);
+      }
+    });
+    jPanel1.setLayout(gridLayout1);
+    jButton2.setEnabled(false);
+    jButton2.setText("Stack Trace");
+    jLabel1.setEnabled(false);
+    getContentPane().add(panel1);
+    panel1.add(message, BorderLayout.CENTER);
+    this.getContentPane().add(jPanel1, BorderLayout.SOUTH);
+    jPanel1.add(jButton2, null);
+    jPanel1.add(jLabel1, null);
+    jPanel1.add(jButton1, null);
+  }
+
+  /**
+   * Sets the Frame used to display all dialog boxes.
+   */
+  public static void setFrame(Frame aFrame)
+  {
+    globalFrame = aFrame;
+  }
+
+  /**
+   * Displays a dialog based on the exception
+   * @param ex Exception that was thrown
+   */
+  public static void displayException(Exception ex)
+  {
+    displayException(ex,null);
+  }
+
+  /**
+   * Displays a dialog based on the exception
+   * @param ex Exception that was thrown
+   */
+  public static void displayException(Exception ex,String msg)
+  {
+    String cname = ex.getClass().getName();
+    int i=cname.lastIndexOf(".");
+    displayException(cname.substring(i+1),ex,msg);
+  }
+
+  public static void displayException(String title,Exception ex)
+  {
+    displayException(title,ex,null);
+  }
+
+  public static void displayException(String title,Exception ex,String msg)
+  {
+    Logger.log(Logger.ERROR,title,ex.getMessage());
+
+    // Default to a stack trace if no frame set
+    if(globalFrame==null) {
+      ex.printStackTrace();
+      return;
+    }
+
+    if(globalDialog==null) {
+      globalDialog=new ExceptionDialog(globalFrame,title,true);
+      globalDialog.pack();
+    }
+
+    globalDialog.setTitle(title);
+
+    if(msg!=null) {
+      globalDialog.message.setText(msg);
+      globalDialog.message.append(":\n");
+    }
+    globalDialog.message.append(ex.getMessage());
+
+    globalDialog.pack();
+    globalDialog.setVisible(true);
+  }
+
+  void jButton1_actionPerformed(ActionEvent e)
+  {
+    setVisible(false);
+  }
+}
\ No newline at end of file
diff --git a/contrib/retep/uk/org/retep/util/Globals.java b/contrib/retep/uk/org/retep/util/Globals.java
new file mode 100644 (file)
index 0000000..c0e3548
--- /dev/null
@@ -0,0 +1,170 @@
+package uk.org.retep.util;
+
+import uk.org.retep.util.Logger;
+
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Properties;
+
+/**
+ * This is a Singleton that stores global properties, command line arguments
+ * etc.
+ *
+ * All tools are guranteed that this will exist.
+ *
+ * @author
+ * @version 1.0
+ */
+
+public class Globals
+{
+  private static final Globals SINGLETON = new Globals();
+
+  private Hashtable   global= new Hashtable();
+  private Properties  props = new Properties();
+  private ArrayList   args  = new ArrayList();
+
+  private Globals()
+  {
+  }
+
+  public static Globals getInstance()
+  {
+    return SINGLETON;
+  }
+
+  /**
+   * Retrieves an object from the global pool
+   * @param aKey key of the object
+   * @return The object, null if not found
+   */
+  public Object get(Object aKey)
+  {
+    return global.get(aKey);
+  }
+
+  /**
+   * Stores an object into the global pool
+   * @param aKey key of the object
+   * @param aObj the object to store
+   * @return aObj
+   */
+  public Object put(Object aKey,Object aObj)
+  {
+    return global.put(aKey,aObj);
+  }
+
+  /**
+   * Returns a Properties object of all properties
+   */
+  /*
+  public Properties getProperties()
+  {
+    return props;
+  }
+  */
+
+  /**
+   * @param aProp a property supplied to the command line
+   * @return property or NULL if not present
+   */
+  public String getProperty(String aProp)
+  {
+    return props.getProperty(aProp);
+  }
+
+  /**
+   * @param aProp a property supplied to the command line
+   * @param aDefault default to return if property was not supplied
+   * @return property value
+   */
+  public String getProperty(String aProp,String aDefault)
+  {
+    return props.getProperty(aProp,aDefault);
+  }
+
+  /**
+   * @param aID ID of the argument, 0 ... getArgumentCount()-1
+   * @return argument
+   */
+  public String getArgument(int aID)
+  {
+    return (String) args.get(aID);
+  }
+
+  /**
+   * Returns an array of String objects representing the arguments
+   */
+  public String[] getArguments()
+  {
+    return (String[]) args.toArray();
+  }
+
+  /**
+   * Returns an Iterator of the arguments
+   */
+  public Iterator getArgumentIterator()
+  {
+    return args.iterator();
+  }
+
+  /**
+   * @return number of arguments
+   */
+  public int getArgumentCount()
+  {
+    return args.size();
+  }
+
+  /**
+   * Parses the command line arguments
+   */
+  public void parseArguments(String[] aArgs)
+  throws Exception
+  {
+    for(int i=0;i<aArgs.length;i++) {
+      String arg = aArgs[i];
+      if(arg.startsWith("--") || arg.startsWith("-")) {
+        if(arg.length()>1) {
+          // Split the option at the first '=' char if any
+          int s = arg.startsWith("--") ? 2 : 1 ;  // -- or -
+          int e = arg.indexOf("=");
+          String key,val;
+          if(e>s) {
+            // Format: -key=value
+            key=arg.substring(s,e-1);
+            val=arg.substring(e+1);
+          } else if(e>-1 && e<=s) {
+            // Can't have a property without a key!
+            throw new Exception("Invalid option -=");
+          } else {
+            key=arg.substring(s);
+            val=""; // can't be null
+          }
+
+          if(key.equals("d")) {
+            // -d | --d is reserved to set the Logger level
+            int level=0;
+            if(!val.equals("")) {
+              level=Integer.parseInt(val);
+            }
+            Logger.setLevel(level);
+          } else {
+            // Add all other properties into the Properties object
+            props.put(key,val);
+            Logger.log(Logger.INFO,"Argument",key,val);
+          }
+
+        } else {
+          // Just a - on its own?
+          System.out.println("Unknown option: -");
+        }
+      } else {
+        // Add the argument to the array
+        args.add(arg);
+      }
+    }
+  }
+
+}
\ No newline at end of file
diff --git a/contrib/retep/uk/org/retep/util/Logger.java b/contrib/retep/uk/org/retep/util/Logger.java
new file mode 100644 (file)
index 0000000..c272f1d
--- /dev/null
@@ -0,0 +1,150 @@
+package uk.org.retep.util;
+
+import java.io.CharArrayWriter;
+import java.io.PrintWriter;
+
+public class Logger
+{
+  protected static int level;
+  protected static PrintWriter logger;
+
+  public static final int NONE  = -1;
+  public static final int INFO  = 0;
+  public static final int ERROR = 1;
+  public static final int DEBUG = 2;
+  public static final int ALL   = 3;
+
+  static {
+    level = NONE;
+    logger = null;
+  };
+
+  private static final String levels[] = {
+    "INFO :",
+    "ERROR:",
+    "DEBUG:",
+    "ALL  :"
+  };
+
+  public static void setLevel(int aLevel)
+  {
+    // Incase we have not yet set a logger
+    if(logger==null) {
+      logger = new PrintWriter(System.out);
+    }
+
+    if(aLevel<NONE) {
+      aLevel=NONE;
+    } else if(aLevel>ALL) {
+      aLevel=ALL;
+    }
+
+    level=aLevel;
+
+    if(level>NONE) {
+      log(INFO,"Log level changed to",level,levels[level]);
+    }
+  }
+
+  public static void setLogger(PrintWriter pw)
+  {
+    if(logger!=null) {
+      try {
+        logger.flush();
+        logger.close();
+      } catch(Exception ex) {
+        logger=pw;
+        log(ERROR,"Exception while closing logger",ex);
+      }
+    }
+    logger=pw;
+  }
+
+  public static void log(String msg)
+  {
+    log(INFO,msg);
+  }
+
+  public static void log(int aLevel,String msg)
+  {
+    write(aLevel,msg,null);
+  }
+
+  public static void log(int aLevel,String msg,int arg1)
+  {
+    Object o[] = {new Integer(arg1)};
+    write(aLevel,msg,o);
+  }
+
+  public static void log(int aLevel,String msg,int arg1,Object arg2)
+  {
+    Object o[] = {new Integer(arg1),arg2};
+    write(aLevel,msg,o);
+  }
+
+  public static void log(int aLevel,String msg,double arg1)
+  {
+    Object o[] = {new Double(arg1)};
+    write(aLevel,msg,o);
+  }
+
+  public static void log(int aLevel,String msg,double arg1,Object arg2)
+  {
+    Object o[] = {new Double(arg1),arg2};
+    write(aLevel,msg,o);
+  }
+
+  public static void log(int aLevel,String msg,Object arg1)
+  {
+    Object o[] = {arg1};
+    write(aLevel,msg,o);
+  }
+
+  public static void log(int aLevel,String msg,Object arg1,Object arg2)
+  {
+    Object o[] = {arg1,arg2};
+    write(aLevel,msg,o);
+  }
+
+  public static void log(int aLevel,String msg,Object arg1,Object arg2,Object arg3)
+  {
+    Object o[] = {arg1,arg2,arg3};
+    write(aLevel,msg,o);
+  }
+
+  public static void log(int aLevel,String msg,Throwable t)
+  {
+    CharArrayWriter buffer = new CharArrayWriter();
+    PrintWriter printWriter = new PrintWriter(buffer);
+    t.printStackTrace(printWriter);
+    Object o[] = {buffer.toString()};
+    buffer.close();
+    write(aLevel,msg,o);
+  }
+
+  private static void write(int aLevel,String aMsg,Object args[])
+  {
+    // Can't be above ALL
+    if(aLevel>ALL) {
+      aLevel=ALL;
+    }
+
+    // Ignore if below or equal to NONE
+    if(aLevel<INFO || aLevel>level) {
+      return;
+    }
+
+    logger.print("Logger:");
+    logger.print(levels[aLevel]);
+    logger.print(aMsg);
+    if(args!=null) {
+      for(int a=0;a<args.length;a++) {
+        logger.print(":");
+        logger.print(args[a]);
+      }
+    }
+    logger.println();
+    logger.flush();
+  }
+
+}
diff --git a/contrib/retep/uk/org/retep/util/Main.java b/contrib/retep/uk/org/retep/util/Main.java
new file mode 100644 (file)
index 0000000..5df75e0
--- /dev/null
@@ -0,0 +1,42 @@
+package uk.org.retep.util;
+
+import uk.org.retep.util.StandaloneApp;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+
+/**
+ * This is a template for your own Tools. Copy not extend this class. Please
+ * refer to Implementation for details.
+ *
+ * All you need to to is implement the init() method.
+ *
+ * $Id: Main.java,v 1.1 2001/03/05 09:15:36 peter Exp $
+ */
+
+public class Main extends StandaloneApp
+{
+  public Main(String[] args)
+  throws Exception
+  {
+    super(args);
+  }
+
+  public JComponent init()
+  throws Exception
+  {
+    // Create your tool here, then do things like load files based on the
+    // command line arguments. Then return that tool.
+
+    // NB: This just allows us to compile. You're implementation must return
+    // the Tool itself.
+    return new JLabel("Replace with your own tool!");
+  }
+
+  public static void main(String[] args)
+  throws Exception
+  {
+    Main main = new Main(args);
+    main.pack();
+    main.setVisible(true);
+  }
+}
diff --git a/contrib/retep/uk/org/retep/util/StandaloneApp.java b/contrib/retep/uk/org/retep/util/StandaloneApp.java
new file mode 100644 (file)
index 0000000..3c6c9ac
--- /dev/null
@@ -0,0 +1,79 @@
+package uk.org.retep.util;
+
+import uk.org.retep.tools.Tool;
+import uk.org.retep.util.Globals;
+import uk.org.retep.util.ExceptionDialog;
+
+import java.awt.*;
+import javax.swing.*;
+import java.awt.event.*;
+
+/**
+ * This provides the basic services needed for enabling some of the tools to
+ * run in a Stand-alone fassion.
+ *
+ * Note: Because it's designed for standalone use, if this window is closed,
+ * the JVM is terminated. Do not use for normal application use.
+ *
+ * $Id: StandaloneApp.java,v 1.1 2001/03/05 09:15:36 peter Exp $
+ *
+ * @author
+ * @version 1.0
+ */
+
+public abstract class StandaloneApp extends JFrame
+{
+  public StandaloneApp(String[] aArgs)
+  throws Exception
+  {
+    super(); // Initialise JFrame
+
+    // Allow dialogs to work with us
+    ExceptionDialog.setFrame(this);
+
+    // Add a window listener
+    this.addWindowListener(new java.awt.event.WindowAdapter()
+    {
+      public void windowClosing(WindowEvent e)
+      {
+        System.exit(0);
+      }
+    });
+
+    // Parse the command line arguments
+    Globals.getInstance().parseArguments(aArgs);
+
+    // Now initialise this tool (init is overidden)
+    JComponent tool = init();
+
+    // Now add to this frame
+    this.getContentPane().add(tool, BorderLayout.CENTER);
+
+    // Finally call the Tool interface
+    if(tool instanceof Tool) {
+      Tool t = (Tool) tool;
+
+      // Notify the tool we are a standalone
+      t.setStandaloneMode(true);
+
+      // Fetch the title
+      setTitle(t.getTitle());
+
+      // and a MenuBar (if needed)
+      JMenuBar mb = t.getMenuBar();
+      if(mb!=null) {
+        setJMenuBar(t.getMenuBar());
+      }
+    } else {
+      // Ok, set a default title string
+      setTitle("RetepTools Standalone");
+    }
+
+  }
+
+  /**
+   * You must overide this method with your initialiser.
+   */
+  public abstract JComponent init() throws Exception;
+
+}
\ No newline at end of file
diff --git a/contrib/retep/uk/org/retep/util/hba/Editor.java b/contrib/retep/uk/org/retep/util/hba/Editor.java
new file mode 100644 (file)
index 0000000..6571bfe
--- /dev/null
@@ -0,0 +1,141 @@
+package uk.org.retep.util.hba;
+
+import uk.org.retep.tools.Tool;
+import uk.org.retep.util.models.HBATableModel;
+
+import java.awt.*;
+import java.io.*;
+import java.util.*;
+import javax.swing.table.*;
+import javax.swing.*;
+
+/**
+ * pg_hba.conf editor (& repairer)
+ *
+ * @author
+ * @version 1.0
+ */
+
+public class Editor extends JPanel implements Tool
+{
+  BorderLayout borderLayout1 = new BorderLayout();
+  HBATableModel model = new HBATableModel();
+  JPanel jPanel1 = new JPanel();
+  GridBagLayout gridBagLayout1 = new GridBagLayout();
+  JLabel jLabel1 = new JLabel();
+  JComboBox typeEntry = new JComboBox();
+  JLabel jLabel2 = new JLabel();
+  JLabel jLabel3 = new JLabel();
+  JLabel jLabel4 = new JLabel();
+  JTextField ipEntry = new JTextField();
+  JTextField maskEntry = new JTextField();
+  JComboBox authEntry = new JComboBox();
+  JTextField argEntry = new JTextField();
+  JLabel jLabel5 = new JLabel();
+  JPanel jPanel2 = new JPanel();
+  FlowLayout flowLayout1 = new FlowLayout();
+  JButton jButton1 = new JButton();
+  JButton jButton2 = new JButton();
+  JScrollPane jScrollPane1 = new JScrollPane();
+  JButton jButton3 = new JButton();
+  JTable jTable1 = new JTable();
+
+  public Editor()
+  {
+    try
+    {
+      jbInit();
+    }
+    catch(Exception ex)
+    {
+      ex.printStackTrace();
+    }
+  }
+  void jbInit() throws Exception
+  {
+    this.setLayout(borderLayout1);
+    jTable1.setPreferredSize(new Dimension(600, 300));
+    jTable1.setModel(model);
+    this.setPreferredSize(new Dimension(600, 300));
+    this.add(jScrollPane1, BorderLayout.CENTER);
+    jScrollPane1.getViewport().add(jTable1, null);
+    jPanel1.setLayout(gridBagLayout1);
+    jLabel1.setText("Type");
+    jLabel2.setText("IP Address");
+    jLabel3.setText("Mask");
+    jLabel4.setText("Authentication");
+    ipEntry.setText("jTextField1");
+    maskEntry.setText("jTextField2");
+    argEntry.setText("jTextField3");
+    jLabel5.setText("Argument");
+    jPanel2.setLayout(flowLayout1);
+    jButton1.setText("New entry");
+    jButton2.setText("Validate");
+    jButton3.setText("Devele");
+    this.add(jPanel1, BorderLayout.SOUTH);
+    jPanel1.add(jLabel1, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0
+            ,GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
+    jPanel1.add(typeEntry, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0
+            ,GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
+    jPanel1.add(jLabel2, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0
+            ,GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
+    jPanel1.add(jLabel3, new GridBagConstraints(3, 1, 1, 1, 0.0, 0.0
+            ,GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
+    jPanel1.add(jLabel4, new GridBagConstraints(1, 2, 1, 1, 0.0, 0.0
+            ,GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
+    jPanel1.add(ipEntry, new GridBagConstraints(2, 1, 1, 1, 0.0, 0.0
+            ,GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
+    jPanel1.add(maskEntry, new GridBagConstraints(4, 1, 1, 1, 0.0, 0.0
+            ,GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
+    jPanel1.add(authEntry, new GridBagConstraints(2, 2, 1, 1, 0.0, 0.0
+            ,GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
+    jPanel1.add(argEntry, new GridBagConstraints(4, 2, 1, 1, 0.0, 0.0
+            ,GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
+    jPanel1.add(jLabel5, new GridBagConstraints(3, 2, 1, 1, 0.0, 0.0
+            ,GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
+    jPanel1.add(jPanel2, new GridBagConstraints(0, 3, 5, 1, 0.0, 0.0
+            ,GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
+    jPanel2.add(jButton3, null);
+    jPanel2.add(jButton1, null);
+    jPanel2.add(jButton2, null);
+  }
+
+  public void openFile(String aFilename)
+  throws IOException
+  {
+    FileInputStream fis = new FileInputStream(aFilename);
+    BufferedReader br = new BufferedReader(new InputStreamReader(fis));
+    ArrayList list = model.getArray();
+
+    String s = br.readLine();
+    while(s!=null) {
+      if(s.startsWith("#")) {
+        // ignore comments
+      } else {
+        Record rec = Record.parseLine(s);
+        if(rec!=null) {
+          rec.validate();
+          list.add(rec);
+        }
+      }
+      s=br.readLine();
+    }
+
+    model.fireTableDataChanged();
+  }
+
+  public JMenuBar getMenuBar()
+  {
+    return null;
+  }
+
+  public String getTitle()
+  {
+    return "pg_hba.conf Editor/Repair tool";
+  }
+
+  public void setStandaloneMode(boolean aMode)
+  {
+  }
+
+}
\ No newline at end of file
diff --git a/contrib/retep/uk/org/retep/util/hba/Main.java b/contrib/retep/uk/org/retep/util/hba/Main.java
new file mode 100644 (file)
index 0000000..3d93431
--- /dev/null
@@ -0,0 +1,47 @@
+package uk.org.retep.util.hba;
+
+import uk.org.retep.util.ExceptionDialog;
+import uk.org.retep.util.Globals;
+import uk.org.retep.util.Logger;
+import uk.org.retep.util.StandaloneApp;
+
+import java.io.IOException;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+
+/**
+ * Standalone entry point for the Properties editor
+ *
+ * $Id: Main.java,v 1.1 2001/03/05 09:15:37 peter Exp $
+ */
+
+public class Main extends StandaloneApp
+{
+  public Main(String[] args)
+  throws Exception
+  {
+    super(args);
+  }
+
+  public JComponent init()
+  throws Exception
+  {
+    Globals globals = Globals.getInstance();
+
+    Editor editor = new Editor();
+
+    if(globals.getArgumentCount()>0) {
+      editor.openFile(globals.getArgument(0));
+    }
+
+    return editor;
+  }
+
+  public static void main(String[] args)
+  throws Exception
+  {
+    Main main = new Main(args);
+    main.pack();
+    main.setVisible(true);
+  }
+}
diff --git a/contrib/retep/uk/org/retep/util/hba/Record.java b/contrib/retep/uk/org/retep/util/hba/Record.java
new file mode 100644 (file)
index 0000000..b91e6dc
--- /dev/null
@@ -0,0 +1,238 @@
+package uk.org.retep.util.hba;
+
+import uk.org.retep.util.Logger;
+import uk.org.retep.util.misc.IPAddress;
+import uk.org.retep.util.misc.WStringTokenizer;
+
+/**
+ * Used to store the entries of a pg_hba.conf file
+ * @author
+ * @version 1.0
+ */
+
+public class Record
+{
+  int       type;
+  String    dbname;
+  IPAddress ip;
+  IPAddress mask;
+  int       authType;
+  String    authArg;
+
+  public static final int TYPE_LOCAL    = 0;
+  public static final int TYPE_HOST     = 1;
+  public static final int TYPE_HOSTSSL  = 2;
+
+  public static final String types[] = {
+    "local","host","hostssl"
+  };
+
+  public static final int AUTH_TRUST    = 0;
+  public static final int AUTH_PASSWORD = 1;
+  public static final int AUTH_CRYPT    = 2;
+  public static final int AUTH_IDENT    = 3;
+  public static final int AUTH_KRB4     = 4;
+  public static final int AUTH_KRB5     = 5;
+  public static final int AUTH_REJECT   = 6;
+
+  public static final String auths[] = {
+    "trust","password","crypt",
+    "ident",
+    "krb4","krb5",
+    "reject"
+  };
+
+  private static final String spc = " ";
+
+  public Record()
+  {
+  }
+
+  public int getType()
+  {
+    return type;
+  }
+
+  public void setType(int aType)
+  {
+    type=aType;
+  }
+
+  public String getDatabase()
+  {
+    return dbname;
+  }
+
+  public void setDatabase(String aDB)
+  {
+    dbname=aDB;
+  }
+
+  public int getAuthType()
+  {
+    return authType;
+  }
+
+  public void setAuthType(int aType)
+  {
+    authType=aType;
+  }
+
+  public String getAuthArgs()
+  {
+    return authArg;
+  }
+
+  public void setAuthArgs(String aArg)
+  {
+    authArg=aArg;
+  }
+
+  public IPAddress getIP()
+  {
+    return ip;
+  }
+
+  public void setIP(String aArg)
+  {
+    setIP(new IPAddress(aArg));
+  }
+
+  public void setIP(IPAddress aArg)
+  {
+    ip=aArg;
+  }
+
+    public IPAddress getMask()
+  {
+    return mask;
+  }
+
+  public void setMask(String aArg)
+  {
+    setMask(new IPAddress(aArg));
+  }
+
+  public void setMask(IPAddress aArg)
+  {
+    mask=aArg;
+  }
+
+  public String toString()
+  {
+    StringBuffer buf = new StringBuffer();
+    write(buf);
+    return buf.toString();
+  }
+
+  public void write(StringBuffer buf)
+  {
+    buf.append(types[type]).append(spc);
+
+    if(type==TYPE_HOST || type==TYPE_HOSTSSL) {
+      buf.append(getIP()).append(spc);
+      buf.append(getMask()).append(spc);
+    }
+
+    buf.append(auths[authType]);
+
+    // Now the authArg
+    switch(type)
+    {
+      // These have no authArgs
+      case AUTH_TRUST:
+      case AUTH_REJECT:
+      case AUTH_KRB4:
+      case AUTH_KRB5:
+        break;
+
+      // These must have an arg
+      case AUTH_IDENT:
+        buf.append(spc).append(getAuthArgs());
+        break;
+
+      // These may have an optional arg
+      case AUTH_PASSWORD:
+      case AUTH_CRYPT:
+        if(!(authArg==null || authArg.equals("")))
+          buf.append(spc).append(getAuthArgs());
+        break;
+    }
+  }
+
+  private static WStringTokenizer tok;
+
+  public static Record parseLine(String s)
+  {
+    Record res = new Record();
+    int type;
+
+    if(s==null || s.equals("") || s.startsWith("#"))
+      return null;
+
+    if(tok==null)
+      tok=new WStringTokenizer();
+
+    tok.setString(s);
+
+    type=WStringTokenizer.matchToken(types,tok.nextToken());
+    res.setType(type);
+
+    res.setDatabase(tok.nextToken());
+
+    if(type==TYPE_HOST || type==TYPE_HOSTSSL) {
+      res.setIP(new IPAddress(tok.nextToken()));
+      res.setMask(new IPAddress(tok.nextToken()));
+    }
+
+    res.setAuthType(WStringTokenizer.matchToken(auths,tok.nextToken()));
+    res.setAuthArgs(tok.nextToken());
+
+    return res;
+  }
+
+  public static final int VALID         = 0;
+  public static final int INVALID_TYPE  = 1;
+  public static final int INVALID_IPREQUIRED  = 2;
+
+  /**
+   * Validates the record
+   */
+  public int validate()
+  {
+    switch(type)
+    {
+      case TYPE_HOST:
+      case TYPE_HOSTSSL:
+        if(ip==null || ip.isInvalid()) {
+          Logger.log(Logger.INFO,"pg_hba.conf: IP missing or invalid - repairing");
+          setMask("127.0.0.1");
+        }
+
+        if(mask==null || mask.isInvalid() || !ip.validateMask(mask)) {
+          Logger.log(Logger.INFO,"pg_hba.conf: IP address without mask - repairing");
+          setMask(ip.getMask());
+        }
+
+        break;
+
+      case TYPE_LOCAL:
+        break;
+
+      default:
+        return INVALID_TYPE;
+    }
+
+    return VALID;
+  }
+
+  /*
+# host       all        192.168.54.1   255.255.255.255    reject
+# host       all        0.0.0.0        0.0.0.0            krb5
+# host       all        192.168.0.0    255.255.0.0        ident     omicron
+#
+
+local        all                                           trust
+host         all         127.0.0.1     255.255.255.255     trust
+*/
+}
\ No newline at end of file
diff --git a/contrib/retep/uk/org/retep/util/misc/IPAddress.java b/contrib/retep/uk/org/retep/util/misc/IPAddress.java
new file mode 100644 (file)
index 0000000..a04babd
--- /dev/null
@@ -0,0 +1,125 @@
+package uk.org.retep.util.misc;
+
+import java.util.StringTokenizer;
+
+/**
+ * Represent an IP address
+ * @author
+ * @version 1.0
+ */
+
+public class IPAddress
+{
+  protected long    address;
+  protected long    b[] = new long[4];
+  protected boolean invalid=true;
+
+  public IPAddress()
+  {
+  }
+
+  public IPAddress(String s)
+  {
+    setAddress(s);
+  }
+
+  public synchronized void setAddress(String s)
+  {
+    if(s==null || s.equals("")) {
+      invalid=true;
+      return;
+    }
+
+    address=0;
+    StringTokenizer tok = new StringTokenizer(s,".");
+    int i=0;
+    while(i<4 && tok.hasMoreElements()) {
+      b[i++] = Long.parseLong(tok.nextToken());
+    }
+    while(i<4) {
+      b[i++]=0;
+    }
+
+    invalid=false;
+    refresh();
+  }
+
+  public void refresh()
+  {
+    if(invalid)
+      return;
+    address = (b[0]<<24) | (b[1]<<16) | (b[2]<<8) | (b[3]);
+  }
+
+  public boolean isInvalid()
+  {
+    refresh();
+    return invalid;
+  }
+
+  public String toString()
+  {
+    refresh();
+    if(invalid)
+      return "*INVALID*";
+
+    return Long.toString(b[0])+"."+Long.toString(b[1])+"."+Long.toString(b[2])+"."+Long.toString(b[3]);
+  }
+
+  public boolean equals(Object o)
+  {
+    if(o instanceof IPAddress) {
+      IPAddress ip = (IPAddress) o;
+
+      refresh();
+      ip.refresh();
+
+      if(ip.invalid == invalid)
+        return false;
+
+      return address==ip.address;
+    }
+    return false;
+  }
+
+  private static int gethoststart(long b)
+  {
+    if((b & 0x80)==0x00) return 1; // class A
+    if((b & 0xc0)==0x80) return 2; // class B
+    if((b & 0xe0)==0xc0) return 3; // class C
+    return 4;                      // class D
+  }
+
+  public boolean validateMask(IPAddress mask)
+  {
+    // If were a network check the host mask
+    int i=gethoststart(b[0]);
+System.out.println("Host start "+i);
+    while(i<4 && b[i]==0) {
+      if(mask.b[i++]>0)
+        return false;
+    }
+
+    for(i=0;i<4;i++) {
+      if((b[i]&mask.b[i])!=b[i])
+        return false;
+    }
+
+    return true;
+  }
+
+  public IPAddress getMask()
+  {
+    IPAddress mask = new IPAddress();
+    int i=3;
+    while(i>-1 && b[i]==0) {
+      mask.b[i--]=0;
+    }
+    while(i>-1) {
+      mask.b[i--]=255;
+    }
+    mask.invalid=false;
+    mask.refresh();
+    return mask;
+  }
+}
\ No newline at end of file
diff --git a/contrib/retep/uk/org/retep/util/misc/PropertiesIO.java b/contrib/retep/uk/org/retep/util/misc/PropertiesIO.java
new file mode 100644 (file)
index 0000000..7bed62c
--- /dev/null
@@ -0,0 +1,157 @@
+package uk.org.retep.util.misc;
+
+import java.io.*;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Properties;
+import java.util.TreeMap;
+
+/**
+ * Misc Properties utilities..
+ * @author
+ * @version 1.0
+ */
+
+public class PropertiesIO
+{
+
+  public PropertiesIO()
+  {
+  }
+
+  /**
+   * Builds a TreeMap based on the given Properties object. This is useful
+   * because the keys will be in sorted order.
+   */
+  public static TreeMap getTreeMap(Properties p)
+  {
+    TreeMap map = new TreeMap();
+    Iterator e = p.keySet().iterator();
+    while(e.hasNext()) {
+      Object k = e.next();
+      map.put(k,p.get(k));
+    }
+    return map;
+  }
+
+  /**
+   * Writes a Properties file to the writer. This is similar to Properties.save
+   * except you can pick the key/value separator
+   */
+  public static synchronized void save(Properties p,OutputStream out,char sep,String header)
+  throws IOException
+  {
+    save(p,p.keySet().iterator(),out,sep,header);
+  }
+
+  /**
+   * Writes a Properties file to the writer. This is similar to Properties.save
+   * except you can pick the key/value separator and the keys are written
+   * in a sorted manner
+   */
+  public static synchronized void saveSorted(Properties p,OutputStream out,char sep,String header)
+  throws IOException
+  {
+    save(p,getTreeMap(p).keySet().iterator(),out,sep,header);
+  }
+
+  /**
+   * This is the same as save, only the keys in the enumeration are written.
+   */
+  public static synchronized void save(Properties p,Iterator e, OutputStream out,char sep,String header)
+  throws IOException
+  {
+    BufferedWriter w = new BufferedWriter(new OutputStreamWriter(out, "8859_1"));
+
+    if (header != null) {
+      w.write('#');
+      w.write(header);
+      w.newLine();
+    }
+
+    w.write('#');
+    w.write(new Date().toString());
+    w.newLine();
+
+    while(e.hasNext()) {
+      String key = (String)e.next();
+      w.write(encode(key,true));
+      w.write(sep);
+      w.write(encode((String)p.get(key),false));
+      w.newLine();
+    }
+    w.flush();
+  }
+
+  private static final String specialSaveChars = "=: \t\r\n\f#!";
+
+  /**
+   * Encodes a string in a way similar to the JDK's Properties method
+   */
+  public static String encode(String s, boolean escapeSpace)
+  {
+    int l=s.length();
+    StringBuffer buf = new StringBuffer(l<<1);
+
+    for(int i=0;i<l;i++) {
+      char c = s.charAt(i);
+      switch(c)
+        {
+          case ' ':
+            if(i==0 || escapeSpace) {
+              buf.append('\\');
+            }
+            buf.append(' ');
+            break;
+
+          case '\\':
+            buf.append('\\').append('\\');
+            break;
+
+          case '\t':
+            buf.append('\\').append('t');
+            break;
+
+          case '\n':
+            buf.append('\\').append('n');
+            break;
+
+          case '\r':
+            buf.append('\\').append('r');
+            break;
+
+          case '\f':
+            buf.append('\\').append('f');
+            break;
+
+          default:
+            if((c<0x20)||(c>0x7e)) {
+              buf.append('\\').append('u');
+              buf.append(toHex((c >> 12) & 0xF));
+              buf.append(toHex((c >>  8) & 0xF));
+              buf.append(toHex((c >>  4) & 0xF));
+              buf.append(toHex( c        & 0xF));
+            } else {
+              if (specialSaveChars.indexOf(c) != -1)
+                buf.append('\\');
+              buf.append(c);
+            }
+        }
+    }
+    return buf.toString();
+  }
+
+  /**
+   * Convert a nibble to a hex character
+   * @param    nibble  the nibble to convert.
+   */
+  public static char toHex(int n) {
+    return hd[(n & 0xF)];
+  }
+
+  /** A table of hex digits */
+  private static final char[] hd = {
+    '0','1','2','3','4','5','6','7',
+    '8','9','A','B','C','D','E','F'
+  };
+}
\ No newline at end of file
diff --git a/contrib/retep/uk/org/retep/util/misc/WStringTokenizer.java b/contrib/retep/uk/org/retep/util/misc/WStringTokenizer.java
new file mode 100644 (file)
index 0000000..763676c
--- /dev/null
@@ -0,0 +1,102 @@
+package uk.org.retep.util.misc;
+
+/**
+ * Similar to StringTokenizer but handles white spaces and multiple delimiters
+ * between tokens. It also handles quotes
+ *
+ * @author
+ * @version 1.0
+ */
+
+public class WStringTokenizer
+{
+  String string;
+  int pos,len;
+
+  /**
+   * Constructor
+   */
+  public WStringTokenizer()
+  {
+  }
+
+  /**
+   * Constructor: set the initial string
+   * @param aString String to tokenise
+   */
+  public WStringTokenizer(String aString)
+  {
+    setString(aString);
+  }
+
+  /**
+   * @param aString String to tokenise
+   */
+  public void setString(String aString)
+  {
+    string=aString;
+    pos=0;
+    len=string.length();
+  }
+
+  /**
+   * @return true if more tokens may be possible
+   */
+  public boolean hasMoreTokens()
+  {
+    return !(string==null || pos==len);
+  }
+
+  /**
+   * @return next token, null if complete.
+   */
+  public String nextToken()
+  {
+    char c;
+    boolean q=false;
+
+    if(!hasMoreTokens())
+      return null;
+
+    // find start of token
+    while(pos<len) {
+      c = string.charAt(pos);
+      if(c=='\'' || c=='\"')
+        q=!q;
+      if(q || c==' '||c=='\t')
+        pos++;
+      else
+        break;
+    }
+
+    // find last char of token
+    int p=pos;
+    while(pos<len) {
+      c = string.charAt(pos);
+      if(c=='\'' || c=='\"')
+        q=!q;
+      if(!q && (c==' '||c=='\t') )
+        break;
+      else
+        pos++;
+    }
+
+    return string.substring(p,pos);
+  }
+
+  /**
+   * Compare a string against an array of strings and return the index
+   * @param t array to compare against (all lowercase)
+   * @param s string to test
+   * @return index in t of s, -1 if not present
+   */
+  public static int matchToken(String[] t,String s)
+  {
+    s=s.toLowerCase();
+    for(int i=0;i<t.length;i++)
+      if(t[i].equals(s))
+        return i;
+    return -1;
+  }
+
+}
\ No newline at end of file
diff --git a/contrib/retep/uk/org/retep/util/models/HBATableModel.java b/contrib/retep/uk/org/retep/util/models/HBATableModel.java
new file mode 100644 (file)
index 0000000..fb7bb72
--- /dev/null
@@ -0,0 +1,91 @@
+package uk.org.retep.util.models;
+
+import uk.org.retep.util.hba.Record;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import javax.swing.table.*;
+
+/**
+ * A TableModel to display the contents of a pg_hba.conf file
+ * @author
+ * @version 1.0
+ */
+
+public class HBATableModel extends AbstractTableModel
+{
+  ArrayList list = new ArrayList();
+
+  private static final String cols[] = {
+    "Type","Database","IP Address","IP Mask","Authentication","Arguments"
+  };
+
+
+  public HBATableModel()
+  {
+  }
+
+  public ArrayList getArray()
+  {
+    return list;
+  }
+
+  public int getColumnCount()
+  {
+    return cols.length;
+  }
+
+  public Object getValueAt(int aRow, int aCol)
+  {
+    Record rec = (Record) list.get(aRow);
+    int t;
+
+    switch(aCol)
+    {
+      case 0:
+        t = rec.getType();
+        return t<0 ? "ERR" : Record.types[t] ;
+
+      case 1:
+        return rec.getDatabase();
+
+      case 2:
+        return rec.getIP();
+
+      case 3:
+        return rec.getMask();
+
+      case 4:
+        t=rec.getAuthType();
+        return t<0 ? "ERR" : Record.auths[t] ;
+
+      case 5:
+        return rec.getAuthArgs();
+
+      default:
+        return "";
+    }
+  }
+
+  public int getRowCount()
+  {
+    return list.size();
+  }
+
+  public boolean isCellEditable(int rowIndex, int columnIndex)
+  {
+    /**@todo: Override this javax.swing.table.AbstractTableModel method*/
+    return super.isCellEditable( rowIndex,  columnIndex);
+  }
+
+  public String getColumnName(int aColumn)
+  {
+    return cols[aColumn];
+  }
+
+  public void setValueAt(Object aValue, int rowIndex, int columnIndex)
+  {
+    /**@todo: Override this javax.swing.table.AbstractTableModel method*/
+    super.setValueAt( aValue,  rowIndex,  columnIndex);
+  }
+}
\ No newline at end of file
diff --git a/contrib/retep/uk/org/retep/util/models/PropertiesTableModel.java b/contrib/retep/uk/org/retep/util/models/PropertiesTableModel.java
new file mode 100644 (file)
index 0000000..d48b166
--- /dev/null
@@ -0,0 +1,176 @@
+package uk.org.retep.util.models;
+
+import uk.org.retep.util.Logger;
+import uk.org.retep.util.misc.PropertiesIO;
+
+import java.io.*;
+import java.util.*;
+import javax.swing.table.*;
+
+import java.text.*;
+
+/**
+ * A TableModel that shows a view of a PropertyFile object
+ *
+ * $Id: PropertiesTableModel.java,v 1.1 2001/03/05 09:15:37 peter Exp $
+ *
+ * @author
+ * @version 1.0
+ */
+public class PropertiesTableModel extends AbstractTableModel
+{
+  // The properties
+  protected TreeMap properties;
+  protected Properties origProperties;
+  protected Object keys[];
+
+  public PropertiesTableModel()
+  {
+    this(new Properties());
+  }
+
+  public PropertiesTableModel(Properties aProperties)
+  {
+    setProperties(aProperties);
+  }
+
+  public synchronized int getKeyRow(Object k)
+  {
+    for(int i=0;i<keys.length;i++) {
+      if(keys[i].equals(k)) {
+        return i;
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * Best not use this one to update, use the put method in this class!
+   */
+  public Properties getProperties()
+  {
+    return origProperties;
+  }
+
+  public synchronized void put(Object k,Object v)
+  {
+    properties.put(k,v);
+    origProperties.put(k,v);
+    refresh();
+  }
+
+  public Object get(Object k)
+  {
+    return origProperties.get(k);
+  }
+
+  public synchronized void remove(Object k)
+  {
+    properties.remove(k);
+    origProperties.remove(k);
+    refresh();
+  }
+
+  public boolean contains(Object o)
+  {
+    return origProperties.contains(o);
+  }
+
+  public boolean containsKey(Object o)
+  {
+    return origProperties.containsKey(o);
+  }
+
+  public boolean containsValue(Object o)
+  {
+    return origProperties.containsValue(o);
+  }
+
+  public void setProperties(Properties aProperties)
+  {
+    origProperties=aProperties;
+    properties = PropertiesIO.getTreeMap(aProperties);
+    refresh();
+  }
+
+  public void refresh()
+  {
+    keys = properties.keySet().toArray();
+    fireTableDataChanged();
+  }
+
+  private static final String cols[] = {
+    "Property","Value"
+  };
+
+  public int getColumnCount()
+  {
+    return cols.length;
+  }
+
+  public Object getValueAt(int aRow, int aColumn)
+  {
+    if(aRow<0 || aRow>=keys.length || aColumn<0 || aColumn>=cols.length)
+      return null;
+
+    Object key = keys[aRow];
+
+    switch(aColumn)
+    {
+      case 0:
+        return key;
+
+      case 1:
+        return properties.get(key);
+
+      default:
+        return null;
+    }
+  }
+
+  public int getRowCount()
+  {
+    return keys.length;
+  }
+
+  public String getColumnName(int aColumn)
+  {
+    return cols[aColumn];
+  }
+
+  public void setValueAt(Object aValue, int aRow, int aColumn)
+  {
+    if(aRow<0 || aRow>=keys.length || aColumn<0 || aColumn>=cols.length)
+      return;
+
+    switch(aColumn)
+      {
+        // Rename the key (only if not already present). If already present
+        // the refresh() will replace with the old one anyhow...
+        case 0:
+          if(!properties.containsKey(aValue)) {
+            Object oldValue = get(keys[aRow]);
+            remove(keys[aRow]);
+            put(aValue,oldValue);
+          }
+          refresh();
+          break;
+
+        // Update the value...
+        case 1:
+          put(keys[aRow],aValue);
+          //refresh();
+          break;
+
+        default:
+          // Should never be called
+          Logger.log(Logger.ERROR,"PropertiesTableModel: Column range",aColumn);
+      }
+  }
+
+  public boolean isCellEditable(int aRow, int aColumn)
+  {
+    return true;
+  }
+
+}
diff --git a/contrib/retep/uk/org/retep/util/proped/Main.java b/contrib/retep/uk/org/retep/util/proped/Main.java
new file mode 100644 (file)
index 0000000..6f2c73b
--- /dev/null
@@ -0,0 +1,53 @@
+package uk.org.retep.util.proped;
+
+import uk.org.retep.util.ExceptionDialog;
+import uk.org.retep.util.Globals;
+import uk.org.retep.util.Logger;
+import uk.org.retep.util.StandaloneApp;
+
+import java.io.IOException;
+import java.util.Iterator;
+import javax.swing.JComponent;
+
+/**
+ * Standalone entry point for the Properties editor
+ *
+ * $Id: Main.java,v 1.1 2001/03/05 09:15:38 peter Exp $
+ */
+
+public class Main extends StandaloneApp
+{
+  public Main(String[] args)
+  throws Exception
+  {
+    super(args);
+  }
+
+  public JComponent init()
+  throws Exception
+  {
+    Globals globals = Globals.getInstance();
+
+    PropertyEditor panel = new PropertyEditor();
+
+    // Only handle 1 open at a time in standalone mode
+    if(globals.getArgumentCount()>0) {
+      try {
+        panel.openFile(globals.getArgument(0));
+      } catch(IOException ioe) {
+        ExceptionDialog.displayException(ioe,"while loading "+globals.getArgument(0));
+        throw (Exception) ioe.fillInStackTrace();
+      }
+    }
+
+    return panel;
+  }
+
+  public static void main(String[] args)
+  throws Exception
+  {
+    Main main = new Main(args);
+    main.pack();
+    main.setVisible(true);
+  }
+}
\ No newline at end of file
diff --git a/contrib/retep/uk/org/retep/util/proped/PropertyEditor.java b/contrib/retep/uk/org/retep/util/proped/PropertyEditor.java
new file mode 100644 (file)
index 0000000..b5c19e1
--- /dev/null
@@ -0,0 +1,381 @@
+package uk.org.retep.util.proped;
+
+import uk.org.retep.util.ExceptionDialog;
+import uk.org.retep.util.misc.PropertiesIO;
+import uk.org.retep.util.models.PropertiesTableModel;
+
+import java.awt.*;
+import java.io.*;
+import java.util.*;
+import javax.swing.*;
+import java.awt.event.*;
+
+/**
+ * A property file editor
+ *
+ * $Id: PropertyEditor.java,v 1.1 2001/03/05 09:15:38 peter Exp $
+ *
+ * @author
+ * @version 1.0
+ */
+
+public class PropertyEditor
+extends JPanel
+implements uk.org.retep.tools.Tool
+{
+  BorderLayout borderLayout1 = new BorderLayout();
+
+  // The filename, null if not set
+  String filename;
+  File file;
+
+  JScrollPane jScrollPane1 = new JScrollPane();
+  JTable contentTable = new JTable();
+
+  PropertiesTableModel model = new PropertiesTableModel();
+
+  boolean standaloneMode;
+
+  private static final String TITLE_PREFIX = "Retep PropertyEditor";
+  JPopupMenu popupMenu = new JPopupMenu();
+  JMenuItem newPopupItem = new JMenuItem();
+  JMenuItem dupPopupItem = new JMenuItem();
+  JMenuItem delPopupItem = new JMenuItem();
+  JMenuBar menuBar = new JMenuBar();
+  JMenu jMenu1 = new JMenu();
+  JMenuItem jMenuItem4 = new JMenuItem();
+  JMenuItem jMenuItem5 = new JMenuItem();
+  JMenuItem jMenuItem6 = new JMenuItem();
+  JMenuItem jMenuItem7 = new JMenuItem();
+  JMenuItem jMenuItem8 = new JMenuItem();
+  JMenuItem closeMenuItem = new JMenuItem();
+
+  public PropertyEditor()
+  {
+    try
+    {
+      jbInit();
+    }
+    catch(Exception ex)
+    {
+      ex.printStackTrace();
+    }
+  }
+
+  /**
+   * @return the default menubar
+   */
+  public JMenuBar getMenuBar()
+  {
+    return menuBar;
+  }
+
+  /**
+   * @return the File menu
+   */
+  public JMenu getMenu()
+  {
+    return jMenu1;
+  }
+
+  /**
+   * @return the recomended title string for the parent JFrame/JInternalFrame
+   */
+  public String getTitle()
+  {
+    if(filename==null) {
+      return TITLE_PREFIX;
+    }
+    return TITLE_PREFIX+": "+filename;
+  }
+
+  /**
+   * Sets menus up to Standalone mode
+   */
+  public void setStandaloneMode(boolean aMode)
+  {
+    standaloneMode=aMode;
+    if(aMode) {
+      closeMenuItem.setText("Exit");
+    } else {
+      closeMenuItem.setText("Close");
+    }
+  }
+
+  public boolean isStandalone()
+  {
+    return standaloneMode;
+  }
+
+  public void openFile(String aFile)
+  throws IOException
+  {
+    openFile(new File(aFile));
+  }
+
+  public void openFile(File aFile)
+  throws IOException
+  {
+    FileInputStream fis = new FileInputStream(aFile);
+    Properties p = new Properties();
+    p.load(fis);
+    fis.close();
+    model.setProperties(p);
+
+    file=aFile;
+    filename = aFile.getAbsolutePath();
+  }
+
+  public void saveFile(File aFile)
+  throws IOException
+  {
+    FileOutputStream fis = new FileOutputStream(aFile);
+    PropertiesIO.save(model.getProperties(),fis,'=',"Written by "+TITLE_PREFIX);
+    fis.close();
+
+    filename = aFile.getAbsolutePath();
+    file = aFile;
+  }
+
+  void jbInit() throws Exception
+  {
+    this.setLayout(borderLayout1);
+    contentTable.setToolTipText("");
+    contentTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
+    contentTable.setModel(model);
+    contentTable.addMouseListener(new java.awt.event.MouseAdapter()
+    {
+      public void mouseClicked(MouseEvent e)
+      {
+        contentTable_mouseClicked(e);
+      }
+      public void mouseReleased(MouseEvent e)
+      {
+        contentTable_mouseReleased(e);
+      }
+    });
+    newPopupItem.setText("New");
+    newPopupItem.addActionListener(new java.awt.event.ActionListener()
+    {
+      public void actionPerformed(ActionEvent e)
+      {
+        newPopupItem_actionPerformed(e);
+      }
+    });
+    dupPopupItem.setText("Duplicate");
+    dupPopupItem.setAccelerator(javax.swing.KeyStroke.getKeyStroke(67, java.awt.event.KeyEvent.CTRL_MASK, false));
+    dupPopupItem.addActionListener(new java.awt.event.ActionListener()
+    {
+      public void actionPerformed(ActionEvent e)
+      {
+        dupPopupItem_actionPerformed(e);
+      }
+    });
+    delPopupItem.setText("Delete");
+    delPopupItem.setAccelerator(javax.swing.KeyStroke.getKeyStroke(68, java.awt.event.KeyEvent.CTRL_MASK, false));
+    delPopupItem.addActionListener(new java.awt.event.ActionListener()
+    {
+      public void actionPerformed(ActionEvent e)
+      {
+        delPopupItem_actionPerformed(e);
+      }
+    });
+    jMenu1.setText("File");
+    jMenuItem4.setText("Open");
+    jMenuItem4.addActionListener(new java.awt.event.ActionListener()
+    {
+      public void actionPerformed(ActionEvent e)
+      {
+        jMenuItem4_actionPerformed(e);
+      }
+    });
+    jMenuItem5.setText("Save");
+    jMenuItem5.addActionListener(new java.awt.event.ActionListener()
+    {
+      public void actionPerformed(ActionEvent e)
+      {
+        jMenuItem5_actionPerformed(e);
+      }
+    });
+    jMenuItem6.setText("Save As");
+    jMenuItem6.addActionListener(new java.awt.event.ActionListener()
+    {
+      public void actionPerformed(ActionEvent e)
+      {
+        jMenuItem6_actionPerformed(e);
+      }
+    });
+    jMenuItem7.setText("Revert");
+    jMenuItem7.addActionListener(new java.awt.event.ActionListener()
+    {
+      public void actionPerformed(ActionEvent e)
+      {
+        jMenuItem7_actionPerformed(e);
+      }
+    });
+    jMenuItem8.setText("Print");
+    closeMenuItem.setText("Close");
+    closeMenuItem.addActionListener(new java.awt.event.ActionListener()
+    {
+      public void actionPerformed(ActionEvent e)
+      {
+        closeMenuItem_actionPerformed(e);
+      }
+    });
+    jMenu2.setText("Edit");
+    jMenuItem1.setText("New");
+    jMenuItem1.setAccelerator(javax.swing.KeyStroke.getKeyStroke(78, java.awt.event.KeyEvent.CTRL_MASK, false));
+    jMenuItem1.addActionListener(new java.awt.event.ActionListener()
+    {
+      public void actionPerformed(ActionEvent e)
+      {
+        newPopupItem_actionPerformed(e);
+      }
+    });
+    jMenuItem2.setText("Duplicate");
+    jMenuItem3.setText("Delete");
+    this.add(jScrollPane1, BorderLayout.CENTER);
+    jScrollPane1.getViewport().add(contentTable, null);
+    popupMenu.add(newPopupItem);
+    popupMenu.add(dupPopupItem);
+    popupMenu.add(delPopupItem);
+    menuBar.add(jMenu1);
+    menuBar.add(jMenu2);
+    jMenu1.add(jMenuItem4);
+    jMenu1.add(jMenuItem5);
+    jMenu1.add(jMenuItem6);
+    jMenu1.add(jMenuItem7);
+    jMenu1.addSeparator();
+    jMenu1.add(jMenuItem8);
+    jMenu1.addSeparator();
+    jMenu1.add(closeMenuItem);
+    jMenu2.add(jMenuItem1);
+    jMenu2.add(jMenuItem2);
+    jMenu2.add(jMenuItem3);
+  }
+
+  Point popupPoint = new Point();
+  JMenu jMenu2 = new JMenu();
+  JMenuItem jMenuItem1 = new JMenuItem();
+  JMenuItem jMenuItem2 = new JMenuItem();
+  JMenuItem jMenuItem3 = new JMenuItem();
+  void contentTable_mouseClicked(MouseEvent e)
+  {
+    if(e.isPopupTrigger()) {
+      popupPoint.setLocation(e.getX(),e.getY());
+      popupMenu.show(contentTable,e.getX(),e.getY());
+    }
+  }
+
+  void contentTable_mouseReleased(MouseEvent e)
+  {
+    contentTable_mouseClicked(e);
+  }
+
+  void jMenuItem4_actionPerformed(ActionEvent e)
+  {
+    JFileChooser fc = new JFileChooser();
+    if(fc.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
+      try {
+        openFile(fc.getSelectedFile());
+      } catch(IOException ioe) {
+        ExceptionDialog.displayException(ioe);
+      }
+    }
+  }
+
+  void closeMenuItem_actionPerformed(ActionEvent e)
+  {
+    if(standaloneMode) {
+      System.exit(0);
+    } else {
+      filename="";
+      file=null;
+      model.setProperties(new Properties());
+    }
+  }
+
+  void newPopupItem_actionPerformed(ActionEvent e)
+  {
+    int y = contentTable.rowAtPoint(popupPoint);
+
+    // create a new unique key based on the current one
+    String key=(String) model.getValueAt(y,0);
+
+    if(key==null) {
+      key="new-key";
+    }
+
+    int uid=1;
+    while(model.containsKey(key+uid)) {
+      uid++;
+    }
+
+    key=key+uid;
+    model.put(key,"");
+    contentTable.clearSelection();
+  }
+
+  void dupPopupItem_actionPerformed(ActionEvent e)
+  {
+    int y = contentTable.rowAtPoint(popupPoint);
+
+    // create a new unique key based on the current one
+    String key=(String) model.getValueAt(y,0);
+    Object val=model.get(key);
+
+    int uid=1;
+    while(model.containsKey(key+uid)) {
+      uid++;
+    }
+
+    key=key+uid;
+    model.put(key,val);
+    contentTable.clearSelection();
+  }
+
+  void delPopupItem_actionPerformed(ActionEvent e)
+  {
+    int y = contentTable.rowAtPoint(popupPoint);
+    model.remove(model.getValueAt(y,0));
+  }
+
+  void jMenuItem6_actionPerformed(ActionEvent e)
+  {
+    JFileChooser fc = new JFileChooser();
+    if(fc.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
+      try {
+        saveFile(fc.getSelectedFile());
+      } catch(IOException ioe) {
+        ExceptionDialog.displayException(ioe);
+      }
+    }
+  }
+
+  void jMenuItem5_actionPerformed(ActionEvent e)
+  {
+    if(filename==null) {
+      jMenuItem6_actionPerformed(e);
+    } else {
+      try {
+        saveFile(file);
+      } catch(IOException ioe) {
+        ExceptionDialog.displayException(ioe);
+      }
+    }
+  }
+
+  void jMenuItem7_actionPerformed(ActionEvent e)
+  {
+    // add check here
+    if(file!=null) {
+      try {
+        openFile(file);
+      } catch(IOException ioe) {
+        ExceptionDialog.displayException(ioe);
+      }
+    } else {
+      jMenuItem4_actionPerformed(e);
+    }
+  }
+}
\ No newline at end of file