Index: /applications/editors/josm/plugins/seachart/jrenderpgsql/README
===================================================================
--- /applications/editors/josm/plugins/seachart/jrenderpgsql/README	(revision 35091)
+++ /applications/editors/josm/plugins/seachart/jrenderpgsql/README	(revision 35091)
@@ -0,0 +1,40 @@
+jrenderpgsql
+------------
+
+This is a modified version of "jrender".
+
+Instead of loading .osm files from a pre-defined directory structure
+like "jrender" does, this program issues an SQL query and fetches
+seamark data from PostGIS, where it is expected to have been imported
+with a standard osm2pgsql import using the "--hstore" flag.
+
+It then generates an XML document from the PostGIS data which is
+good enough to be processed by the S57 library routines as usual.
+
+
+BUILDING
+--------
+
+You might have to adapt some paths in the build.xml for the PostGIS
+and PostgreSQL JDBC drivers to be found.
+
+
+RUNNING
+-------
+
+java -jar jrenderpgsql.jar "jdbc:postgresql:///mydatabae?user=myuser&password=mypassword" 14 8663 5304 tile.png 
+
+Optional command line flags --debug (outputs the OSM XML), 
+and --tilesize and --scale (use --tilesize 512 --scale 2.0 
+for "retina" tiles)
+
+
+STATUS
+------
+
+Seems to work at least for z12 and larger, but has received little 
+testing.
+
+Author: Frederik Ramm. 
+License: GPL v2.0 or later
+
Index: /applications/editors/josm/plugins/seachart/jrenderpgsql/build.xml
===================================================================
--- /applications/editors/josm/plugins/seachart/jrenderpgsql/build.xml	(revision 35091)
+++ /applications/editors/josm/plugins/seachart/jrenderpgsql/build.xml	(revision 35091)
@@ -0,0 +1,30 @@
+<project name="jrenderpgsql" default="dist" basedir=".">
+  <property name="src" location="src"/>
+  <property name="srcmain" location="../src"/>
+  <property name="build" location="build"/>
+  <property name="jarfile" location="./jrenderpgsql.jar"/>
+
+  <target name="init">
+    <mkdir dir="${build}"/>
+  </target>
+
+  <target name="compile" depends="init" description="compile the source " >
+    <javac classpath="/usr/share/java/postgis-jdbc-2.2.1.jar:/usr/share/java/postgresql.jar" includeantruntime="false" sourcepath="${srcmain}" srcdir="${src}" destdir="${build}" encoding="UTF-8" />
+  </target>
+
+  <target name="dist" depends="compile" description="generate the distribution" >
+    <jar jarfile="${jarfile}" basedir="${build}" manifestencoding="UTF-8">
+      <zipgroupfileset dir="/usr/share/java" includes="postgresql.jar" />
+      <zipgroupfileset dir="/usr/share/java" includes="postgis-jdbc-2.2.1.jar" />
+      <manifest>
+        <attribute name="Main-Class" value="jrenderpgsql.JrenderPgsql"/>
+        <attribute name="Class-Path" value="$jarfile"/>
+      </manifest>
+    </jar>
+  </target>
+
+  <target name="clean" description="clean up" >
+    <delete dir="${build}"/>
+    <delete file="${jarfile}"/>
+  </target>
+</project>
Index: /applications/editors/josm/plugins/seachart/jrenderpgsql/src/jrenderpgsql/JrenderPgsql.java
===================================================================
--- /applications/editors/josm/plugins/seachart/jrenderpgsql/src/jrenderpgsql/JrenderPgsql.java	(revision 35091)
+++ /applications/editors/josm/plugins/seachart/jrenderpgsql/src/jrenderpgsql/JrenderPgsql.java	(revision 35091)
@@ -0,0 +1,428 @@
+// License: GPL. For details, see LICENSE file.
+package jrenderpgsql;
+
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.geom.Point2D;
+import java.awt.image.BufferedImage;
+import java.io.BufferedReader;
+import java.io.StringReader;
+import java.io.ByteArrayOutputStream;
+import java.io.FileOutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import javax.imageio.ImageIO;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import render.ChartContext;
+import render.Renderer;
+import s57.S57map;
+import s57.S57map.Feature;
+import s57.S57map.Snode;
+import s57.S57osm;
+
+import org.postgis.Point;
+import org.postgis.Geometry;
+import org.postgis.LineString;
+import org.postgis.Polygon;
+import org.postgis.LinearRing;
+import org.postgis.PGgeometry;
+
+/**
+ * @author Frederik Ramm
+ * Based on Jrender by Malcom Herring
+ */
+public final class JrenderPgsql {
+    private JrenderPgsql() {
+        // Hide default constructor for utilities classes
+    }
+
+    public final static double RADIUS = 6378137.0;
+    public final static double MERCATOR_WIDTH  = 40075016.685578488;
+    public final static double MERCATOR_OFFSET = 20037508.342789244;
+
+    static String dburl;
+    static String outfile;
+    static int xtile;
+    static int ytile;
+    static int zoom;
+    static S57map map;
+    static int empty;
+
+    static StringBuilder nodebuf;
+    static StringBuilder waybuf;
+    static int nid;
+    static int wid;
+
+    static double scale = 1.0;
+    static double tilesize = 256; 
+    static double border = 256;
+
+    static double minlon = 999.0;
+    static double minlat = 999.0;
+    static double maxlat = -999.0;
+    static double maxlon = -999.0;
+
+    static boolean debug = false;
+
+    static HashMap<Point,Integer> nodes = new HashMap<Point, Integer>();
+
+    /**
+     *  helper for adding a <node> tag to the pseudo OSM XML
+     *  @param p the node coordinates
+     *  @param close whether to close the XML tag or not
+     *  @returns ID of the node created (re-uses IDs at same location)
+     */
+    private static int addnode(Point p, boolean close)
+    {
+        if (close)
+        {
+            Integer existing = nodes.get(p);
+            if (existing != null) return existing.intValue();
+        }
+
+        double lat = p.getY();
+        double lon = p.getX();
+        if (lat > maxlat) maxlat = lat;
+        if (lat < minlat) minlat = lat;
+        if (lon > maxlon) maxlon = lon;
+        if (lon < minlon) minlon = lon;
+        nodebuf.append("<node id=\"");
+        nodebuf.append(++nid);
+        nodebuf.append("\" lat=\"");
+        nodebuf.append(p.getY());
+        nodebuf.append("\" lon=\"");
+        nodebuf.append(p.getX());
+        nodebuf.append("\" version=\"1\" user=\"1\" uid=\"1\" changeset=\"1\" timestamp=\"1980-01-01T00:00:00Z\"");
+        if (close) nodebuf.append("/");
+        nodebuf.append(">\n");
+        nodes.put(p, new Integer(nid));
+        return nid;
+    }
+
+    /**
+     *  helper for adding a PostGIS geometry to pseudo OSM XML
+     *  @returns either the node or the way string buffer, depending on geom type
+     */
+    private static StringBuilder decode_geom(PGgeometry geom)
+    {
+        if (geom.getGeoType() == Geometry.POINT) 
+        {
+            addnode((Point) geom.getGeometry(), false);
+            return nodebuf;
+        }
+        else if (geom.getGeoType() == Geometry.LINESTRING)
+        {
+            LineString ls = (LineString) geom.getGeometry();
+            waybuf.append("<way id=\"");
+            waybuf.append(++wid);
+            waybuf.append("\" version=\"1\" user=\"1\" uid=\"1\" changeset=\"1\" timestamp=\"1980-01-01T00:00:00Z\">\n");
+            for (int i=0; i < ls.numPoints(); i++)
+            {
+                int n = addnode ((Point) ls.getPoint(i), true);
+                waybuf.append("<nd ref=\"" + n + "\" />\n");
+            }
+            return waybuf;
+        }
+        else if (geom.getGeoType() == Geometry.POLYGON)
+        {
+            Polygon po = (Polygon) geom.getGeometry();
+            if (po.numRings() > 1)
+            {
+                System.err.println("warning: polygons with holes not supported");
+            }
+            LinearRing lr = (LinearRing) po.getRing(0);
+            waybuf.append("<way id=\"");
+            waybuf.append(++wid);
+            waybuf.append("\" version=\"1\" user=\"1\" uid=\"1\" changeset=\"1\" timestamp=\"1980-01-01T00:00:00Z\">\n");
+            for (int i=0; i < lr.numPoints(); i++)
+            {
+                int n = addnode ((Point) lr.getPoint(i), true);
+                waybuf.append("<nd ref=\"" + n + "\" />\n");
+            }
+            return waybuf;
+        }
+        
+        System.err.println("bad geo type: " + geom.getGeoType());
+        System.exit(-1);
+        return null;
+    }
+
+    /**
+     *  adds closing XML tag depending on geometry 
+     */
+    private static void finalize_geom(PGgeometry geom)
+    {
+        if (geom.getGeoType() == Geometry.POINT) 
+        {
+            nodebuf.append("</node>\n");
+        }
+        else if (geom.getGeoType() == Geometry.LINESTRING)
+        {
+            waybuf.append("</way>\n");
+        }
+        else if (geom.getGeoType() == Geometry.POLYGON)
+        {
+            waybuf.append("</way>\n");
+        }
+    }
+
+    /**
+     *  helper for escaping and writing a tag
+     */
+    private static void write_tag(StringBuilder buf, String k, String v)
+    {
+       buf.append("<tag k=\"" + k.replace("&", "&amp;").replace("\"", "&quot;") + "\" v=\"" + v.replace("&", "&amp;").replace("\"", "&quot;") + "\" />\n");
+    }
+
+    /**
+     * converts a radian latitude to spherical Mercator Y
+     */
+    public static double radlat2y(double aLat) 
+    {
+       return Math.log(Math.tan(Math.PI / 4 + aLat / 2)) * RADIUS;
+    }
+
+    /**
+     * converts a radian longitude to spherical Mercator X
+     */
+    public static double radlon2x(double aLong) 
+    {
+       return aLong * RADIUS;
+    }
+
+    public static void main(String[] args) throws Exception 
+    {
+
+        // parse command line
+        // ------------------
+
+        ArrayList<String> remain = new ArrayList<String>();
+
+        for (int i=0; i<args.length; i++) 
+        {
+            if (args[i].equals("--scale"))
+            {
+                scale = Double.parseDouble(args[++i]);
+            }
+            else if (args[i].equals("--tilesize"))
+            {
+                tilesize = Double.parseDouble(args[++i]);
+            }
+            else if (args[i].equals("--debug"))
+            {
+                debug = true;
+            }
+            else
+            {
+                remain.add(args[i]);
+            }
+        }
+
+        if (remain.size() < 5) 
+        {
+            System.err.println("Usage: java -jar jrenderpgsql.jar [--scale x] [--tilesize x] [--debug] <database connection string> <zoom> <xtile> <ytile> <outputfile>");
+            System.err.println("format of the database connection string: jdbc:postgresql:///dbname?user=myuser&password=mypwd");
+            System.exit(-1);
+        }
+
+        dburl = remain.get(0);
+        zoom = Integer.parseInt(remain.get(1));
+        xtile = Integer.parseInt(remain.get(2));
+        ytile = Integer.parseInt(remain.get(3));
+        outfile = remain.get(4);
+
+        nodebuf = new StringBuilder();
+        waybuf = new StringBuilder();
+
+        Connection c = null;
+        try 
+        {
+            Class.forName("org.postgresql.Driver");
+            c = DriverManager.getConnection(dburl);
+        } 
+        catch (Exception e) 
+        {
+            e.printStackTrace();
+            System.err.println(e.getClass().getName()+": "+e.getMessage());
+            System.exit(-1);
+        }
+
+        double pow = Math.pow(2, zoom);
+        border = (zoom < 12) ? (256 / (int) (Math.pow(2, (11 - zoom)))) : 256;
+        double border_merc = MERCATOR_WIDTH / 256 / pow * border;
+
+        // calculate spherical mercator bounds of the requested tile.
+
+        double west = (xtile) * MERCATOR_WIDTH / pow - MERCATOR_OFFSET;
+        double east = (xtile + 1) * MERCATOR_WIDTH / pow - MERCATOR_OFFSET;
+        double north = (pow - ytile) * MERCATOR_WIDTH / pow - MERCATOR_OFFSET;
+        double south = (pow - ytile - 1) * MERCATOR_WIDTH / pow - MERCATOR_OFFSET;
+
+        // request data from PostGIS
+        // -------------------------
+        // This assumes the given database has the usual osm2pgsql tables, 
+        // and a "tags" column (i.e. imported with --hstore). Caution, if
+        // the import was made with --hstore-match-only then not all
+        // seamarks will be present.
+
+        Statement stmt = c.createStatement();
+        for (String table : new String[] { "planet_osm_point", "planet_osm_line", "planet_osm_polygon" }) 
+        {
+            String query = "SELECT st_transform(way,4326) as mygeom, * FROM " 
+              + table + " WHERE tags?'seamark:type' AND way && " 
+              + "st_setsrid(st_makebox2d(st_makepoint(" + (west - border_merc) 
+              + "," + (south - border_merc) + "), st_makepoint(" 
+              + (east + border_merc) + "," + (north + border_merc) + ")),3857)";
+            if (debug) System.out.println(query);
+            ResultSet rs = stmt.executeQuery(query);
+
+            // analyse the result
+            // ------------------
+            // The result will contain of these columns:
+            // 1. the spherical mercator geometry column "way" which we ignore
+            // 2. the new geometry column "mygeom" which we use
+            // 3. the "tags" column which requires special treatment
+            // 4. lots of other, "normal" columns which we treat "normally"
+
+            ResultSetMetaData meta = rs.getMetaData();
+            int colCount = meta.getColumnCount();
+            int ogeomcol = 0;
+            int geomcol = 0;
+            int tagscol = 0;
+            for (int i = 1; i<colCount; i++)
+            {
+                String n = meta.getColumnName(i);
+                if (n.equals("way")) {
+                    ogeomcol = i;
+                } else if (n.equals("tags")) {
+                    tagscol = i;
+                } else if (n.equals("mygeom")) {
+                    geomcol = i;
+                }
+            }
+            if (geomcol == 0) 
+            {
+                System.err.println("no geometry column in table " + table + "\n");
+                System.exit(-1);
+            }
+            if (tagscol == 0) 
+            {
+                System.err.println("no tags column in table " + table + "\n");
+                System.exit(-1);
+            }
+
+            // read data 
+            // ---------
+            // for each row, write a geomtry to the output stream, and
+            // assemble its tags from the "normal" columns plus the "tags"
+            // column.
+
+            while (rs.next())
+            {
+                PGgeometry geom = (PGgeometry) rs.getObject(geomcol);
+                StringBuilder currentbuf = decode_geom(geom);
+
+                for (int i = 1; i<colCount; i++)
+                {
+                    if (i==ogeomcol) continue;
+                    if (i==geomcol) continue;
+                    if (i==tagscol) continue;
+                    String k = meta.getColumnName(i);
+                    String v = rs.getString(i);
+                    if (v != null)
+                    {
+                        write_tag(currentbuf, k, v);
+                    }
+                }
+                PGHStore h = new PGHStore(rs.getString(tagscol));
+                for (Object k : h.keySet())
+                {
+                    write_tag(currentbuf, (String) k, (String) (h.get(k)));
+                }
+
+                finalize_geom(geom);
+            }
+        }
+
+        // done querying database. build pseudo OSM file
+        
+        StringBuilder combinedBuf = new StringBuilder();
+        combinedBuf.append("<osm version=\"0.6\">\n");
+
+        combinedBuf.append("<bounds minlon=\"");
+        combinedBuf.append(minlon);
+        combinedBuf.append("\" maxlon=\"");
+        combinedBuf.append(maxlon);
+        combinedBuf.append("\" minlat=\"");
+        combinedBuf.append(minlat);
+        combinedBuf.append("\" maxlat=\"");
+        combinedBuf.append(maxlat);
+        combinedBuf.append("\" />\n");
+        
+        combinedBuf.append(nodebuf);
+        combinedBuf.append(waybuf);
+        combinedBuf.append("</osm>");
+
+        if (debug) System.out.println(combinedBuf);
+
+        // The pseudo OSM file is now complete, and we feed it to the S57
+        // library where it will be parsed again.
+
+        BufferedReader in = new BufferedReader(new StringReader(combinedBuf.toString()));
+        map = new S57map(true);
+        S57osm.OSMmap(in, map, false);
+        in.close();
+
+        // this ChartContext is mainly there for converting lat/lon to
+        // tile x/y pixel coordinates.
+
+        ChartContext context = new ChartContext() {
+            public Point2D getPoint(Snode coord) {
+                double x = border + (radlon2x(coord.lon) - west) * 256 * scale * pow / MERCATOR_WIDTH;
+                double y = tilesize + border - ((radlat2y(coord.lat) - south) * scale * 256 * pow / MERCATOR_WIDTH);
+                System.out.println("point convert from " + Math.toDegrees(coord.lat)+","+Math.toDegrees(coord.lon)+" to "+x+","+y);
+                return new Point2D.Double(x, y);
+            }
+
+            public double mile(Feature feature) {
+                return 21600 / 256 / pow / scale;
+            }
+
+            public boolean clip() {
+                return false;
+            }
+
+            public Color background(S57map map) {
+                return new Color(0, true);
+            }
+
+            public RuleSet ruleset() {
+                return RuleSet.SEAMARK;
+            }
+        };
+
+        // invoke renderer, and write file to disk
+        // ---------------------------------------
+
+        BufferedImage img = new BufferedImage((int)tilesize, (int)tilesize, BufferedImage.TYPE_INT_ARGB);
+        Graphics2D g2 = img.createGraphics();
+        g2.translate(-border, -border);
+        Renderer.reRender(g2, new Rectangle((int)tilesize, (int)tilesize), zoom, scale * Math.pow(2, (zoom - 12)), map, context);
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        ImageIO.write(img, "png", bos);
+        FileOutputStream fos = new FileOutputStream(outfile);
+        bos.writeTo(fos);
+        fos.close();
+        System.exit(0);
+    }
+}
+
Index: /applications/editors/josm/plugins/seachart/jrenderpgsql/src/jrenderpgsql/PGHStore.java
===================================================================
--- /applications/editors/josm/plugins/seachart/jrenderpgsql/src/jrenderpgsql/PGHStore.java	(revision 35091)
+++ /applications/editors/josm/plugins/seachart/jrenderpgsql/src/jrenderpgsql/PGHStore.java	(revision 35091)
@@ -0,0 +1,363 @@
+/*-------------------------------------------------------------------------
+*
+* A preliminary version of a custom type wrapper for hstore data.
+* Once it gets some testing and cleanups it will go into the official
+* PG JDBC driver, but stick it here for now because we need it sooner.
+*
+* Copyright (c) 2009, PostgreSQL Global Development Group
+*
+* IDENTIFICATION
+*   $PostgreSQL$
+*
+*-------------------------------------------------------------------------
+*/
+package jrenderpgsql;
+
+import java.io.Serializable;
+
+import java.sql.SQLException;
+
+import java.util.Set;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Collection;
+
+import org.postgresql.util.PGobject;
+/**
+ * This implements a class that handles the PostgreSQL contrib/hstore type
+ */
+public class PGHStore extends PGobject implements Serializable, Cloneable, Map
+{
+    private final static long serialVersionUID = 1;
+    private Map _map;
+
+    /**
+     * required by the driver
+     */
+    public PGHStore()
+    {
+        setType("hstore");
+        _map = new HashMap();
+    }
+
+    /**
+     * Initialize a hstore with a given string representation
+     *
+     * @param value String representated hstore
+     * @throws SQLException Is thrown if the string representation has an unknown format
+     * @see #setValue(String)
+     */
+    public PGHStore(String value)
+    throws SQLException
+    {
+        this();
+        setValue(value);
+    }
+
+    public PGHStore(Map map)
+    {
+        this();
+        setValue(map);
+    }
+
+    public void setValue(Map map)
+    {
+        _map = map;
+    }
+
+    /**
+     */
+    public void setValue(String value)
+    throws SQLException
+    {
+        Parser p = new Parser();
+        _map = p.parse(value);
+    }
+
+    /**
+     * Returns the stored information as a string
+     *
+     * @return String represented hstore
+     */
+    public String getValue()
+    {
+        StringBuffer buf = new StringBuffer();
+        Iterator i = _map.keySet().iterator();
+        boolean first = true;
+        while (i.hasNext()) {
+            Object key = i.next();
+            Object value = _map.get(key);
+
+            if (first) {
+                first = false;
+            } else {
+                buf.append(',');
+            }
+
+            writeValue(buf, key);
+            buf.append("=>");
+            writeValue(buf, value);
+        }
+
+        return buf.toString();
+    }
+
+    private static void writeValue(StringBuffer buf, Object o) {
+        if (o == null) {
+            buf.append("NULL");
+            return;
+        }
+
+        String s = o.toString();
+
+        buf.append('"');
+        for (int i=0; i<s.length(); i++) {
+            char c = s.charAt(i);
+            if (c == '"' || c == '\\') {
+                buf.append('\\');
+            }
+            buf.append(c);
+        }
+        buf.append('"');
+    }
+
+
+    /**
+     * Returns whether an object is equal to this one or not
+     *
+     * @param obj Object to compare with
+     * @return true if the two hstores are identical
+     */
+    public boolean equals(Object obj)
+    {
+        if (obj == null)
+            return false;
+
+        if (obj == this)
+            return true;
+
+        if (!(obj instanceof PGHStore))
+            return false;
+
+        return _map.equals(((PGHStore)obj)._map);
+
+    }
+
+    private static class Parser {
+        private String value;
+        private int ptr;
+        private StringBuffer cur;
+        private boolean escaped;
+
+        private List keys;
+        private List values;
+
+        private final static int GV_WAITVAL = 0;
+        private final static int GV_INVAL = 1;
+        private final static int GV_INESCVAL = 2;
+        private final static int GV_WAITESCIN = 3;
+        private final static int GV_WAITESCESCIN = 4;
+
+        private final static int WKEY = 0;
+        private final static int WVAL = 1;
+        private final static int WEQ = 2;
+        private final static int WGT = 3;
+        private final static int WDEL = 4;
+
+        public Parser() {
+        }
+
+    	@SuppressWarnings("unchecked")
+        private Map parse(String value) throws SQLException {
+            this.value = value;
+            ptr = 0;
+            keys = new ArrayList();
+            values = new ArrayList();
+            
+            parseHStore();
+
+            Map map = new HashMap();
+            for (int i=0; i<keys.size(); i++) {
+                map.put(keys.get(i), values.get(i));
+            }
+
+            return map;
+        }
+
+        private boolean getValue(boolean ignoreEqual) throws SQLException {
+            int state = GV_WAITVAL;
+
+            cur = new StringBuffer();
+            escaped = false;
+
+            while (true) {
+                boolean atEnd = (value.length() == ptr);
+                char c = '\0';
+                if (!atEnd) {
+                    c = value.charAt(ptr);
+                }
+
+                if (state == GV_WAITVAL) {
+                    if (c == '"') {
+                        escaped = true;
+                        state = GV_INESCVAL;
+                    } else if (c == '\0') {
+                        return false;
+                    } else if (c == '=' && !ignoreEqual) {
+                        throw new SQLException("KJJ");
+                    } else if (c == '\\') {
+                        state = GV_WAITESCIN;
+                    } else if (!Character.isWhitespace(c)) {
+                        cur.append(c);
+                        state = GV_INVAL;
+                    }
+                } else if (state == GV_INVAL) {
+                    if (c == '\\') {
+                        state = GV_WAITESCIN;
+                    } else if (c == '=' && !ignoreEqual) {
+                        ptr--;
+                        return true;
+                    } else if (c == ',' && ignoreEqual) {
+                        ptr--;
+                        return true;
+                    } else if (Character.isWhitespace(c)) {
+                        return true;
+                    } else if (c == '\0') {
+                        ptr--;
+                        return true;
+                    } else {
+                        cur.append(c);
+                    }
+                } else if (state == GV_INESCVAL) {
+                    if (c == '\\') {
+                        state = GV_WAITESCESCIN;
+                    } else if (c == '"') {
+                        return true;
+                    } else if (c == '\0') {
+                        throw new SQLException("KJJ, unexpected end of string");
+                    } else {
+                        cur.append(c);
+                    }
+                } else if (state == GV_WAITESCIN) {
+                    if (c == '\0') {
+                        throw new SQLException("KJJ, unexpected end of string");
+                    }
+
+                    cur.append(c);
+                    state = GV_INVAL;
+                } else if (state == GV_WAITESCESCIN) {
+                    if (c == '\0') {
+                        throw new SQLException("KJJ, unexpected end of string");
+                    }
+
+                    cur.append(c);
+                    state = GV_INESCVAL;
+                } else {
+                    throw new SQLException("KJJ");
+                }
+
+                ptr++;
+            }
+        }
+
+    	@SuppressWarnings("unchecked")
+        private void parseHStore() throws SQLException {
+            int state = WKEY;
+            escaped = false;
+
+            while (true) {
+                char c = '\0';
+                if (ptr < value.length()) {
+                    c = value.charAt(ptr);
+                }
+
+                if (state == WKEY) {
+                    if (!getValue(false))
+                        return;
+
+                    keys.add(cur.toString());
+                    cur = null;
+                    state = WEQ;
+                } else if (state == WEQ) {
+                    if (c == '=') {
+                        state = WGT;
+                    } else if (state == '\0') {
+                        throw new SQLException("KJJ, unexpected end of string");
+                    } else if (!Character.isWhitespace(c)) {
+                        throw new SQLException("KJJ, syntax err");
+                    }
+                } else if (state == WGT) {
+                    if (c == '>') {
+                        state = WVAL;
+                    } else if (c == '\0') {
+                        throw new SQLException("KJJ, unexpected end of string");
+                    } else {
+                        throw new SQLException("KJJ, syntax err [" + c + "] at " + ptr);
+                    }
+                } else if (state == WVAL) {
+                    if (!getValue(true)) {
+                        throw new SQLException("KJJ, unexpected end of string");
+                    }
+
+                    String val = cur.toString();
+                    cur = null;
+                    if (!escaped && "null".equalsIgnoreCase(val)) {
+                        val = null;
+                    }
+
+                    values.add(val);
+                    state = WDEL;
+                } else if (state == WDEL) {
+                    if (c == ',')
+                    {
+                        state = WKEY;
+                    } else if (c == '\0') {
+                        return;
+                    } else if (!Character.isWhitespace(c)) {
+                        throw new SQLException("KJJ, syntax err");
+                    }
+                } else {
+                    throw new SQLException("KJJ unknown state");
+                }
+
+                ptr++;
+            }
+        }
+
+    }
+
+
+    // Farm out all the work to the real underlying map.
+
+    public void clear() { _map.clear(); }
+
+    public boolean containsKey(Object key) { return _map.containsKey(key); }
+
+    public boolean containsValue(Object value) { return _map.containsValue(value); }
+
+    public Set entrySet() { return _map.entrySet(); }
+
+    public Object get(Object key) { return _map.get(key); }
+
+    public int hashCode() { return _map.hashCode(); }
+
+    public boolean isEmpty() { return _map.isEmpty(); }
+
+    public Set keySet() { return _map.keySet(); }
+
+    @SuppressWarnings("unchecked")
+    public Object put(Object key, Object value) { return _map.put(key, value); }
+
+    @SuppressWarnings("unchecked")
+    public void putAll(Map m) { _map.putAll(m); }
+
+    public Object remove(Object key) { return _map.remove(key); }
+
+    public int size() { return _map.size(); }
+
+    public Collection values() { return _map.values(); }
+
+}
