org.faul.jql.core.Jql.java Source code

Java tutorial

Introduction

Here is the source code for org.faul.jql.core.Jql.java

Source

/*
 * This file is part of JQL.
 *
 * JQL is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * JQL is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Jql.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.faul.jql.core;

import java.io.ByteArrayInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.faul.jql.utils.RemoteObjectHandler;
import org.gibello.zql.ZFromItem;
import org.gibello.zql.ZDelete;
import org.gibello.zql.ZDropTable;
import org.gibello.zql.ZInsert;
import org.gibello.zql.ZListTables;
import org.gibello.zql.ZLoadFile;
import org.gibello.zql.ZLockTable;
import org.gibello.zql.ZQuery;
import org.gibello.zql.ZSaveFile;
import org.gibello.zql.ZStatement;
import org.gibello.zql.ZTransactStmt;
import org.gibello.zql.ZUnLockTable;
import org.gibello.zql.ZUpdate;
import org.gibello.zql.ZUse;
import org.gibello.zql.ZqlParser;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.Scriptable;

import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

/**
 * Implements the JSON Query Language for JAVA. This object is heart of the system
 * all entry points into the JSON database are made through this object.
 * 
 * @author Ben Faul
 * 
 */
public class Jql {

    //static transient Gson gson = null;
    static transient Gson gsonPretty = null;
    static transient GsonBuilder gx = new GsonBuilder();// The static json

    static Gson nopretty = new Gson();
    static String sepCSV = ",";

