From e2e84a1c5e137111a2d11e21423af204344190c5 Mon Sep 17 00:00:00 2001 From: Peter Mount Date: Mon, 5 Mar 2001 09:15:38 +0000 Subject: [PATCH] First batch of the tools merged in... --- contrib/retep/CHANGELOG | 5 +- contrib/retep/Implementation | 116 ++++++ contrib/retep/README | 35 ++ contrib/retep/build.xml | 10 +- contrib/retep/retep.jpx | 16 +- .../retep/uk/org/retep/dtu/DCollection.java | 228 +++++++++++ .../retep/uk/org/retep/dtu/DConstants.java | 43 ++ contrib/retep/uk/org/retep/dtu/DElement.java | 31 ++ .../retep/uk/org/retep/dtu/DEnvironment.java | 30 ++ contrib/retep/uk/org/retep/dtu/DModule.java | 97 +++++ .../retep/uk/org/retep/dtu/DModuleXML.java | 233 +++++++++++ contrib/retep/uk/org/retep/dtu/DNode.java | 233 +++++++++++ .../retep/uk/org/retep/dtu/DProcessor.java | 191 +++++++++ .../retep/uk/org/retep/dtu/DTransform.java | 133 ++++++ contrib/retep/uk/org/retep/tools.properties | 8 + contrib/retep/uk/org/retep/tools/Tool.java | 33 ++ .../uk/org/retep/util/ExceptionDialog.java | 141 +++++++ contrib/retep/uk/org/retep/util/Globals.java | 170 ++++++++ contrib/retep/uk/org/retep/util/Logger.java | 150 +++++++ contrib/retep/uk/org/retep/util/Main.java | 42 ++ .../uk/org/retep/util/StandaloneApp.java | 79 ++++ .../retep/uk/org/retep/util/hba/Editor.java | 141 +++++++ contrib/retep/uk/org/retep/util/hba/Main.java | 47 +++ .../retep/uk/org/retep/util/hba/Record.java | 238 +++++++++++ .../uk/org/retep/util/misc/IPAddress.java | 125 ++++++ .../uk/org/retep/util/misc/PropertiesIO.java | 157 ++++++++ .../org/retep/util/misc/WStringTokenizer.java | 102 +++++ .../org/retep/util/models/HBATableModel.java | 91 +++++ .../util/models/PropertiesTableModel.java | 176 ++++++++ .../retep/uk/org/retep/util/proped/Main.java | 53 +++ .../org/retep/util/proped/PropertyEditor.java | 381 ++++++++++++++++++ 31 files changed, 3526 insertions(+), 9 deletions(-) create mode 100644 contrib/retep/uk/org/retep/dtu/DCollection.java create mode 100644 contrib/retep/uk/org/retep/dtu/DConstants.java create mode 100644 contrib/retep/uk/org/retep/dtu/DElement.java create mode 100644 contrib/retep/uk/org/retep/dtu/DEnvironment.java create mode 100644 contrib/retep/uk/org/retep/dtu/DModule.java create mode 100644 contrib/retep/uk/org/retep/dtu/DModuleXML.java create mode 100644 contrib/retep/uk/org/retep/dtu/DNode.java create mode 100644 contrib/retep/uk/org/retep/dtu/DProcessor.java create mode 100644 contrib/retep/uk/org/retep/dtu/DTransform.java create mode 100644 contrib/retep/uk/org/retep/tools.properties create mode 100644 contrib/retep/uk/org/retep/tools/Tool.java create mode 100644 contrib/retep/uk/org/retep/util/ExceptionDialog.java create mode 100644 contrib/retep/uk/org/retep/util/Globals.java create mode 100644 contrib/retep/uk/org/retep/util/Logger.java create mode 100644 contrib/retep/uk/org/retep/util/Main.java create mode 100644 contrib/retep/uk/org/retep/util/StandaloneApp.java create mode 100644 contrib/retep/uk/org/retep/util/hba/Editor.java create mode 100644 contrib/retep/uk/org/retep/util/hba/Main.java create mode 100644 contrib/retep/uk/org/retep/util/hba/Record.java create mode 100644 contrib/retep/uk/org/retep/util/misc/IPAddress.java create mode 100644 contrib/retep/uk/org/retep/util/misc/PropertiesIO.java create mode 100644 contrib/retep/uk/org/retep/util/misc/WStringTokenizer.java create mode 100644 contrib/retep/uk/org/retep/util/models/HBATableModel.java create mode 100644 contrib/retep/uk/org/retep/util/models/PropertiesTableModel.java create mode 100644 contrib/retep/uk/org/retep/util/proped/Main.java create mode 100644 contrib/retep/uk/org/retep/util/proped/PropertyEditor.java diff --git a/contrib/retep/CHANGELOG b/contrib/retep/CHANGELOG index 59f7c335cb9..188c40129e2 100644 --- a/contrib/retep/CHANGELOG +++ b/contrib/retep/CHANGELOG @@ -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. diff --git a/contrib/retep/Implementation b/contrib/retep/Implementation index e69de29bb2d..b3125acf0ed 100644 --- a/contrib/retep/Implementation +++ b/contrib/retep/Implementation @@ -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. + diff --git a/contrib/retep/README b/contrib/retep/README index e69de29bb2d..5355c9d99f3 100644 --- a/contrib/retep/README +++ b/contrib/retep/README @@ -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. + diff --git a/contrib/retep/build.xml b/contrib/retep/build.xml index 019903bc798..78b45e3bf45 100644 --- a/contrib/retep/build.xml +++ b/contrib/retep/build.xml @@ -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 $ --> @@ -35,16 +35,16 @@ - + - + - + - + diff --git a/contrib/retep/retep.jpx b/contrib/retep/retep.jpx index 640df105046..746137dd306 100644 --- a/contrib/retep/retep.jpx +++ b/contrib/retep/retep.jpx @@ -4,6 +4,12 @@ + + + + + + @@ -15,7 +21,7 @@ - + @@ -25,7 +31,7 @@ - + @@ -34,13 +40,17 @@ - + + + + + diff --git a/contrib/retep/uk/org/retep/dtu/DCollection.java b/contrib/retep/uk/org/retep/dtu/DCollection.java new file mode 100644 index 00000000000..e97fc067c43 --- /dev/null +++ b/contrib/retep/uk/org/retep/dtu/DCollection.java @@ -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-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) + return null; + + return elements[id]; + } + + /** + * Repairs the collection, ensuring all id's are correct + */ + public synchronized void repair() + { + for(int i=0;iNB: 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;iNB: 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 index 00000000000..7a8321741cd --- /dev/null +++ b/contrib/retep/uk/org/retep/dtu/DNode.java @@ -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=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=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;i0) { + 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;i1) { + // 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 index 00000000000..c272f1d005a --- /dev/null +++ b/contrib/retep/uk/org/retep/util/Logger.java @@ -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(aLevelALL) { + 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(aLevellevel) { + return; + } + + logger.print("Logger:"); + logger.print(levels[aLevel]); + logger.print(aMsg); + if(args!=null) { + for(int a=0;a0) { + 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 index 00000000000..b91e6dc49df --- /dev/null +++ b/contrib/retep/uk/org/retep/util/hba/Record.java @@ -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 index 00000000000..a04babde3a2 --- /dev/null +++ b/contrib/retep/uk/org/retep/util/misc/IPAddress.java @@ -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 index 00000000000..7bed62c4d28 --- /dev/null +++ b/contrib/retep/uk/org/retep/util/misc/PropertiesIO.java @@ -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;i0x7e)) { + 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 index 00000000000..763676cfb3b --- /dev/null +++ b/contrib/retep/uk/org/retep/util/misc/WStringTokenizer.java @@ -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=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 index 00000000000..6f2c73bc68f --- /dev/null +++ b/contrib/retep/uk/org/retep/util/proped/Main.java @@ -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 index 00000000000..b5c19e10876 --- /dev/null +++ b/contrib/retep/uk/org/retep/util/proped/PropertyEditor.java @@ -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