package example;

import java.io.*;
import java.sql.*;
import java.util.*;

/**
 *  Test inserting and extracting Unicode-encoded strings.
 *
 *  Synopsis:
 *     example.Unicode <url> <user> <password>
 *  where <url> must specify an existing database to which <user> and
 *  <password> give access and which has UNICODE as its encoding.
 *  (To create a database with UNICODE encoding, you need to compile
 *  postgres with "--enable-multibyte" and run createdb with the
 *  flag "-E UNICODE".)
 *
 *  This test only produces output on error.
 *
 *  @author William Webber <william@live.com.au>
 */
public class Unicode {
    
    /**
     *  The url for the database to connect to.
     */
    private String url;

    /**
     *  The user to connect as.
     */
    private String user;

    /**
     *  The password to connect with.
     */
    private String password;

    private static void usage() {
        log("usage: example.Unicode <url> <user> <password>");
    }

    private static void log(String message) {
        System.err.println(message);
    }

    private static void log(String message, Exception e) {
        System.err.println(message);
        e.printStackTrace();
    }


    public Unicode(String url, String user, String password) {
        this.url = url;
        this.user = user;
        this.password = password;
    }

    /**
     *  Establish and return a connection to the database.
     */
    private Connection getConnection() throws SQLException, 
    ClassNotFoundException {
        Class.forName("org.postgresql.Driver");
        Properties info = new Properties();
        info.put("user", user);
        info.put("password", password);
        info.put("charSet", "utf-8");
        return DriverManager.getConnection(url, info);
    }

    /**
     *  Get string representing a block of 256 consecutive unicode characters.
     *  We exclude the null character, "'",  and "\".
     */
    private String getSqlSafeUnicodeBlock(int blockNum) {
        if (blockNum < 0 || blockNum > 255)
            throw new IllegalArgumentException("blockNum must be from 0 to "
                                               + "255: " + blockNum);
        StringBuffer sb = new StringBuffer(256);
        int blockFirst = blockNum * 256;
        int blockLast = blockFirst + 256;
        for (int i = blockFirst; i < blockLast; i++) {
            char c = (char) i;
            if (c == '\0' || c == '\'' || c == '\\')
                continue;
            sb.append(c);
        }
        return sb.toString();
    }

    /**
     *  Is the block a block of valid unicode values.
     *  d800 to db7f is the "unassigned high surrogate" range.
     *  db80 to dbff is the "private use" range.
     *  These should not be used in actual Unicode strings;
     *  at least, jdk1.2 will not convert them to utf-8.
     */
    private boolean isValidUnicodeBlock(int blockNum) {
        if (blockNum >= 0xd8 && blockNum <= 0xdb)
            return false;
        else
            return true;
    }

    /**
     *  Report incorrect block retrieval.
     */
    private void reportRetrievalError(int blockNum, String block, 
                                      String retrieved) {
        String message = "Block " + blockNum + " returned incorrectly: ";
        int i = 0;
        for (i = 0; i < block.length(); i++) {
            if (i >= retrieved.length()) { 
                message += "too short";
                break;
            } else if (retrieved.charAt(i) != block.charAt(i)) {
                message += 
                    "first changed character at position " + i + ", sent as 0x"
                   + Integer.toHexString((int) block.charAt(i)) 
                   + ", retrieved as 0x" 
                   + Integer.toHexString ((int) retrieved.charAt(i));
                break;
            }
        }
        if (i >= block.length())
            message += "too long";
        log(message);
    }
        
    /**
     *  Do the testing.
     */
    public void runTest() {
        Connection connection = null; 
        Statement statement = null; 
        int blockNum = 0;
        final int CREATE = 0;
        final int INSERT = 1;
        final int SELECT = 2;
        final int LIKE = 3;
        int mode = CREATE;
        try {
            connection = getConnection();
            statement = connection.createStatement();
            statement.executeUpdate("CREATE TABLE test_unicode " 
                                    + "( blockNum INT PRIMARY KEY, "
                                    + "block TEXT );");
            mode = INSERT;
            for (blockNum = 0; blockNum < 256; blockNum++) {
                if (isValidUnicodeBlock(blockNum)) {
                    String block = getSqlSafeUnicodeBlock(blockNum);
                    statement.executeUpdate
                        ("INSERT INTO test_unicode VALUES ( " + blockNum 
                         + ", '" + block + "');");
                }
            }
            mode = SELECT;
            for (blockNum = 0; blockNum < 256; blockNum++) {
                if (isValidUnicodeBlock(blockNum)) {
                    String block = getSqlSafeUnicodeBlock(blockNum);
                    ResultSet rs = statement.executeQuery
                        ("SELECT block FROM test_unicode WHERE blockNum = "
                         + blockNum + ";");
                    if (!rs.next())
                        log("Could not retrieve block " + blockNum);
                    else {
                        String retrieved = rs.getString(1);
                        if (!retrieved.equals(block)) {
                            reportRetrievalError(blockNum, block, retrieved);
                        }
                    }
                }
            }
            mode = LIKE;
            for (blockNum = 0; blockNum < 256; blockNum++) {
                if (isValidUnicodeBlock(blockNum)) {
                    String block = getSqlSafeUnicodeBlock(blockNum);
                    String likeString = "%" + 
                        block.substring(2, block.length() - 3) + "%" ;
                    ResultSet rs = statement.executeQuery
                        ("SELECT blockNum FROM test_unicode WHERE block LIKE '"
                         + likeString + "';");
                    if (!rs.next())
                        log("Could get block " + blockNum + " using LIKE");
                }
            }
        } catch (SQLException sqle) {
            switch (mode) {
            case CREATE:
                log("Exception creating database", sqle);
                break;
            case INSERT:
                log("Exception inserting block " + blockNum, sqle);
                break;
            case SELECT:
                log("Exception selecting block " + blockNum, sqle);
                break;
            case LIKE: 
                log("Exception doing LIKE on block " + blockNum, sqle);
                break;
            default:
                log("Exception", sqle);
                break;
            }
        } catch (ClassNotFoundException cnfe) {
            log("Unable to load driver", cnfe);
            return;
        }
        try {
            if (statement != null)
                statement.close();
            if (connection != null)
                connection.close();
        } catch (SQLException sqle) {
            log("Exception closing connections", sqle);
        }
        if (mode > CREATE) {
            // If the backend gets what it regards as garbage on a connection,
            // that connection may become unusable.  To be safe, we create
            // a fresh connection to delete the table.
            try {
                connection = getConnection();
                statement = connection.createStatement();
                statement.executeUpdate("DROP TABLE test_unicode;");
            } catch (Exception sqle) {
                log("*** ERROR: unable to delete test table "
                    + "test_unicode; must be deleted manually", sqle);
            }
        }
    }

    public static void main(String [] args) {
        if (args.length != 3) {
            usage();
            System.exit(1);
        }
        new Unicode(args[0], args[1], args[2]).runTest();
    }
}
