/*
* This file has been copied from the following location:
* http://archives.postgresql.org/pgsql-jdbc/2009-12/msg00037.php
*
* PostgreSQL code is typically under a BSD licence.
* http://jdbc.postgresql.org/license.html
*/
/*-------------------------------------------------------------------------
*
* 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 org.openstreetmap.osmosis.hstore;
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<String, String>
{
private final static long serialVersionUID = 1;
private Map<String, String> _map;
/**
* required by the driver
*/
public PGHStore()
{
setType("hstore");
_map = new HashMap<String, String>();
}
/**
* 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<String, String> map)
{
this();
setValue(map);
}
public void setValue(Map<String, String> 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()
{
StringBuilder builder = new StringBuilder();
Iterator<String> i = _map.keySet().iterator();
boolean first = true;
while (i.hasNext()) {
Object key = i.next();
Object value = _map.get(key);
if (first) {
first = false;
} else {
builder.append(',');
}
writeValue(builder, key);
builder.append("=>");
writeValue(builder, value);
}
return builder.toString();
}
private static void writeValue(StringBuilder 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 StringBuilder cur;
private boolean escaped;
private List<String> keys;
private List<String> 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() {
}
private Map<String, String> parse(String value) throws SQLException {
this.value = value;
ptr = 0;
keys = new ArrayList<String>();
values = new ArrayList<String>();
parseHStore();
Map<String, String> map = new HashMap<String, String>();
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 StringBuilder();
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++;
}
}
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<Map.Entry<String, String>> entrySet() { return _map.entrySet(); }
public String get(Object key) { return _map.get(key); }
public int hashCode() { return _map.hashCode(); }
public boolean isEmpty() { return _map.isEmpty(); }
public Set<String> keySet() { return _map.keySet(); }
public String put(String key, String value) { return _map.put(key, value); }
public void putAll(Map<? extends String, ? extends String> m) { _map.putAll(m); }
public String remove(Object key) { return _map.remove(key); }
public int size() { return _map.size(); }
public Collection<String> values() { return _map.values(); }
}