    public static ObjectMapper mapper = new ObjectMapper();
    static {
        mapper.setSerializationInclusion(Include.NON_NULL);
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    static {
        gx.registerTypeAdapter(Object.class, new RemoteObjectHandler());
        gsonPretty = gx.setPrettyPrinting().create();
    }

    /**
     * Return a linked hashmap object from the JSON formatted data string. The JSON data is stored as a List of Map objects.
     * 
     * @param data
     *            String. The JSON data to convert.
     * @return Object Map. The list of hashmaps that represents the object defined in the data parameter.
     * @throws Exception. Throws
     *             exception on badly formed JSON data.
     */
    public static Map<String, Object> fromJson(String data) throws Exception {
        return (Map<String, Object>) mapper.readValue(data, Map.class);
    }

    /**
     * Given an object File::/filename, ArrayList or JSON source, convert to a
     * JSON database or a FileReader. When given a file name, the contents of the file is converted to JSON.
     * If the object parameter is a list of objects, then this is converted into a JSOn database.
     * If the object parameter is a string in JSON format, it will be used to create a JSON database.
     * 
     * @param source
     *            . The source of the database.
     * @return Object. The List of Hashmaps that represent the data formed by
     * @throws Exception. Throws exceptions on i/o errors and badly formed JSON.
     */
    public static Object jsqlConvert(Object source) throws Exception {
        if (source instanceof String) {
            String data = (String) source;
            if (data.startsWith("file://")) {
                data = data.substring(7);
                FileReader fr = new FileReader(data);
                return fr;
            } else {
                String content = mapper.writer().writeValueAsString(Object.class);
                return content;
            }
        }
        if (source instanceof ArrayList) {
            return source;
        }

        /**
         * Convert to arraylist;
         */

        String data = mapper.writer().writeValueAsString(source);
        return mapper.readValue(data, List.class);
    }

    /**
     * Convert an object of a linked hashmap into a JSON string. Pass the object to
     * this method and will return a  the JSON formatted compressed string representation of that object.
     * 
     * @param convert
     *            Object. The object to convert to a JSON string.
     * @return String. The string representation of the Object.
     * @throws Exception. Throws
     *             exceptions if the Gson converter could not parse the input
     *             string.
     */
    public static String toJson(Object convert) throws Exception {
        String content = mapper.writer().writeValueAsString(convert);
        return content;
    }

    /**
     * Convert an object into a pretty printed JSON string. Pass the object to
     * this method and will return a JSON formatted string representation of that object.
     * 
     * @param convert
     *            Object. The object to convert to JSON string.
     * @param pretty
     *            boolean. Set to true to get pretty output.
     * @return String. The returned object.
     * @throws Exception. Throws exception on bad JSON.
     *             exceptions on JSON errors.
     */
    public static String toJson(Object convert, boolean pretty) throws Exception {
        if (pretty) {
            String content = mapper.writer().withDefaultPrettyPrinter().writeValueAsString(convert);
            return content;
        } else {
            return toJson(convert);
        }
    }

    transient Map<String, Map<String, Object>> dbMap = new HashMap<String, Map<String, Object>>();

    /**
     * Add an empty table to the database.
     * 
     * @param name
     *            String. The name of the empty table
     */
    Map<String, Double> indexes;

    transient Object intermediateResult = null;

    transient String lastName = null;

    /**
     * Lock information
     */
    Map<String, List<String>> locks = new HashMap<String, List<String>>();

    transient ZqlParser parser = null;

    /** Reference to stored procedures in database */
    public JStoredProcedure sp = null;

    List<String> tableLocked = new ArrayList<String>();

    transient Map<String, Object> tableMap = null;

    /**
     * Empty constructor. An empty collection is created with name
     * 'defaultTable'.
     */
    public Jql() {

    }

    /**
     * A FileReader constructor for the JQL. Pass it a database name, and the file reader
     * for a data file of JSON data, the constructor instantiates the Jql object with the
     * contents of that file.
     * 
     * @param dbName. String - the name to associiate with this database,
     * @param source. FileReader - The filereader object to instantiate JQL from.
     * @throws Exception. Throws exception on bad JSON.
     */
    public Jql(String dbName, FileReader source) throws Exception {
        tableMap = (Map<String, Object>) jsqlConvert(source);
        dbMap.put(dbName, tableMap);
    }

    /**
     * Constructor that creates a JQL parser from a Map. Pass a Map object to the constructor
     * and a database is generated from it by the named database.
     * 
     * @param source
     *            HashMap. The hashmap you wish to treat as a database.
     * @param dbName. String - the name to associiate with this database,
     */
    public Jql(String dbName, Map<String, Object> source) {
        tableMap = source;
        dbMap.put(dbName, tableMap);
    }

    /**
     * Create a table from the JSON string. This constructor generates a Jql object from
     * the JSON formatted string.
     * 
     * @param source
     *            String. The JSON string to convert to a database.
     * @throws Exception. Throws
     *             an exception if the source could not be converted to a Map
     *             object.
     */
    public Jql(String dbName, String source) throws Exception {
        tableMap = (Map<String, Object>) jsqlConvert(source);
        dbMap.put(dbName, tableMap);
    }

    /**
     * Add an empty database to the database mapping.
     * 
     * Creates a new hashmap to represent the new database, stored in the
     * database map.
     * 
     * @param key String. The name of the empty database in memory.
     */
    public void addDatabase(String key) {
        Map value = new HashMap();
        dbMap.put(key, value);
    }

    /**
     * Add a record (Map) to the list. Using key/value pairs/.
     * 
     * @param list
     *            List<Map>. A list of Map objects.
     * @param args
     *            Object[]. The varargs list of arguments in the form name,value
     *            ...
     */
    public void addRecord(List<Map> list, Object... args) {
        Map map = new HashMap();
        for (int i = 0; i < args.length; i += 2) {
            map.put(args[i], args[i + 1]);
        }
        list.add(map);
    }

    /**
     * Add an object to a table. If it is a string it is converted to a Map.
     * 
     * @param tableName
     *            String. The name of the table.
     * @param obj
     *            Object, A list object, or a JSON formatted List.
     * @throws Exception. Throws
     *             exceptions if obj is wrong type, or JSON decode error occurs.
     */
    public void addRecord(String tableName, Object obj) throws Exception {
        List list = null;
        if (obj instanceof String) {
            list = mapper.readValue((String) obj, List.class);
        } else
            list = (List) obj;
        List table = (List) tableMap.get(tableName);
        table.add(list);
    }

    /**
     * Adds a record to the table. of the named table using key/value pairs.
     * 
     * @param name
     *            String. The name of the table.
     * @param objects
     *            Object... The name, value pairs of the Map to add to the
     *            table.
     * @throws Exception. Throws
     *             exceptions if the number of objects is odd.
     */
    public void addRecord(String name, Object... objects) throws Exception {
        Map row = new HashMap();

        if (objects.length % 2 != 0) {
            throw new Exception("Odd number of field:value pairs specified...");
        }
        for (int i = 0; i < objects.length; i += 2) {
            row.put((String) objects[i], objects[i + 1]);
        }
        if (tableMap.get(name) == null) {
            tableMap.put(name, new HashMap());
        }
        List table = (List) tableMap.get(name);
        table.add(row);
    }

    /**
     * Programatically add am object to the named dataabase and table. 
     * 
     * @param dbName
     *            String. The name of the database being edited.
     * @param table
     *            String. The name of the table to add the record to.
     * @param obj
     *            Object. A list or JSON representation of a list.
     * @throws Exception. Throws
     *             exceptions of Object is not String or List, or there are JSON
     *             errors.
     */
    public void addRecord(String dbName, String table, Object obj) throws Exception {

        Map<String, Object> db = dbMap.get(dbName);
        List list = null;
        if (obj instanceof String) {
            list = mapper.readValue((String) obj, List.class);
        }
        List records = (List) tableMap.get(table);
        records.add(list);
    }

    /**
     * Adds a new table to the currently used database.
     * @param name Strting. The name of the new table.
     */
    public void addTable(String name) {
        List<Map> table = new ArrayList<Map>();
        tableMap.put(name, table);

        List<Map> schema = (List) tableMap.get("indexes");
        Map m = null;
        if (schema == null) {
            schema = new ArrayList<Map>();
            tableMap.put("indexes", schema);
            indexes = new HashMap();
            schema.add(indexes);
        }
        indexes.put(name, new Double(0));
    }

    /**
     * Add an object List<Map> for use as a database table.
     * 
     * @param key
     *            String. The name of the table.
     * @param value
     *            Object. The object to use as the database. 
     */
    public void addTableMap(String key, Object value) throws Exception {
        String[] parts = key.split("\\.");
        String table = key;
        if (parts.length == 2) {
            tableMap = dbMap.get(parts[0]);
            if (tableMap == null) {
                tableMap = new HashMap();
                dbMap.put(parts[0], tableMap);
            }
            table = parts[1];
        }
        if (tableMap == null) {
            throw new Exception("No database was specified first.");
        }
        tableMap.put(table, value);
        dbMap.put("default", tableMap);
    }

    /**
     * Set the stored procedure map to null. This will set the stored procedures reference to null.
     * If you want to save the stored procedures to disk, don't call closeProcedure(), keep
     * the stored procedures open, then write the database to disk.
     */
    public void closeStoredProcedures() {
        sp = null;
    }

    /**
     * Compile SQL statements into their JQL Statement primitives.
     * 
     * @param sql
     *            String. The SQL statements to compile.
     * @return List<ZStatement>. The executable statements.
     * @throws Exception. Throws
     *             exceptions on SQL errors.
     */
    public List<ZStatement> compile(String sql) throws Exception {
        List<ZStatement> list = new ArrayList();

        if (sql.trim().endsWith(";") == false) {
            sql += ";";
            System.err.println("Warning, sql statement did not end in ';', terminator added automatically.");
        }
        byte[] bytes = sql.getBytes("UTF-8");
        InputStream input = new ByteArrayInputStream(bytes);
        parser = new ZqlParser(input);

        ZStatement st = null;
        Object arg = null;

        intermediateResult = null;
        try {
            while ((st = parser.readStatement()) != null) {
                list.add(st);
            }
        } catch (Exception error) {

        }
        return list;
    }

    /**
     * Return a list of databases loaded in this instance of JQL.
     * 
     * @return List<String>. The list of databases loaded.
     */
    public List<String> databasesLoaded() {
        List<String> list = new ArrayList<String>();
        Set keys = dbMap.keySet();
        Iterator it = keys.iterator();

        for (Entry<String, Map<String, Object>> skeys : dbMap.entrySet()) {
            list.add(skeys.getKey());
        }

        return list;
    }

    /**
     * Perform a delete statement.
     * 
     * @param z
     *            ZDelete. The delete statement compiled from the textual input
     * @throws Exception
     */
    void doDelete(ZDelete z) throws Exception {
        intermediateResult = new JDelete(dbMap, z, (ArrayList) intermediateResult).execute();
        tableMap.put("intermediate", intermediateResult);
    }

    /**
     * Perform a SQL insert statement.
     * 
     * @param z
     *            ZInsert. The insert compiled from a textual insert statement.
     * @throws Exception. Throw
     *             errors on JSON errors.
     */
    void doInsert(ZInsert z) throws Exception {
        intermediateResult = new JInsert(tableMap, z, indexes).execute();
        tableMap.put("intermediate", intermediateResult);
    }

    /**
     * Given an SQL statement, execute it.
     * 
     * @param st
     *            ZStatement. The statement to execute, compiled from the
     *            textual.
     * @throws Exception. Throws
     *             exceptions on JSON errors.
     */
    void doJqlStatement(ZStatement st) throws Exception {
        if (st instanceof ZInsert) { // An SQL insert
            doInsert((ZInsert) st);
            return;
        } else if (st instanceof ZUpdate) {
            doUpdate((ZUpdate) st);
            return;
        } else if (st instanceof ZDelete) {
            doDelete((ZDelete) st);
            return;
        } else if (st instanceof ZQuery) { // An SQL query: query the DB
            doQuery((ZQuery) st);
            return;
        } else if (st instanceof ZDropTable) {
            dropTables((ZDropTable) st);
            return;
        } else if (st instanceof ZSaveFile) {
            if (sp != null)
                sp.stripShells(); // Don't save the js objects themselves.
            new JSaveFile(dbMap, (ZSaveFile) st);
            return;
        } else if (st instanceof ZUse) {
            doUse((ZUse) st);
            return;
        } else if (st instanceof ZLoadFile) {
            doLoad((ZLoadFile) st);
            return;
        }
        if (st instanceof ZTransactStmt) {
            doTransaction((ZTransactStmt) st);
            return;
        } else if (st instanceof ZLockTable) {
            new JLockTable(this, (ZLockTable) st);
            return;
        } else if (st instanceof ZUnLockTable) {
            new JUnLockTable(this, (ZUnLockTable) st);
            return;
        } else if (st instanceof ZListTables) {
            doListTables((ZListTables) st);
            return;
        }
    }

    /**
     * Load the file into the Jql database.
     * 
     * @param z
     *            ZLoadFile. The SQL load construct.
     * @throws Exception. Throws
     *             exceptions on I/O and JSON errors.
     */
    void doLoad(ZLoadFile z) throws Exception {
        JLoad jl = new JLoad(dbMap, z);
        intermediateResult = jl.execute();
        String db = jl.getDbName();
        tableMap = dbMap.get(db);
    }

    /**
     * Perform a select statement.
     * 
     * @param z
     *            ZQuery. The query compiled from a textual select statement.
     * @throws Exception. Throws
     *             exceptions on JSON errors.
     */
    void doQuery(ZQuery z) throws Exception {
        List list = (List) intermediateResult;
        if (list != null && list.size() > 0) {
            if (list.get(0) instanceof String)
                lastName = (String) list.get(0);

        }
        if (intermediateResult == null) {

            List from = z.getFrom(); // FROM part of the query
            ZFromItem table = (ZFromItem) from.get(0);

            // Remember the last name used as it used for commit!
            lastName = table.getTable();

            if (table.getSchema() != null)
                tableMap = dbMap.get(table.getSchema());

            Object x = tableMap.get(lastName);
            intermediateResult = jsqlConvert(x);
        }

        if (intermediateResult == null) {
            throw new Exception("No table mapping found for: " + lastName);
        }

        intermediateResult = new JqlSelect(tableMap, z).results(intermediateResult);
        // intermediateResult = queryJSON(z, intermediateResult);
        tableMap.put("intermediate", intermediateResult);
    }

    /**
     * Perform a COMMIT, saves intermediate table over the last table name used.
     * 
     * @param z
     */
    void doTransaction(ZTransactStmt z) {
        String str = z.getStatement();
        if (str.equalsIgnoreCase("COMMIT")) {
            tableMap.put(lastName, intermediateResult);
        }
    }

    /**
     * Perform an SQL update statement.
     * 
     * @param z
     *            ZUpdate. The compiled update statement from the textual update
     *            statement.
     * @throws Exception. Throws
     *             exceptions on JSON errors.
     */
    void doUpdate(ZUpdate z) throws Exception {
        intermediateResult = new JUpdate(dbMap, z, (ArrayList) intermediateResult).execute();
        tableMap.put("intermediate", intermediateResult);
    }

    /**
     * Execute a USE statement.
     * 
     * @param z
     *            ZUse. The USE construct.
     * @return List<String>. The names of all the tables in the USEd database.
     * @throws Exception. Throws
     *             Exceptions if the database does not exist.
     */
    List<String> doUse(ZUse z) throws Exception {
        String db = z.getDbName();
        tableMap = dbMap.get(db);
        if (tableMap == null) {
            throw new Exception("No such database named: " + db);
        }
        dbMap.put(db, tableMap);
        return tableList();
    }

    /**
     * Links the parser production to listTables() method.
     * @param z ZListTables. The object that represents the return from the listTables() method to the SQLproduction.
     * @throws Exception
     */
    void doListTables(ZListTables z) throws Exception {
        List theList = listTables();
        z.addTablesInfo(theList);
        intermediateResult = theList;
    }

    /**
     * Drop a table.
     * 
     * @param drop
     *            ZDropTable. The SQL construct of drop.
     */
    public void dropTables(ZDropTable drop) {
        List<String> tables = drop.getTables();

        if (tables == null)
            return;

        for (String table : tables) {
            tableMap.remove(table);

            if (indexes != null)
                indexes.remove(table);
        }
    }

    /**
     * Execute a list of precompiled statements, one after another.
     * 
     * @param list
     *            List<ZStatement>. The list of precompiled statements to
     *            execute.
     * @return Object. The table resulting from executing the list.
     * @throws Exception
     */
    public Object execute(List<ZStatement> list) throws Exception {
        for (ZStatement st : list) {
            doJqlStatement(st);
        }
        return intermediateResult;
    }

    /**
     * Execute the SQL statements in the string argument. This is where the classical
     * SQL statements are executed, such as selct, delete, insert, etc.
     * 
     * @param sql
     *            String. The SQL to execute, can be more than one. Each is
     *            terminated with ';'.
     * @return Object. The results of the SQL statements.
     * @throws Exception, Throws errors on bad JSON format and SQL parse errors.
     */
    public Object execute(String sql) throws Exception {
        return execute(sql, null);
    }

    /**
     * Given a textual SQL statement, compile it ad then execute it.
     * 
     * @param sql
     *            String. One or more SQL statements, each terminated with ';'.
     * @param tableInfo
     *            Map<String,Object>. The table name to JAVA object map.
     * @return Object. The results of the statements as an intermediate table.
     * @throws Exception. Throws
     *             exception on SQL and JSON errors.
     */

    public Object execute(String sql, Object tableInfo) throws Exception {

        byte[] bytes = sql.getBytes("UTF-8");
        InputStream input = new ByteArrayInputStream(bytes);
        if (parser == null)
            parser = new ZqlParser(input);
        else
            parser.initParser(input);

        ZStatement st = null;
        try {
            while ((st = parser.readStatement()) != null) {
                doJqlStatement(st);

            }
        } catch (Exception error) {
            if (error.toString().contains("<EOF") == false) {
                error.printStackTrace();
                throw new Exception(error.toString());
            }
        }

        return intermediateResult;
    }

    /**
     * Extend the parser with these built-in functions: sum, min. max, count, and avg.
     * 
     * @param z
     *            ZqlParser. The parser to extend.
     */
    public void extendParser(ZqlParser z) {
        z.addCustomFunction("sum", 1);
        z.addCustomFunction("min", 1);
        z.addCustomFunction("max", 1);
        z.addCustomFunction("count", 1);
        z.addCustomFunction("avg", 1);

        z.addCustomFunction("jql", 1);
    }

    /**
     * Format an object into a pretty printed JSON.
     * 
     * @param test
     *            Object. The object to pretty print.
     * @return String. The pretty printed format of the data to return.
     */

    public String format(Object test) {
        String rets = "";

        if (test instanceof Map == false) {
            rets = gsonPretty.toJson(test);
            return rets;
        }
        List<Map> o = (List) test;
        int i = 0;
        for (Map map : o) {
            i++;
            String data = gsonPretty.toJson(map, HashMap.class);
            rets += data;
            if (i != o.size())
                rets += ",\n";
            else
                rets += "\n";
        }
        return rets;

    }

    /**
     * Given a JSON string. pretty print it.
     * 
     * @param data
     *            String. The unformatted JSON to pretty print.
     * @return String. The formatted JSON.
     */
    public String format(String data) throws Exception {
        String rets = null;
        Object obj = tableMap.get(data);
        if (obj == null) {
            obj = mapper.readValue(data, Object.class);
        }
        rets = mapper.writer().withDefaultPrettyPrinter().writeValueAsString(obj);
        return rets;
    }

    /**
     * Retrieve a CSV file, convert to Maps and append them to the given List.
     * 
     * @param list
     *            List<Map>. The list to append the CSV file onto.
     * @param fr
     *            FileReader. The file reader of the CSV file.
     * @throws Exception. Throws
     *             exceptions on I/O errors
     */
    public void fromCSV(List<Map> list, FileReader fr) throws Exception {
        String data = "";
        char buf[] = new char[4096];
        int rc = 1;
        while (rc > 0) {
            rc = fr.read(buf);
            data += new String(buf, 0, rc);
        }
        fromCSV(list, data);
    }

    /**
     * Read CSV data from a string converts it to Maps and appends them  to the provided list.
     * 
     * @param list
     *            List<Map>. The table to append the CSV data to.
     * @param data
     *            . String. The CSV formatted data.
     * @throws IOException 
     * @throws JsonMappingException 
     * @throws JsonParseException 
     */
    public void fromCSV(List<Map> list, String data) throws JsonParseException, JsonMappingException, IOException {
        String[] tokens = null;
        Map newmap = null;
        String[] lines = data.split("\n");
        tokens = lines[0].split(sepCSV + "(?=([^\"]*\"[^\"]*\")*[^\"]*$)");
        String[] keys = new String[tokens.length];
        String[] types = new String[tokens.length];
        int j = 0;
        for (String t : tokens) {
            setType(t, j, keys, types);
            j++;
        }

        for (int i = 1; i < lines.length; i++) {
            String line = lines[i];
            newmap = new HashMap();
            tokens = line.split(sepCSV + "(?=([^\"]*\"[^\"]*\")*[^\"]*$)");
            j = 0;
            for (String t : tokens) {
                t = t.trim();
                if (types[j].equals("c"))
                    newmap.put(keys[j++], t);
                else if (types[j].equals("n"))
                    newmap.put(keys[j++], new Double(t));
                else {
                    while (t.startsWith("\"")) {
                        t = t.substring(1);
                        t = t.substring(0, t.length() - 1);
                    }
                    Object test = mapper.readValue(t, Object.class);
                    newmap.put(keys[j++], test);
                }
            }
            list.add(newmap);
        }
    }

    /**
     * Retrieve a CSV file and create a table (List of Maps) from it.
     * 
     * @param table
     *            String. The name of the table.
     * @param fr
     *            FileReader. The CSV file to convert.
     * @throws Exception. Throws
     *             exceptions on I/O errors.
     */
    public void fromCSV(String table, FileReader fr) throws Exception {
        List<Map> list = (List) tableMap.get(table);
        fromCSV(list, fr);
    }

    /**
     * Take CSV data from file and append it to the named table.
     * 
     * @param table
     *            String. The table to append to.
     * @param data
     *            String. CSV formatted data.
     */
    public void fromCSV(String table, String data) throws Exception {
        List list = (List) tableMap.get(table);
        fromCSV(list, data);
    }

    /**
     * return the List of tables of the given database name.
     * 
     * @param name
     *            String. The name of the database.
     * @return List<List>. The List of tables in the database.
     */
    public List getDataBase(String name) {
        return (List) tableMap.get(name);
    }

    /**
     * Return a List from the table map of the currently USEd database. This is
     * equivalent to "select * from table;"
     * 
     * @param table
     *            - String. The name of the table.
     * @return List<Map>. The list that is this table, or null if table does not
     *         exist.
     */
    public List<Map> getTable(String table) {
        return (List) tableMap.get(table);
    }

    /**
     * List the tables and their row counts.
     * 
     * @return List<Map>. A list of the tables and their record counts.
     * @throws Exception. Will throw an exception on null Lists.
     */
    public List<Map> listTables() throws Exception {
        List<Map> list = new ArrayList();

        for (Entry<String, Object> entry : tableMap.entrySet()) {
            String key = entry.getKey();
            List records = (List) entry.getValue();
            Map nr = new HashMap();
            nr.put("name", key);
            nr.put("count", records.size());
            if (locked(key))
                nr.put("locked", true);
            else
                nr.put("locked", false);
            list.add(nr);
        }
        return list;
    }

    /**
     * Load a previously saved database from disk.
     * 
     * @param dbname
     *            String, The name of the database after loading it from file.
     * @param filename
     *            String. The name of the file to load.
     * @return List<String>. The list of table namesin the database.
     * @throws Exception. Throws
     *             exceptions fileI/O and JSON parsing errors.
     */
    public List<String> load(String dbname, String filename) throws Exception {
        JLoad jl = new JLoad(dbMap, dbname, filename);
        intermediateResult = jl.execute();
        String db = jl.getDbName();
        tableMap = dbMap.get(db);
        return jl.execute();
    }

    /**
     * Determine if the named table is locked, and if so, is it owned by the current
     * thread.
     * 
     * @param table
     *            String. The name of the current table.
     * @return boolean. Returns true if table is locked and current thread is
     *         not owner, else returns false.
     */
    public boolean locked(String table) {

        if (tableLocked.size() == 0)
            return false;

        String current = Thread.currentThread().getName();

        /**
         * Is table even locked?
         */
        if (tableLocked.contains(table) == false)
            return false;

        /**
         * If its in the current threads list its not locked to caller.
         */
        List<String> list = locks.get(current);
        if (list.contains(table))
            return false;

        // Lock exists and current thread doesn't have it.
        return true;
    }

    /**
     * Creates a new stored procedure object for this JQL instance to use.
     */
    public JStoredProcedure openStoredProcedures() {
        sp = new JStoredProcedure(this);
        return sp;
    }

    /**
     * Alternate form of the format method.
     * 
     * @param test
     *            - Object. The object to pretty format as JSON.
     * @return String. The pretty formatted JSON, that is easy to read.
     */
    public String prettyFormat(Object test) {
        String rets = "";

        if (test instanceof List == false) {
            rets = gsonPretty.toJson(test);
            return rets;
        }

        List<Map> o = (List) test;
        int i = 0;
        for (Map map : o) {
            i++;
            String data = gsonPretty.toJson(map, HashMap.class);
            rets += data;
            if (i != o.size())
                rets += ",\n";
            else
                rets += "\n";
        }
        return rets;

    }

    /**
     * Alternate form of the pretty print JSON data.
     * 
     * @param data
     *            String. The JSON string to format.
     * @return String. The formatted JSON data.
     */
    public String prettyFormat(String data) throws Exception {
        String rets = null;
        Object obj = tableMap.get(data);
        if (obj == null) {
            obj = mapper.readValue(data, Object.class);
        }
        rets = gsonPretty.toJson(obj);
        return rets;
    }

    /**
     * Alternate form of the pretty print JSON data.
     * 
     * @param data
     *            Object. The object to format.
     * @return String. The formatted JSON data.
     */
    public String prettyPrint(Object data) {
        return prettyFormat(data);
    }

    /**
     * Alternate form of the pretty print JSON data.
     * 
     * @param data
     *            String. The JSON string to format.
     * @return String. The formatted JSON data.
     */
    public String prettyPrint(String data) throws Exception {
        return prettyFormat(data);
    }

    /**
     * Given a filename, read it into a string buffer.
     * @param fileName. String. The name of the file to load.
     * @return String. The contents of the file that was read.
     * @throws Exception. Throws exception on I/O errors.
     */
    public String readFile(String fileName) throws Exception {
        String data = "";
        FileReader fr = new FileReader(fileName);
        char[] buf = new char[4096];
        int i = 1;
        while (i > 0) {
            i = fr.read(buf);
            if (i > 0)
                data += new String(buf, 0, i);
        }
        return data;
    }

    /**
     * Removes the index for the named table.
     * 
     * @param name
     *            String. The name of the table to remove the indexes from.
     */
    public void removeIndex(String name) {
        indexes.remove(name);
    }

    /**
     * Removes a table from the database.
     * 
     * @param key
     *            String. The name of the table to remove from the database..
     */
    public void removeTable(String key) {
        tableMap.remove(key);
    }

    /**
     * Sets the CSV separator string.
     * 
     * @param sep
     *            String. The new CSV separator to use.
     */
    public void setCSVSeparator(String sep) {
        sepCSV = sep;
    }

    /**
     * TBD, not used yet.
     * @param t
     *            String. The token to decode to the
     * @param j
     *            int. The index of the ket/types.
     * @param keys
     *            String[]. The key/column name.
     * @param types
     *            String[]. The type of data.
     */
    void setType(String t, int j, String[] keys, String[] types) {
        if (t.startsWith("N::")) {
            types[j] = "n";
            t = t.replace("N::", "");
        } else if (t.startsWith("JSON::")) {
            types[j] = "j";
            t = t.replace("JSON::", "");
        } else
            types[j] = "c";
        keys[j] = t;
    }

    /**
     * String format the output of the arguments. Uses JAVA formatted output.
     * 
     * @param fmt
     *            String. The format of the output.
     * @param args
     *            Object[]. The arguments to print.
     * @return String. The formatted output.
     */
    public String sformat(String fmt, Object... args) {
        String s = String.format(fmt, args);
        return s;
    }

    /**
     * Determine if a table exists in the database.
     * 
     * @param name
     *            String. The name of the table.
     * @return boolean. Returns true if table exists, else returns false.
     */
    public boolean tableExists(String name) {
        if (tableMap == null)
            return false;
        if (tableMap.get(name) == null)
            return false;
        else
            return true;
    }

    /**
     * List the tables of the currently USEd database.
     * 
     * @return List<String>. The names of the tables of the currently used
     *         database.
     */
    public List<String> tableList() {
        List<String> list = new ArrayList<String>();
        Set keys = tableMap.keySet();
        Iterator it = keys.iterator();
        while (it.hasNext()) {
            list.add((String) it.next());
        }
        return list;
    }

    /**
     * Convert the given list to a CSV file. Note, all columns are printed, included embedded ones.
     * 
     * @param list
     *            List<Map>. The map table to turn into CSV formatted string.
     * @return String. The CSV version of the table.
     */
    public String toCSV(List<Map> list) throws Exception {
        String buf = "";
        Map m = list.get(0);
        Set set = m.keySet();
        Iterator it = set.iterator();
        String[] keys = new String[m.size()];
        int j = 0;

        while (it.hasNext()) {
            keys[j++] = (String) it.next();
        }
        return toCSV(list, keys);
    }

    /**
     * Convert the given list into a CSV file. Use this form to print only those
     * columns you want, and in the order you want.
     * 
     * @param list
     *            List<Map>. The table map to convert to CSV.
     * @param tokens
     *            String []. the column names to output.
     * @return String. The formatted CSV of the given table.
     */
    public String toCSV(List<Map> list, String... tokens) throws Exception {
        String output = "";
        for (String token : tokens) {
            output += token + ",";
        }
        output = output.substring(0, output.length() - 1) + "\n";

        for (Map m : list) {
            String line = "";
            for (String arg : tokens) {
                Object o = m.get(arg);
                if (o instanceof String)
                    line += "\"" + m.get(arg) + "\",";
                else if (o instanceof Double)
                    line += m.get(arg) + ",";
                else {
                    String conv = mapper.writer().withDefaultPrettyPrinter().writeValueAsString(list);
                    conv = conv.replaceAll("\n", " ");
                    line += "\"" + conv + "\",";
                }
            }
            line = line.substring(0, line.length() - 1) + "\n";
            output += line;
        }
        return output;
    }

    /**
     * Convert the named table to CSV string, using the keys of the map as column
     * names.
     * 
     * @param table
     *            String. The name of the table to convert.
     * @return String. The table's CSV representation.
     */
    public String toCSV(String table) throws Exception {
        List<Map> list = (List) tableMap.get(table);
        return toCSV(list);
    }

    /**
     * Convert a table into CSV, using the column arguments provided.
     * 
     * @param table
     *            String. The name of the table to convert.
     * @param args
     *            String[] args. The columns to convert. Unmentioned keys are ignored.
     * @return String. The table converted to CSV as a string.
     */
    public String toCSV(String table, String... args) throws Exception {
        List<Map> list = (List) tableMap.get(table);
        return toCSV(list, args);
    }

    /**
     *  Add a record to the table named in select.
     * 
     * @param select
     *            String. The table to add to.
     * @param args
     *            Object... The name/value pairs to add to the table.
     * @throws Exception. Throws
     *             exceptions if table doesn't exist or JSON errors.
     */
    public void updateRecord(String select, Object... args) throws Exception {
        int length = args.length;
        if (length % 2 != 0) {
            throw new Exception("Odd number of update predicates");
        }

        this.execute("select * from " + select + ";");

        List list = (List) intermediateResult;
        List results = (List) tableMap.get(lastName);

        for (Object o : list) {
            Map map = (Map) o;
            results.remove(map);

            for (int i = 0; i < length; i += 2) {
                map.put(args[i], args[i + 1]);
            }
            results.add(map);
        }
    }

    /**
     * Method to USE a database previously loaded
     * 
     * @param dbName
     *            String. The database name to use.
     * @return List<String>. A list of tables found in the USEd database.
     * @throws Exception. Throws
     *             exceptions if the db name doesn't exist.
     */
    public List<String> use(String dbName) throws Exception {
        tableMap = dbMap.get(dbName);

        List<Map> list = (List<Map>) tableMap.get("indexes");
        indexes = list.get(0);
        return tableList();
    }
}