Package org.apache.harmony.sql.internal.rowset

Source Code of org.apache.harmony.sql.internal.rowset.CachedRowSetImpl

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License.  You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.harmony.sql.internal.rowset;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringBufferInputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.Ref;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;
import javax.sql.RowSet;
import javax.sql.RowSetEvent;
import javax.sql.RowSetInternal;
import javax.sql.RowSetListener;
import javax.sql.RowSetMetaData;
import javax.sql.rowset.BaseRowSet;
import javax.sql.rowset.CachedRowSet;
import javax.sql.rowset.RowSetMetaDataImpl;
import javax.sql.rowset.RowSetWarning;
import javax.sql.rowset.serial.SerialArray;
import javax.sql.rowset.serial.SerialBlob;
import javax.sql.rowset.serial.SerialClob;
import javax.sql.rowset.serial.SerialRef;
import javax.sql.rowset.spi.SyncFactory;
import javax.sql.rowset.spi.SyncFactoryException;
import javax.sql.rowset.spi.SyncProvider;
import javax.sql.rowset.spi.SyncProviderException;

import org.apache.harmony.sql.internal.nls.Messages;

public class CachedRowSetImpl extends BaseRowSet implements CachedRowSet,
        RowSetInternal {

    private static final long serialVersionUID = 1L;

    protected ArrayList<CachedRow> rows;

    protected RowSetMetaData meta;

    protected CachedRow currentRow;

    /**
     * current row index include deleted rows, start from 1
     */
    protected int currentRowIndex;

    // the number of the rows in one "page"
    private int pageSize;

    /**
     * used for paging, record row index for next page in ResultSet, start from
     * 1
     */
    private int nextPageRowIndex = -1;

    /**
     * used for paging, record row index for previous page in ResultSet, start
     * from 1
     */
    private int previousPageRowIndex = -1;

    /**
     * cached ResultSet for paging in memory
     */
    private CachedRowSet cachedResultSet = null;

    private String tableName;

    private int rememberedCursorPosition;

    private CachedRow insertRow;

    private boolean isCursorOnInsert;

    private int[] keyCols;

    protected int columnCount;

    private int deletedRowCount;

    private SyncProvider syncProvider;

    protected CachedRowSetImpl originalResultSet;

    private SQLWarning sqlwarn = new SQLWarning();

    // TODO deal with rowSetWarning
    private RowSetWarning rowSetWarning = new RowSetWarning();

    protected Class[] columnTypes;

    private String[] matchColumnNames;

    private int[] matchColumnIndexes;

    private String cursorName;

    private boolean isLastColNull;

    protected transient Connection conn;

    private boolean isNotifyListener = true;

    protected static Map<Integer, Class<?>> TYPE_MAPPING = initialTypeMapping();

    public static final String PROVIDER_ID = "Apache Harmony HYOptimisticProvider"; //$NON-NLS-1$

    public CachedRowSetImpl(String providerID) throws SyncFactoryException {
        syncProvider = SyncFactory.getInstance(providerID);
        initialProperties();
    }

    private static Map<Integer, Class<?>> initialTypeMapping() {
        HashMap<Integer, Class<?>> map = new HashMap<Integer, Class<?>>();
        map.put(Integer.valueOf(Types.ARRAY), Array.class);
        map.put(Integer.valueOf(Types.BIGINT), Long.class);
        map.put(Integer.valueOf(Types.BINARY), byte[].class);
        map.put(Integer.valueOf(Types.BIT), Boolean.class);
        map.put(Integer.valueOf(Types.BLOB), Blob.class);
        map.put(Integer.valueOf(Types.BOOLEAN), Boolean.class);
        map.put(Integer.valueOf(Types.CHAR), String.class);
        map.put(Integer.valueOf(Types.CLOB), Clob.class);
        map.put(Integer.valueOf(Types.DATE), Date.class);
        map.put(Integer.valueOf(Types.DECIMAL), BigDecimal.class);
        map.put(Integer.valueOf(Types.DOUBLE), Double.class);
        map.put(Integer.valueOf(Types.FLOAT), Double.class);
        map.put(Integer.valueOf(Types.INTEGER), Integer.class);
        map.put(Integer.valueOf(Types.LONGVARBINARY), byte[].class);
        map.put(Integer.valueOf(Types.LONGVARCHAR), String.class);
        map.put(Integer.valueOf(Types.NUMERIC), BigDecimal.class);
        map.put(Integer.valueOf(Types.REAL), Float.class);
        map.put(Integer.valueOf(Types.REF), Ref.class);
        map.put(Integer.valueOf(Types.SMALLINT), Short.class);
        map.put(Integer.valueOf(Types.STRUCT), Struct.class);
        map.put(Integer.valueOf(Types.TIME), Time.class);
        map.put(Integer.valueOf(Types.TIMESTAMP), Timestamp.class);
        map.put(Integer.valueOf(Types.TINYINT), Byte.class);
        map.put(Integer.valueOf(Types.VARBINARY), byte[].class);
        map.put(Integer.valueOf(Types.VARCHAR), String.class);

        return map;
    }

    private void initialProperties() {
        try {
            setEscapeProcessing(true);
            setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
            setConcurrency(ResultSet.CONCUR_UPDATABLE);
            setType(ResultSet.TYPE_SCROLL_INSENSITIVE);
            setMaxRows(0);
            setQueryTimeout(0);
            setShowDeleted(false);
            setUsername(null);
            setPassword(null);
            setMaxFieldSize(0);
            setTypeMap(null);
            setFetchSize(0);
        } catch (SQLException e) {
            // ignore, never reached
        }

    }

    public CachedRowSetImpl() throws SyncFactoryException {
        this(PROVIDER_ID);
    }

    public void setRows(ArrayList<CachedRow> data, int cloumnCount) {
        rows = data;
        this.columnCount = cloumnCount;
    }

    public void acceptChanges() throws SyncProviderException {
        if (isCursorOnInsert) {
            // rowset.11=Illegal operation on an insert row
            throw new SyncProviderException(Messages.getString("rowset.11")); //$NON-NLS-1$
        }

        Connection currentConn = null;
        Connection preConn = conn;
        try {
            currentConn = retrieveConnection();
            currentConn.setTypeMap(getTypeMap());
            acceptChanges(currentConn);
            currentConn.commit();
        } catch (SQLException e) {
            try {
                if (currentConn != null) {
                    currentConn.rollback();
                }
            } catch (SQLException ex) {
                // ignore
            }
            SyncProviderException ex = new SyncProviderException();
            ex.initCause(e);
            throw ex;
        } finally {
            conn = preConn;
            if (currentConn != null) {
                try {
                    currentConn.close();
                } catch (SQLException ex) {
                    SyncProviderException spe = new SyncProviderException();
                    spe.initCause(ex);
                    throw spe;
                }
            }
        }
    }

    public void acceptChanges(Connection connection)
            throws SyncProviderException {
        if (isCursorOnInsert) {
            // rowset.11=Illegal operation on an insert row
            throw new SyncProviderException(Messages.getString("rowset.11")); //$NON-NLS-1$
        }

        // Follow RI to assign conn before checking whether conn is null
        conn = connection;

        if (conn == null) {
            throw new SyncProviderException();
        }

        boolean isShowDeleted = false;
        try {
            isShowDeleted = getShowDeleted();
        } catch (SQLException e) {
            // ignore
        }

        try {
            conn.setAutoCommit(false);
            CachedRowSetWriter rowSetWriter = (CachedRowSetWriter) syncProvider
                    .getRowSetWriter();
            rowSetWriter.setConnection(conn);
            int beforeWriteIndex = currentRowIndex;
            // writer use next navigate rowset, so must make all rows visible
            setShowDeleted(true);

            if (!rowSetWriter.writeData(this)) {
                throw rowSetWriter.getSyncException();
            }

            // must reset curosr before reset showDeleted
            absolute(beforeWriteIndex);
            setShowDeleted(isShowDeleted);

            // record to the next visible row index
            int index = getRow();
            if (index == 0) {
                next();
                index = getRow();
                if (index == 0) {
                    index = rows.size() + 1;
                }
            }

            boolean isChanged = false;
            for (int i = rows.size() - 1; i >= 0; i--) {
                currentRow = rows.get(i);
                if (rowDeleted()) {
                    isChanged = true;
                    setOriginalRow();
                } else if (rowInserted() || rowUpdated()) {
                    isChanged = true;
                    setOriginalRow();
                }
            }
            // Set originalResultSet
            if (isChanged) {
                try {
                    ArrayList<CachedRow> nowRows = new ArrayList<CachedRow>();
                    for (int i = 0; i < rows.size(); i++) {
                        nowRows.add(rows.get(i).createClone());
                        nowRows.get(i).restoreOriginal();
                    }
                    originalResultSet.setRows(nowRows, columnCount);
                } catch (CloneNotSupportedException cloneE) {
                    throw new SyncProviderException(cloneE.getMessage());
                }
            }

            deletedRowCount = 0;

            // move cursor
            if (index > rows.size()) {
                afterLast();
            } else if (index <= 0) {
                beforeFirst();
            } else {
                absolute(index);
            }

            if (isNotifyListener) {
                notifyRowSetChanged();
            }

        } catch (SyncProviderException e) {
            throw e;
        } catch (SQLException e) {
            SyncProviderException ex = new SyncProviderException();
            ex.initCause(e);
            throw ex;
        } finally {
            try {
                setShowDeleted(isShowDeleted);
            } catch (SQLException e) {
                // ignore
            }
        }
    }

    public boolean columnUpdated(int idx) throws SQLException {
        if (currentRow == null || idx > meta.getColumnCount() || idx <= 0
                || currentRow == insertRow) {
            // rowset.0 = Not a valid position
            throw new SQLException(Messages.getString("rowset.0")); //$NON-NLS-1$
        }
        return currentRow.getUpdateMask(idx - 1);
    }

    public boolean columnUpdated(String columnName) throws SQLException {
        return columnUpdated(getIndexByName(columnName));
    }

    private int getIndexByName(String columnName) throws SQLException {
        if (meta == null || columnName == null) {
            throw new NullPointerException();
        }

        for (int i = 1; i <= meta.getColumnCount(); i++) {
            if (columnName.equalsIgnoreCase(meta.getColumnName(i))) {
                return i;
            }
        }
        // rowset.1=Not a valid column name
        throw new SQLException(Messages.getString("rowset.1")); //$NON-NLS-1$
    }

    public void commit() throws SQLException {
        if (conn == null) {
            throw new NullPointerException();
        }
        conn.commit();
    }

    public CachedRowSet createCopy() throws SQLException {
        CachedRowSetImpl output;
        try {
            /*
             * the attribute of BaseRowSet which are needed to deep copy
             */
            // BaseRowSet.params <Hashtable>
            Object[] paramsObjArray = super.getParams();
            Hashtable<Object, Object> paramsHashtable = new Hashtable<Object, Object>();
            for (int i = 0; i < paramsObjArray.length; i++) {
                paramsHashtable.put(Integer.valueOf(i), paramsObjArray[i]);
            }
            // BaseRowSet.listeners <Vector>
            Vector<RowSetListener> listeners = new Vector<RowSetListener>();

            /*
             * deep copy BaseRowSet
             */
            output = (CachedRowSetImpl) super.clone();
            // BaseRowSet.params
            Field paramsField = BaseRowSet.class.getDeclaredField("params"); //$NON-NLS-1$
            paramsField.setAccessible(true);
            paramsField.set(output, paramsHashtable);
            // BaseRowSet.listeners
            Field listenersField = BaseRowSet.class
                    .getDeclaredField("listeners"); //$NON-NLS-1$
            listenersField.setAccessible(true);
            listenersField.set(output, listeners);
            // BaseRowSet.map
            if (super.getTypeMap() != null) {
                Map<String, Class<?>> originalTypeMap = super.getTypeMap();
                Map<String, Class<?>> copyTypeMap = new HashMap<String, Class<?>>();
                copyTypeMap.putAll(originalTypeMap);
                output.setTypeMap(copyTypeMap);
            }

            /*
             * deep copy CachedRowSetImpl
             */
            // CachedRowSetImpl.rows <ArrayList>
            ArrayList<CachedRow> copyRows = new ArrayList<CachedRow>();
            for (int i = 0; i < rows.size(); i++) {
                copyRows.add(rows.get(i).createClone());
            }
            output.setRows(copyRows, columnCount);
            // CachedRowSetImpl.meta <RowSetMetaData>
            output.setMetaData(copyMetaData(getMetaData()));
            // set currentRow
            if ((currentRowIndex > 0) && (currentRowIndex <= rows.size())) {
                output.absolute(currentRowIndex);
            }
            // others
            if (getKeyColumns() != null) {
                output.setKeyColumns(getKeyColumns().clone());
            }
            // CachedRowSetImpl.originalResultSet
            CachedRowSetImpl copyOriginalRs = new CachedRowSetImpl();
            copyOriginalRs.populate(getOriginal());
            getOriginal().beforeFirst();
            output.originalResultSet = copyOriginalRs;

            if (matchColumnIndexes != null) {
                output.matchColumnIndexes = matchColumnIndexes.clone();
            }

            if (matchColumnNames != null) {
                output.matchColumnNames = matchColumnNames.clone();
            }

            output.setSyncProvider(getSyncProvider().getProviderID());
            if (insertRow != null) {
                output.insertRow = insertRow.createClone();
            }

            return output;
        } catch (CloneNotSupportedException e) {
            throw new SQLException(e.getMessage());
        } catch (NoSuchFieldException e) {
            throw new SQLException(e.getMessage());
        } catch (IllegalAccessException e) {
            throw new SQLException(e.getMessage());
        }
    }

    public CachedRowSet createCopyNoConstraints() throws SQLException {
        CachedRowSetImpl output = (CachedRowSetImpl) createCopy();
        output.initialProperties();
        return output;
    }

    public CachedRowSet createCopySchema() throws SQLException {
        CachedRowSetImpl output = (CachedRowSetImpl) createCopy();

        // clean up rows data
        output.currentRow = null;
        output.currentRowIndex = 0;
        output.insertRow = null;
        output.isCursorOnInsert = false;
        output.isLastColNull = false;
        output.nextPageRowIndex = -1;
        output.rememberedCursorPosition = 0;
        output.rows = new ArrayList<CachedRow>();
        output.sqlwarn = null;
        output.deletedRowCount = 0;

        return output;
    }

    public RowSet createShared() throws SQLException {
        // shallow copy
        RowSet result = null;
        try {
            result = (RowSet) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new SQLException(e.getMessage());
        }

        return result;
    }

    public void execute(Connection connection) throws SQLException {
        String localCommand = getCommand();
        if (localCommand == null || getParams() == null) {
            // rowset.16=Not a valid command
            throw new SQLException(Messages.getString("rowset.16")); //$NON-NLS-1$
        }

        conn = connection;
        PreparedStatement ps = connection.prepareStatement(localCommand);
        setParameter(ps);

        if (ps.execute()) {
            doPopulate(ps.getResultSet(), true);

            if (getPageSize() != 0) {
                nextPageRowIndex = rows.size() + 1;
                previousPageRowIndex = 0;
                cachedResultSet = null;
            } else {
                previousPageRowIndex = -1;
                nextPageRowIndex = -1;
                cachedResultSet = null;
            }
        }

    }

    private void setParameter(PreparedStatement ps) throws SQLException {
        Object[] params = getParams();
        for (int i = 0; i < params.length; i++) {
            if (params[i] instanceof Object[]) {
                Object[] objs = (Object[]) params[i];
                // character stream
                if (objs.length == 2) {
                    ps.setCharacterStream(i + 1, (Reader) objs[0],
                            ((Integer) objs[1]).intValue());
                } else {
                    int type = ((Integer) objs[2]).intValue();
                    switch (type) {
                    case BaseRowSet.ASCII_STREAM_PARAM:
                        ps.setAsciiStream(i + 1, (InputStream) objs[0],
                                ((Integer) objs[1]).intValue());
                        break;
                    case BaseRowSet.BINARY_STREAM_PARAM:
                        ps.setBinaryStream(i + 1, (InputStream) objs[0],
                                ((Integer) objs[1]).intValue());
                        break;
                    case BaseRowSet.UNICODE_STREAM_PARAM:
                        ps.setUnicodeStream(i + 1, (InputStream) objs[0],
                                ((Integer) objs[1]).intValue());
                        break;
                    }
                }
            } else {
                ps.setObject(i + 1, params[i]);
            }
        }
    }

    public int[] getKeyColumns() throws SQLException {
        if (rows == null) {
            // rowset.26=The object has not been initialized
            throw new SQLException(Messages.getString("rowset.26")); //$NON-NLS-1$
        }
        if (keyCols == null) {
            return new int[0];
        }

        return keyCols;
    }

    public ResultSet getOriginal() throws SQLException {
        if (originalResultSet == null) {
            throw new NullPointerException();
        }
        return originalResultSet;
    }

    public ResultSet getOriginalRow() throws SQLException {
        if (currentRow == null) {
            // rowset.7=Not a valid cursor
            throw new SQLException(Messages.getString("rowset.7")); //$NON-NLS-1$
        }

        CachedRowSetImpl originalRowRset = new CachedRowSetImpl();
        ArrayList<CachedRow> data = new ArrayList<CachedRow>();
        CachedRow originalCachedRow = currentRow.getOriginal();
        data.add(originalCachedRow);
        originalRowRset.setMetaData(meta);
        originalRowRset.setRows(data, columnCount);
        originalRowRset.setType(ResultSet.TYPE_SCROLL_INSENSITIVE);
        originalRowRset.setConcurrency(ResultSet.CONCUR_UPDATABLE);
        return originalRowRset;
    }

    public int getPageSize() {
        return pageSize;
    }

    public RowSetWarning getRowSetWarnings() throws SQLException {
        return rowSetWarning;
    }

    public SyncProvider getSyncProvider() throws SQLException {
        return syncProvider;
    }

    public String getTableName() throws SQLException {
        return tableName;
    }

    /**
     * TODO refactor paging
     */
    public boolean nextPage() throws SQLException {
        if (rows == null || nextPageRowIndex == -1 || getPageSize() == 0) {
            // rowset.19=Populate data before calling
            throw new SQLException(Messages.getString("rowset.19")); //$NON-NLS-1$
        }

        if (cachedResultSet == null) {
            String localCommand = getCommand();
            if (localCommand == null || getParams() == null) {
                // rowset.16=Not a valid command
                throw new SQLException(Messages.getString("rowset.16")); //$NON-NLS-1$
            }

            PreparedStatement ps = retrieveConnection().prepareStatement(
                    localCommand);

            setParameter(ps);

            if (ps.execute()) {
                ResultSet rs = ps.getResultSet();
                int index = 1;
                while (rs.next() && index < nextPageRowIndex - 1) {
                    index++;
                }

                if (isNotifyListener) {
                    isNotifyListener = false;
                    doPopulate(rs, true);
                    notifyRowSetChanged();
                    isNotifyListener = true;
                } else {
                    doPopulate(rs, true);
                }

                if (rows.size() == 0) {
                    return false;
                }
                previousPageRowIndex = nextPageRowIndex - 1;
                nextPageRowIndex += rows.size();
                return true;

            }
            return false;

        }

        if (cachedResultSet.absolute(nextPageRowIndex)) {
            cachedResultSet.previous();

            if (isNotifyListener) {
                isNotifyListener = false;
                doPopulate(cachedResultSet, true);
                notifyRowSetChanged();
                isNotifyListener = true;
            } else {
                doPopulate(cachedResultSet, true);
            }

            if (rows.size() == 0) {
                return false;
            }
            previousPageRowIndex = nextPageRowIndex - 1;
            nextPageRowIndex += rows.size();
            return true;
        }
        return false;

    }

    public void populate(ResultSet rs) throws SQLException {
        doPopulate(rs, false);
        previousPageRowIndex = -1;
        nextPageRowIndex = -1;
        cachedResultSet = null;
    }

    public void populate(ResultSet rs, int startRow) throws SQLException {
        if (rs == null) {
            // sql.42=Illegal Argument
            throw new SQLException(Messages.getString("sql.42")); //$NON-NLS-1$
        }

        if (startRow == 1) {
            rs.beforeFirst();
            // maybe use next to move is better
        } else if (startRow <= 0 || !rs.absolute(startRow - 1)) {
            // rowset.7=Not a valid cursor
            throw new SQLException(Messages.getString("rowset.7")); //$NON-NLS-1$
        }

        // paging in memory
        if (getPageSize() != 0) {
            cachedResultSet = new CachedRowSetImpl();
            cachedResultSet.setMaxRows(getMaxRows());
            cachedResultSet.populate(rs, startRow);
            doPopulate(cachedResultSet, true);

            nextPageRowIndex = rows.size() + 1;
            previousPageRowIndex = 0;

        } else {
            doPopulate(rs, true);
            previousPageRowIndex = -1;
            nextPageRowIndex = -1;
            cachedResultSet = null;
        }
    }

    protected void doPopulate(ResultSet rs, boolean isPaging)
            throws SQLException {
        boolean oldIsNotifyListener = isNotifyListener;
        isNotifyListener = false;
        meta = copyMetaData(rs.getMetaData());

        columnCount = meta.getColumnCount();
        // initial columnTypes
        columnTypes = new Class[columnCount];
        for (int i = 1; i <= columnTypes.length; ++i) {
            columnTypes[i - 1] = TYPE_MAPPING.get(Integer.valueOf(meta
                    .getColumnType(i)));
        }
        try {
            cursorName = rs.getCursorName();
        } catch (SQLException e) {
            cursorName = null;
            // ignore
        }

        if (rs.getStatement() != null
                && rs.getStatement().getConnection() != null) {
            setTypeMap(rs.getStatement().getConnection().getTypeMap());
        }

        /*
         * this method not support paging, so before readData set pageSize and
         * maxRowsto 0 and restore previous values after readData
         */
        CachedRowSetReader crsReader = (CachedRowSetReader) syncProvider
                .getRowSetReader();
        crsReader.setResultSet(rs);
        if (!isPaging) {
            int prePageSize = getPageSize();
            setPageSize(0);
            int preMaxRows = getMaxRows();
            setMaxRows(0);
            crsReader.readData(this);
            setPageSize(prePageSize);
            setMaxRows(preMaxRows);
        } else {
            crsReader.readData(this);
        }

        setTableName(rs.getMetaData().getTableName(1));

        originalResultSet = new CachedRowSetImpl();
        crsReader.setResultSet(this);
        crsReader.readData(originalResultSet);
        originalResultSet.setMetaData((RowSetMetaData) (getMetaData()));

        // recovery the states
        beforeFirst();

        isNotifyListener = true;
        if (oldIsNotifyListener) {
            notifyRowSetChanged();
        }
    }

    // deep copy of ResultSetMetaData
    protected RowSetMetaData copyMetaData(ResultSetMetaData metaData)
            throws SQLException {
        RowSetMetaDataImpl rowSetMetaData = new RowSetMetaDataImpl();
        rowSetMetaData.setColumnCount(metaData.getColumnCount());
        for (int columnIndex = 1; columnIndex <= metaData.getColumnCount(); columnIndex++) {
            rowSetMetaData.setAutoIncrement(columnIndex, metaData
                    .isAutoIncrement(columnIndex));
            doCopyMetaData(rowSetMetaData, columnIndex, metaData, columnIndex);
        }
        return rowSetMetaData;
    }

    protected void doCopyMetaData(RowSetMetaData targetRsmd, int targetIndex,
            ResultSetMetaData srcRsmd, int srcIndex) throws SQLException {
        targetRsmd.setAutoIncrement(targetIndex, srcRsmd
                .isAutoIncrement(srcIndex));
        targetRsmd.setCaseSensitive(targetIndex, srcRsmd
                .isCaseSensitive(srcIndex));
        targetRsmd
                .setCatalogName(targetIndex, srcRsmd.getCatalogName(srcIndex));
        targetRsmd.setColumnDisplaySize(targetIndex, srcRsmd
                .getColumnDisplaySize(srcIndex));
        targetRsmd
                .setColumnLabel(targetIndex, srcRsmd.getColumnLabel(srcIndex));
        targetRsmd.setColumnName(targetIndex, srcRsmd.getColumnName(srcIndex));
        targetRsmd.setColumnType(targetIndex, srcRsmd.getColumnType(srcIndex));
        targetRsmd.setColumnTypeName(targetIndex, srcRsmd
                .getColumnTypeName(srcIndex));
        targetRsmd.setCurrency(targetIndex, srcRsmd.isCurrency(srcIndex));
        targetRsmd.setNullable(targetIndex, srcRsmd.isNullable(srcIndex));
        targetRsmd.setPrecision(targetIndex, srcRsmd.getPrecision(srcIndex));
        targetRsmd.setScale(targetIndex, srcRsmd.getScale(srcIndex));
        targetRsmd.setSchemaName(targetIndex, srcRsmd.getSchemaName(srcIndex));
        targetRsmd.setSearchable(targetIndex, srcRsmd.isSearchable(srcIndex));
        targetRsmd.setSigned(targetIndex, srcRsmd.isSigned(srcIndex));
        targetRsmd.setTableName(targetIndex, srcRsmd.getTableName(srcIndex));
    }

    public boolean previousPage() throws SQLException {
        if (rows == null || previousPageRowIndex == -1 || getPageSize() == 0) {
            // rowset.19=Populate data before calling
            throw new SQLException(Messages.getString("rowset.19")); //$NON-NLS-1$
        }

        if (previousPageRowIndex == 0) {
            return false;
        }

        if (cachedResultSet == null) {
            String localCommand = getCommand();
            if (localCommand == null || getParams() == null) {
                // rowset.16=Not a valid command
                throw new SQLException(Messages.getString("rowset.16")); //$NON-NLS-1$
            }

            PreparedStatement ps = retrieveConnection().prepareStatement(
                    localCommand);

            setParameter(ps);

            if (ps.execute()) {
                ResultSet rs = ps.getResultSet();
                int startIndex = previousPageRowIndex - getPageSize() + 1;

                if (startIndex <= 0) {
                    startIndex = 1;
                }

                int index = 0;
                while (index < startIndex - 1) {
                    if (!rs.next()) {
                        break;
                    }
                    index++;
                }

                int prePageSize = getPageSize();
                if (prePageSize != 0
                        && previousPageRowIndex - startIndex + 1 != prePageSize) {
                    setPageSize(previousPageRowIndex - startIndex + 1);
                }
                if (isNotifyListener) {
                    isNotifyListener = false;
                    doPopulate(rs, true);
                    notifyRowSetChanged();
                    isNotifyListener = true;
                } else {
                    doPopulate(rs, true);
                }

                setPageSize(prePageSize);

                if (rows.size() == 0) {
                    return false;
                }
                nextPageRowIndex = previousPageRowIndex + 1;
                previousPageRowIndex = startIndex - 1;
                return true;
            }

            return false;
        }

        int startIndex = previousPageRowIndex - getPageSize() + 1;

        if (startIndex <= 0) {
            startIndex = 1;
        }

        if (!cachedResultSet.absolute(startIndex)) {
            return false;
        }

        cachedResultSet.previous();

        int prePageSize = getPageSize();
        if (prePageSize != 0
                && previousPageRowIndex - startIndex + 1 != prePageSize) {
            setPageSize(previousPageRowIndex - startIndex + 1);
        }

        doPopulate(cachedResultSet, true);

        setPageSize(prePageSize);

        if (rows.size() == 0) {
            return false;
        }
        nextPageRowIndex = previousPageRowIndex + 1;
        previousPageRowIndex = startIndex - 1;
        return true;
    }

    public void release() throws SQLException {
        rows = new ArrayList<CachedRow>();
        if (isNotifyListener) {
            notifyRowSetChanged();
        }
    }

    public void restoreOriginal() throws SQLException {
        if (rows == null) {
            return;
        }

        boolean oldIsNotifyListener = isNotifyListener;
        isNotifyListener = false;
        List<CachedRow> insertedRows = new ArrayList<CachedRow>();
        for (CachedRow row : rows) {
            if (row.isInsert()) {
                insertedRows.add(row);
            } else if (row.isDelete() || row.isUpdate()) {
                row.restoreOriginal();
            }
        }
        rows.removeAll(insertedRows);
        insertRow = null;
        isCursorOnInsert = false;
        deletedRowCount = 0;

        first();
        isNotifyListener = true;
        if (oldIsNotifyListener) {
            notifyRowSetChanged();
        }
    }

    public void rollback() throws SQLException {
        if (conn == null) {
            throw new NullPointerException();
        }
        conn.rollback();
    }

    public void rollback(Savepoint s) throws SQLException {
        if (conn == null) {
            throw new NullPointerException();
        }
        conn.rollback(s);
    }

    public void rowSetPopulated(RowSetEvent event, int numRows)
            throws SQLException {
        if (numRows <= 0) {
            // sql.42=Illegal Argument
            throw new SQLException(Messages.getString("sql.42")); //$NON-NLS-1$
        }
        if (numRows < getFetchSize()) {
            // rowset.22=Number of rows is less than fetch size
            throw new SQLException(Messages.getString("rowset.22")); //$NON-NLS-1$
        }
        if (size() == 0 || size() % numRows == 0) {
            if (isNotifyListener) {
                notifyRowSetChanged();
            }
        }
    }

    public void setKeyColumns(int[] keys) throws SQLException {
        if (keys == null) {
            throw new NullPointerException();
        }

        if (rows == null) {
            keyCols = keys.clone();
        } else {
            for (int key : keys) {
                if (key <= 0 || key > columnCount) {
                    // sql.27=Invalid column index :{0}
                    throw new SQLException(Messages.getString("sql.27", key)); //$NON-NLS-1$
                }
            }

            keyCols = keys.clone();
        }
    }

    public void setMetaData(RowSetMetaData md) throws SQLException {
        meta = md;
    }

    public void setOriginalRow() throws SQLException {
        if (currentRow == null) {
            // rowset.7=Not a valid cursor
            throw new SQLException(Messages.getString("rowset.7")); //$NON-NLS-1$
        }

        if (rowDeleted()) {
            rows.remove(currentRow);
            deletedRowCount--;
        } else if (rowUpdated() || rowInserted()) {
            currentRow.setOriginal();
        }
    }

    public void setPageSize(int size) throws SQLException {
        if (size < 0) {
            // rowset.2=Negative page size
            throw new SQLException(Messages.getString("rowset.2")); //$NON-NLS-1$
        }
        if ((getMaxRows() != 0) && (getMaxRows() < size)) {
            // rowset.9=PageSize can not larger than MaxRows
            throw new SQLException(Messages.getString("rowset.9")); //$NON-NLS-1$
        }
        pageSize = size;
    }

    public void setSyncProvider(String provider) throws SQLException {
        syncProvider = SyncFactory.getInstance(provider);
    }

    public void setTableName(String tabName) throws SQLException {
        if (tabName == null) {
            // rowset.3=Table name should not be null
            throw new SQLException(Messages.getString("rowset.3")); //$NON-NLS-1$
        }
        tableName = tabName;
    }

    public int size() {
        if (rows == null) {
            return 0;
        }
        return rows.size();
    }

    public Collection<?> toCollection() throws SQLException {
        if (rows == null) {
            // sql.38=Object is invalid
            throw new SQLException(Messages.getString("sql.38")); //$NON-NLS-1$
        }
        List<Vector<Object>> list = new ArrayList<Vector<Object>>();
        if (rows.size() > 0) {
            Vector<Object> vector = null;
            for (int i = 0; i < rows.size(); i++) {
                CachedRow row = rows.get(i);
                vector = new Vector<Object>();
                for (int j = 1; j <= columnCount; j++) {
                    vector.add(row.getObject(j));
                }
                list.add(vector);
            }
        }
        return list;
    }

    public Collection<?> toCollection(int column) throws SQLException {
        if (rows == null) {
            return new Vector<Object>();
        }

        if (column <= 0 || column > columnCount) {
            // sql.42=Illegal Argument
            throw new SQLException(Messages.getString("sql.42")); //$NON-NLS-1$
        }

        Vector<Object> vector = new Vector<Object>();
        if (rows.size() > 0) {
            for (int i = 0; i < rows.size(); i++) {
                vector.add(rows.get(i).getObject(column));
            }
        }
        return vector;
    }

    public Collection<?> toCollection(String column) throws SQLException {
        return toCollection(getIndexByName(column));
    }

    public void undoDelete() throws SQLException {
        if (isAfterLast() || isBeforeFirst()) {
            // rowset.7=Not a valid cursor
            throw new SQLException(Messages.getString("rowset.7")); //$NON-NLS-1$
        }

        if (currentRow != null && !currentRow.isDelete()) {
            // rowset.0=Not a valid position
            throw new SQLException(Messages.getString("rowset.0")); //$NON-NLS-1$
        }

        if (currentRow != null && currentRow.isDelete()) {
            currentRow.undoDelete();
            deletedRowCount--;
        }

        if (isNotifyListener) {
            notifyRowChanged();
        }
    }

    public void undoInsert() throws SQLException {
        checkValidRow();
        if (isCursorOnInsert) {
            // rowset.11=Illegal operation on an insert row
            throw new SQLException(Messages.getString("rowset.11")); //$NON-NLS-1$
        }
        if (!rowInserted()) {
            // rowset.4=Not an insert row
            throw new SQLException(Messages.getString("rowset.4")); //$NON-NLS-1$
        }
        rows.remove(currentRow);
        next();
        if (isNotifyListener) {
            notifyRowChanged();
        }
    }

    public void undoUpdate() throws SQLException {
        checkValidRow();
        if (isCursorOnInsert && insertRow == null) {
            // rowset.11=Illegal operation on an insert row
            throw new SQLException(Messages.getString("rowset.11")); //$NON-NLS-1$
        }
        if (currentRow == insertRow) {
            currentRow = new CachedRow(new Object[columnCount]);
        } else if (rowUpdated()) {
            currentRow.restoreOriginal();
        }

        if (isNotifyListener) {
            notifyRowChanged();
        }
    }

    public int[] getMatchColumnIndexes() throws SQLException {
        if (matchColumnIndexes == null || matchColumnIndexes.length == 0
                || matchColumnIndexes[0] == -1) {
            // rowset.13=Set Match columns before getting them
            throw new SQLException(Messages.getString("rowset.13")); //$NON-NLS-1$
        }

        return matchColumnIndexes.clone();
    }

    public String[] getMatchColumnNames() throws SQLException {
        if (matchColumnNames == null || matchColumnNames.length == 0
                || matchColumnNames[0] == null) {
            // rowset.13=Set Match columns before getting them
            throw new SQLException(Messages.getString("rowset.13")); //$NON-NLS-1$
        }
        return matchColumnNames.clone();
    }

    public void setMatchColumn(int columnIdx) throws SQLException {
        if (columnIdx < 0) {
            // TODO why is 0 valid?
            // rowset.20=Match columns should be greater than 0
            throw new SQLException(Messages.getString("rowset.20")); //$NON-NLS-1$
        }

        if (matchColumnIndexes == null) {
            /*
             * FIXME initial match column, the default length of array is 10 in
             * RI, we don't know why, just follow now
             */
            matchColumnIndexes = new int[10];
            Arrays.fill(matchColumnIndexes, -1);
        }

        matchColumnIndexes[0] = columnIdx;
    }

    public void setMatchColumn(int[] columnIdxes) throws SQLException {
        if (columnIdxes == null) {
            throw new NullPointerException();
        }

        for (int i : columnIdxes) {
            if (i < 0) {
                // TODO why is 0 valid?
                // rowset.20=Match columns should be greater than 0
                throw new SQLException(Messages.getString("rowset.20")); //$NON-NLS-1$
            }
        }

        if (matchColumnIndexes == null) {
            /*
             * FIXME initial match column, the default length of array is 10 in
             * RI, we don't know why, just follow now
             */
            matchColumnIndexes = new int[10];
            Arrays.fill(matchColumnIndexes, -1);
        }

        int[] newValue = new int[matchColumnIndexes.length + columnIdxes.length];
        System.arraycopy(columnIdxes, 0, newValue, 0, columnIdxes.length);
        System.arraycopy(matchColumnIndexes, 0, newValue, columnIdxes.length,
                matchColumnIndexes.length);

        matchColumnIndexes = newValue;
    }

    public void setMatchColumn(String columnName) throws SQLException {
        if (columnName == null || columnName.equals("")) { //$NON-NLS-1$
            // rowset.12=Match columns should not be empty or null string
            throw new SQLException(Messages.getString("rowset.12")); //$NON-NLS-1$
        }

        if (matchColumnNames == null) {
            /*
             * FIXME initial match column, the default length of array is 10 in
             * RI, we don't know why, just follow now
             */
            matchColumnNames = new String[10];
        }

        matchColumnNames[0] = columnName;
    }

    public void setMatchColumn(String[] columnNames) throws SQLException {
        if (columnNames == null) {
            throw new NullPointerException();
        }
        for (String name : columnNames) {
            if (name == null || name.equals("")) { //$NON-NLS-1$
                // rowset.12=Match columns should not be empty or null string
                throw new SQLException(Messages.getString("rowset.12")); //$NON-NLS-1$
            }
        }

        if (matchColumnNames == null) {
            /*
             * FIXME initial match column, the default length of array is 10 in
             * RI, we don't know why, just follow now
             */
            matchColumnNames = new String[10];
        }

        String[] newValue = new String[matchColumnNames.length
                + columnNames.length];
        System.arraycopy(columnNames, 0, newValue, 0, columnNames.length);
        System.arraycopy(matchColumnNames, 0, newValue, columnNames.length,
                matchColumnNames.length);

        matchColumnNames = newValue;
    }

    public void unsetMatchColumn(int columnIdx) throws SQLException {

        if (matchColumnIndexes == null || matchColumnIndexes.length == 0
                || matchColumnIndexes[0] != columnIdx) {
            throw new SQLException(Messages.getString("rowset.15")); //$NON-NLS-1$
        }

        matchColumnIndexes[0] = -1;
    }

    public void unsetMatchColumn(int[] columnIdxes) throws SQLException {
        if (columnIdxes == null) {
            throw new NullPointerException();
        }

        if (columnIdxes.length == 0) {
            return;
        }

        if (matchColumnIndexes == null
                || matchColumnIndexes.length < columnIdxes.length) {
            throw new SQLException(Messages.getString("rowset.15")); //$NON-NLS-1$
        }

        for (int i = 0; i < columnIdxes.length; i++) {
            if (matchColumnIndexes[i] != columnIdxes[i]) {
                throw new SQLException(Messages.getString("rowset.15")); //$NON-NLS-1$   
            }
        }

        Arrays.fill(matchColumnIndexes, 0, columnIdxes.length, -1);
    }

    public void unsetMatchColumn(String columnName) throws SQLException {
        if (matchColumnNames == null || matchColumnNames.length == 0
                || !matchColumnNames[0].equals(columnName)) {
            throw new SQLException(Messages.getString("rowset.15")); //$NON-NLS-1$
        }

        matchColumnNames[0] = null;

    }

    public void unsetMatchColumn(String[] columnName) throws SQLException {
        if (columnName == null) {
            throw new NullPointerException();
        }

        if (columnName.length == 0) {
            return;
        }

        if (matchColumnNames == null
                || matchColumnNames.length < columnName.length) {
            throw new SQLException(Messages.getString("rowset.15")); //$NON-NLS-1$
        }

        for (int i = 0; i < columnName.length; i++) {
            if (matchColumnNames[i] != columnName[i]) {
                throw new SQLException(Messages.getString("rowset.15")); //$NON-NLS-1$   
            }
        }

        Arrays.fill(matchColumnNames, 0, columnName.length, null);
    }

    public boolean absolute(int row) throws SQLException {
        return doAbsolute(getIndexIncludeDeletedRows(row), true);
    }

    /**
     * internal implement of absolute
     *
     * @param row
     *            index of row cursor to move, include deleted rows
     * @param checkType
     *            whether to check property ResultSet.TYPE_FORWARD_ONLY
     * @return whether the cursor is on result set
     * @throws SQLException
     */
    protected boolean doAbsolute(int row, boolean checkType)
            throws SQLException {
        if (isCursorOnInsert) {
            // rowset.0=Not a valid position
            throw new SQLException(Messages.getString("rowset.0")); //$NON-NLS-1$
        }
        if (rows == null || rows.size() == 0) {
            if (isNotifyListener) {
                notifyCursorMoved();
            }
            return false;
        }

        if (checkType && getType() == ResultSet.TYPE_FORWARD_ONLY) {
            // rowset.8=The Result Set Type is TYPE_FORWARD_ONLY
            throw new SQLException(Messages.getString("rowset.8")); //$NON-NLS-1$
        }

        if (row < 0) {
            row = rows.size() + row + 1;
        }

        if (row <= 0) {
            currentRowIndex = 0;
            currentRow = null;
            if (isNotifyListener) {
                notifyCursorMoved();
            }
            return false;
        }

        if (row > rows.size()) {
            currentRowIndex = rows.size() + 1;
            currentRow = null;
            if (isNotifyListener) {
                notifyCursorMoved();
            }
            return false;
        }

        currentRowIndex = row;
        currentRow = rows.get(currentRowIndex - 1);
        if (isNotifyListener) {
            notifyCursorMoved();
        }
        return true;
    }

    public void afterLast() throws SQLException {
        if (rows == null) {
            return;
        }

        doAbsolute(rows.size() + 1, true);
    }

    public void beforeFirst() throws SQLException {
        doAbsolute(0, true);
    }

    public void cancelRowUpdates() throws SQLException {
        if (currentRow == null) {
            // rowset.7=Not a valid cursor
            throw new SQLException(Messages.getString("rowset.7")); //$NON-NLS-1$
        }
        if (isCursorOnInsert) {
            // rowset.11=Illegal operation on an insert row
            throw new SQLException(Messages.getString("rowset.11")); //$NON-NLS-1$
        }

        if (rowUpdated()) {
            currentRow.restoreOriginal();
            if (isNotifyListener) {
                notifyRowChanged();
            }
        }
    }

    public void clearWarnings() throws SQLException {
        sqlwarn = null;
    }

    public void close() throws SQLException {
        String username = getUsername();
        String password = getPassword();
        initialProperties();
        setUsername(username);
        setPassword(password);

        rows = new ArrayList<CachedRow>();
        currentRowIndex = 0;
        currentRow = null;
        deletedRowCount = 0;
        isCursorOnInsert = false;
        isLastColNull = false;
        matchColumnNames = null;
        matchColumnIndexes = null;
        conn = null;
    }

    public void deleteRow() throws SQLException {
        checkValidRow();
        if (isCursorOnInsert) {
            // rowset.11=Illegal operation on an insert row
            throw new SQLException(Messages.getString("rowset.11")); //$NON-NLS-1$
        }
        currentRow.setDelete();
        deletedRowCount++;
        if (isNotifyListener) {
            notifyRowChanged();
        }
    }

    private void checkValidRow() throws SQLException {
        if (currentRow == null) {
            // rowset.7=Not a valid cursor
            throw new SQLException(Messages.getString("rowset.7")); //$NON-NLS-1$
        }
    }

    /**
     * convert <code>index</code> consider property <code>showDeleted</code>.
     * If <code>showDeleted</code> is true, do nothing, otherwise, re-compute
     * <code>index</code> add deleted rows.
     *
     * @param index
     *            maybe negative, indicate the row number counting from the end
     *            of the result set
     * @return row index include delted rows
     */
    private int getIndexIncludeDeletedRows(int index) throws SQLException {
        if (rows == null || rows.size() == 0) {
            return -1;
        }

        if (getShowDeleted()) {
            return index;
        }

        if (index == 0) {
            return 0;
        }

        if (index > 0) {
            int indexIncludeDeletedRows = 0;
            for (; index > 0; ++indexIncludeDeletedRows) {
                if (indexIncludeDeletedRows == rows.size()) {
                    indexIncludeDeletedRows += index;
                    break;
                }

                if (!rows.get(indexIncludeDeletedRows).isDelete()) {
                    index--;
                }
            }
            return indexIncludeDeletedRows;
        }

        // index < 0
        int indexIncludeDeletedRows = rows.size();
        for (; index < 0; --indexIncludeDeletedRows) {
            if (indexIncludeDeletedRows == 0) {
                break;
            }

            if (!rows.get(indexIncludeDeletedRows - 1).isDelete()) {
                index++;
            }
        }
        if (indexIncludeDeletedRows != 0) {
            indexIncludeDeletedRows++;
        }

        return indexIncludeDeletedRows;
    }

    /**
     * If <code>showDeleted</code> property is true, return the rows size
     * include deleted rows. Otherwise not include deleted rows.
     *
     * @return
     * @throws SQLException
     */
    private int getValidRowSize() throws SQLException {
        if (rows == null) {
            return 0;
        }

        if (getShowDeleted()) {
            return rows.size();
        }

        return rows.size() - deletedRowCount;
    }

    public int findColumn(String columnName) throws SQLException {
        return getIndexByName(columnName);
    }

    public boolean first() throws SQLException {
        return doAbsolute(getIndexIncludeDeletedRows(1), true);
    }

    public Array getArray(int columnIndex) throws SQLException {
        Object obj = getObject(columnIndex);
        if (obj == null) {
            return null;
        }
        if (obj instanceof Array) {
            return (Array) obj;
        }
        // rowset.10=Data Type Mismatch
        throw new SQLException(Messages.getString("rowset.10")); //$NON-NLS-1$
    }

    public Array getArray(String colName) throws SQLException {
        return getArray(getIndexByName(colName));
    }

    public InputStream getAsciiStream(int columnIndex) throws SQLException {
        Object obj = getObject(columnIndex);
        if (obj == null) {
            return null;
        }
        if (obj instanceof String) {
            obj = ((String) obj).toCharArray();
        }

        if (obj instanceof char[]) {
            char[] cs = (char[]) obj;
            byte[] bs = new byte[cs.length];

            for (int i = 0; i < cs.length; i++) {
                // if out of range, convert to unknown char ox3F
                if (cs[i] > Byte.MAX_VALUE || cs[i] < Byte.MIN_VALUE) {
                    bs[i] = 63;
                } else {
                    bs[i] = (byte) cs[i];
                }
            }

            return new ByteArrayInputStream(bs);
        }
        // rowset.10=Data Type Mismatch
        throw new SQLException(Messages.getString("rowset.10")); //$NON-NLS-1$
    }

    public InputStream getAsciiStream(String columnName) throws SQLException {
        return getAsciiStream(getIndexByName(columnName));
    }

    public BigDecimal getBigDecimal(int columnIndex) throws SQLException {
        Object obj = getObject(columnIndex);
        if (obj == null) {
            return null;
        }
        if (obj instanceof BigDecimal) {
            return (BigDecimal) obj;
        }
        try {
            return new BigDecimal(obj.toString());
        } catch (NumberFormatException e) {
            // rowset.10=Data Type Mismatch
            throw new SQLException(Messages.getString("rowset.10")); //$NON-NLS-1$
        }
    }

    public BigDecimal getBigDecimal(int columnIndex, int scale)
            throws SQLException {
        BigDecimal big = getBigDecimal(columnIndex);
        if (big == null) {
            return null;
        }
        return big.setScale(scale);
    }

    public BigDecimal getBigDecimal(String columnName) throws SQLException {
        return getBigDecimal(getIndexByName(columnName));
    }

    public BigDecimal getBigDecimal(String columnName, int scale)
            throws SQLException {
        return getBigDecimal(getIndexByName(columnName), scale);
    }

    public InputStream getBinaryStream(int columnIndex) throws SQLException {
        Object obj = getObject(columnIndex);
        if (obj == null) {
            return null;
        }
        if (obj instanceof byte[]) {
            return new ByteArrayInputStream((byte[]) obj);
        }

        // rowset.10=Data Type Mismatch
        throw new SQLException(Messages.getString("rowset.10")); //$NON-NLS-1$
    }

    public InputStream getBinaryStream(String columnName) throws SQLException {
        return getBinaryStream(getIndexByName(columnName));
    }

    public Blob getBlob(int columnIndex) throws SQLException {
        Object obj = getObject(columnIndex);
        if (obj == null) {
            return null;
        }
        if (obj instanceof Blob) {
            return (Blob) obj;
        }
        // rowset.10=Data Type Mismatch
        throw new SQLException(Messages.getString("rowset.10")); //$NON-NLS-1$
    }

    public Blob getBlob(String columnName) throws SQLException {
        return getBlob(getIndexByName(columnName));
    }

    public boolean getBoolean(int columnIndex) throws SQLException {
        Object obj = getObject(columnIndex);
        if (obj == null) {
            return false;
        }
        if (obj instanceof Boolean) {
            return (Boolean) obj;
        }
        if (obj instanceof Number) {
            if ("0".equals(obj.toString()) || "0.0".equals(obj.toString())) { //$NON-NLS-1$ //$NON-NLS-2$
                return false;
            }
            return true;
        }
        // rowset.10=Data type mismatch
        throw new SQLException(Messages.getString("rowset.10")); //$NON-NLS-1$
    }

    public boolean getBoolean(String columnName) throws SQLException {
        return getBoolean(getIndexByName(columnName));
    }

    public byte getByte(int columnIndex) throws SQLException {
        Object obj = getObject(columnIndex);
        if (obj == null) {
            return 0;
        }
        if (obj instanceof Byte) {
            return (Byte) obj;
        }
        try {
            return Byte.parseByte(obj.toString());
        } catch (NumberFormatException e) {
            // rowset.10=Data Type Mismatch
            throw new SQLException(Messages.getString("rowset.10")); //$NON-NLS-1$
        }
    }

    public byte getByte(String columnName) throws SQLException {
        return getByte(getIndexByName(columnName));
    }

    public byte[] getBytes(int columnIndex) throws SQLException {
        Object obj = getObject(columnIndex);
        if (obj == null) {
            return null;
        }
        if (obj instanceof byte[]) {
            return (byte[]) obj;
        }
        // rowset.10=Data Type Mismatch
        throw new SQLException(Messages.getString("rowset.10")); //$NON-NLS-1$
    }

    public byte[] getBytes(String columnName) throws SQLException {
        return getBytes(getIndexByName(columnName));
    }

    public Reader getCharacterStream(int columnIndex) throws SQLException {
        Object obj = getObject(columnIndex);
        if (obj == null) {
            return null;
        }
        if (obj instanceof String) {
            return new StringReader((String) obj);
        }

        if (obj instanceof byte[]) {
            return new StringReader(new String((byte[]) obj));
        }

        if (obj instanceof char[]) {
            return new StringReader(new String((char[]) obj));
        }
        // rowset.10=Data Type Mismatch
        throw new SQLException(Messages.getString("rowset.10")); //$NON-NLS-1$
    }

    public Reader getCharacterStream(String columnName) throws SQLException {
        return getCharacterStream(getIndexByName(columnName));
    }

    public Clob getClob(int columnIndex) throws SQLException {
        Object obj = getObject(columnIndex);
        if (obj == null) {
            return null;
        }
        if (obj instanceof Clob) {
            return (Clob) obj;
        }
        // rowset.10=Data Type Mismatch
        throw new SQLException(Messages.getString("rowset.10")); //$NON-NLS-1$
    }

    public Clob getClob(String colName) throws SQLException {
        return getClob(getIndexByName(colName));
    }

    public String getCursorName() throws SQLException {
        if (cursorName == null) {
            // rowset.14=Positioned updates not supported
            throw new SQLException(Messages.getString("rowset.14")); //$NON-NLS-1$
        }
        return cursorName;
    }

    public Date getDate(int columnIndex) throws SQLException {
        Object obj = getObject(columnIndex);
        if (obj == null) {
            return null;
        }
        if (obj instanceof Date) {
            return (Date) obj;
        } else if (obj instanceof Timestamp) {
            return new Date(((Timestamp) obj).getTime());
        }
        // rowset.10=Data Type Mismatch
        throw new SQLException(Messages.getString("rowset.10")); //$NON-NLS-1$
    }

    public Date getDate(int columnIndex, Calendar cal) throws SQLException {
        Date date = getDate(columnIndex);
        if (date == null) {
            return null;
        }

        Calendar tempCal = Calendar.getInstance(cal.getTimeZone());
        tempCal.setTime(date);
        cal.set(tempCal.get(Calendar.YEAR), tempCal.get(Calendar.MONTH),
                tempCal.get(Calendar.DAY_OF_MONTH));
        return new Date(cal.getTimeInMillis());
    }

    public Date getDate(String columnName) throws SQLException {
        return getDate(getIndexByName(columnName));
    }

    public Date getDate(String columnName, Calendar cal) throws SQLException {
        return getDate(getIndexByName(columnName), cal);
    }

    public double getDouble(int columnIndex) throws SQLException {
        Object obj = getObject(columnIndex);
        if (obj == null) {
            return 0;
        }
        if (obj instanceof Double) {
            return (Double) obj;
        }
        try {
            return Double.parseDouble(obj.toString());
        } catch (NumberFormatException e) {
            // rowset.10=Data Type Mismatch
            throw new SQLException(Messages.getString("rowset.10")); //$NON-NLS-1$
        }
    }

    public double getDouble(String columnName) throws SQLException {
        return getDouble(getIndexByName(columnName));
    }

    public float getFloat(int columnIndex) throws SQLException {
        Object obj = getObject(columnIndex);
        if (obj == null) {
            return 0;
        }
        if (obj instanceof Float) {
            return (Float) obj;
        }
        try {
            return Float.parseFloat(obj.toString());
        } catch (NumberFormatException e) {
            // rowset.10=Data Type Mismatch
            throw new SQLException(Messages.getString("rowset.10")); //$NON-NLS-1$
        }
    }

    public float getFloat(String columnName) throws SQLException {
        return getFloat(getIndexByName(columnName));
    }

    public int getInt(int columnIndex) throws SQLException {
        Object obj = getObject(columnIndex);
        if (obj == null) {
            return 0;
        }
        if (obj instanceof Integer) {
            return (Integer) obj;
        }
        try {
            return Integer.parseInt(obj.toString());
        } catch (NumberFormatException e) {
            // rowset.10=Data Type Mismatch
            throw new SQLException(Messages.getString("rowset.10")); //$NON-NLS-1$
        }
    }

    public int getInt(String columnName) throws SQLException {
        return getInt(getIndexByName(columnName));
    }

    public long getLong(int columnIndex) throws SQLException {
        Object obj = getObject(columnIndex);
        if (obj == null) {
            return 0;
        }
        if (obj instanceof Long) {
            return (Long) obj;
        }
        try {
            return Long.parseLong(obj.toString());
        } catch (NumberFormatException e) {
            // rowset.10=Data Type Mismatch
            throw new SQLException(Messages.getString("rowset.10")); //$NON-NLS-1$
        }
    }

    public long getLong(String columnName) throws SQLException {
        return getLong(getIndexByName(columnName));
    }

    public ResultSetMetaData getMetaData() throws SQLException {
        return meta;
    }

    public Object getObject(int columnIndex) throws SQLException {
        if (meta == null || currentRow == null) {
            // rowset.7=Not a valid cursor
            throw new SQLException(Messages.getString("rowset.7")); //$NON-NLS-1$
        }
        if (columnIndex <= 0 || columnIndex > columnCount) {
            // sql.27=Invalid column index :{0}
            throw new SQLException(Messages.getString("sql.27", columnIndex)); //$NON-NLS-1$
        }

        Object obj = currentRow.getObject(columnIndex);
        if (obj == null) {
            isLastColNull = true;
        } else {
            isLastColNull = false;
        }
        return obj;
    }

    public Object getObject(int columnIndex, Map<String, Class<?>> map)
            throws SQLException {
        // FIXME the usage of map
        return getObject(columnIndex);
    }

    public Object getObject(String columnName) throws SQLException {
        return getObject(getIndexByName(columnName));
    }

    public Object getObject(String columnName, Map<String, Class<?>> map)
            throws SQLException {
        return getObject(getIndexByName(columnName), map);
    }

    public Ref getRef(int columnIndex) throws SQLException {
        Object obj = getObject(columnIndex);
        if (obj == null) {
            return null;
        }
        if (obj instanceof Ref) {
            return (Ref) obj;
        }
        // rowset.10=Data Type Mismatch
        throw new SQLException(Messages.getString("rowset.10")); //$NON-NLS-1$
    }

    public Ref getRef(String colName) throws SQLException {
        return getRef(getIndexByName(colName));
    }

    public int getRow() throws SQLException {
        if (currentRow == null || rows == null || isCursorOnInsert) {
            return 0;
        }

        if (!getShowDeleted() && currentRow.isDelete()) {
            return 0;
        }

        if (getShowDeleted() || currentRowIndex == 0) {
            return currentRowIndex;
        }

        // doesn't show deleted rows, skip them
        int index = 0;
        for (int i = 0; i < currentRowIndex; ++i) {
            if (!rows.get(i).isDelete()) {
                index++;
            }
        }
        return index;

    }

    public short getShort(int columnIndex) throws SQLException {
        Object obj = getObject(columnIndex);
        if (obj == null) {
            return 0;
        }
        if (obj instanceof Short) {
            return (Short) obj;
        }
        try {
            return Short.parseShort(obj.toString());
        } catch (NumberFormatException e) {
            // rowset.10=Data Type Mismatch
            throw new SQLException(Messages.getString("rowset.10")); //$NON-NLS-1$
        }
    }

    public short getShort(String columnName) throws SQLException {
        return getShort(getIndexByName(columnName));
    }

    public Statement getStatement() throws SQLException {
        return null;
    }

    // columnIndex: from 1 rather than 0
    public String getString(int columnIndex) throws SQLException {
        Object obj = getObject(columnIndex);
        if (obj == null) {
            return null;
        }
        return obj.toString();
    }

    private void checkColumnValid(int columnIndex) throws SQLException {
        if (columnIndex <= 0 || columnIndex > meta.getColumnCount()) {
            // sql.27=Invalid column index :{0}
            throw new SQLException(Messages.getString("sql.27", Integer //$NON-NLS-1$
                    .valueOf(columnIndex)));
        }
    }

    public String getString(String columnName) throws SQLException {
        return getString(getIndexByName(columnName));
    }

    public Time getTime(int columnIndex) throws SQLException {
        Object obj = getObject(columnIndex);
        if (obj == null) {
            return null;
        }
        if (obj instanceof Time) {
            return (Time) obj;
        } else if (obj instanceof Timestamp) {
            return new Time(((Timestamp) obj).getTime());
        }
        // rowset.10=Data Type Mismatch
        throw new SQLException(Messages.getString("rowset.10")); //$NON-NLS-1$
    }

    public Time getTime(int columnIndex, Calendar cal) throws SQLException {
        Time time = getTime(columnIndex);
        if (time == null) {
            return null;
        }
        Calendar tempCal = Calendar.getInstance(cal.getTimeZone());
        tempCal.setTimeInMillis(time.getTime());
        cal.set(Calendar.HOUR, tempCal.get(Calendar.HOUR));
        cal.set(Calendar.MINUTE, tempCal.get(Calendar.MINUTE));
        cal.set(Calendar.SECOND, tempCal.get(Calendar.SECOND));
        return new Time(cal.getTimeInMillis());
    }

    public Time getTime(String columnName) throws SQLException {
        return getTime(getIndexByName(columnName));
    }

    public Time getTime(String columnName, Calendar cal) throws SQLException {
        return getTime(getIndexByName(columnName), cal);
    }

    public Timestamp getTimestamp(int columnIndex) throws SQLException {
        Object obj = getObject(columnIndex);
        if (obj == null) {
            return null;
        }
        if (obj instanceof Date) {
            return new Timestamp(((Date) obj).getTime());
        } else if (obj instanceof Time) {
            return new Timestamp(((Time) obj).getTime());
        }
        try {
            return Timestamp.valueOf(obj.toString());
        } catch (Exception e) {
            // rowset.10=Data Type Mismatch
            throw new SQLException(Messages.getString("rowset.10")); //$NON-NLS-1$
        }
    }

    public Timestamp getTimestamp(int columnIndex, Calendar cal)
            throws SQLException {
        Timestamp timestamp = getTimestamp(columnIndex);
        if (timestamp == null) {
            return null;
        }
        Calendar tempCal = Calendar.getInstance(cal.getTimeZone());
        tempCal.setTimeInMillis(timestamp.getTime());
        cal.set(tempCal.get(Calendar.YEAR), tempCal.get(Calendar.MONDAY),
                tempCal.get(Calendar.DAY_OF_MONTH), tempCal.get(Calendar.HOUR),
                tempCal.get(Calendar.MINUTE), tempCal.get(Calendar.SECOND));
        return new Timestamp(cal.getTimeInMillis());
    }

    public Timestamp getTimestamp(String columnName) throws SQLException {
        return getTimestamp(getIndexByName(columnName));
    }

    public Timestamp getTimestamp(String columnName, Calendar cal)
            throws SQLException {
        return getTimestamp(getIndexByName(columnName), cal);
    }

    public java.net.URL getURL(int columnIndex) throws SQLException {
        Object obj = getObject(columnIndex);
        if (obj == null) {
            return null;
        }
        if (obj instanceof java.net.URL) {
            return (java.net.URL) obj;
        }
        // rowset.10=Data Type Mismatch
        throw new SQLException(Messages.getString("rowset.10")); //$NON-NLS-1$
    }

    public java.net.URL getURL(String columnName) throws SQLException {
        return getURL(getIndexByName(columnName));
    }

    @SuppressWarnings("deprecation")
    public InputStream getUnicodeStream(int columnIndex) throws SQLException {
        Object obj = getObject(columnIndex);
        if (obj == null) {
            return null;
        }
        if (obj instanceof byte[]) {
            return new StringBufferInputStream(new String((byte[]) obj));
        }

        if (obj instanceof String) {
            return new StringBufferInputStream((String) obj);
        }

        if (obj instanceof char[]) {
            return new StringBufferInputStream(new String((char[]) obj));
        }

        // rowset.10=Data Type Mismatch
        throw new SQLException(Messages.getString("rowset.10")); //$NON-NLS-1$
    }

    public InputStream getUnicodeStream(String columnName) throws SQLException {
        return getUnicodeStream(getIndexByName(columnName));
    }

    public SQLWarning getWarnings() throws SQLException {
        if (sqlwarn == null) {
            return null;
        }
        if (currentRow != null && currentRow.getSqlWarning() != null) {
            return currentRow.getSqlWarning();
        }
        return sqlwarn;
    }

    public void insertRow() throws SQLException {
        checkValidRow();
        if (currentRow != insertRow) {
            // rowset.4=Not an insert row
            throw new SQLException(Messages.getString("rowset.4")); //$NON-NLS-1$
        }
        boolean isValueSet = false;
        for (int i = 0; i < columnCount; i++) {
            if (currentRow.getUpdateMask(i)) {
                isValueSet = true;
                break;
            }
        }
        if (!isValueSet) {
            // rowset.18=None column is updated
            throw new SQLException(Messages.getString("rowset.18")); //$NON-NLS-1$
        }
        insertRow.setInsert();
        if (rememberedCursorPosition > rows.size()) {
            rows.add(insertRow);
        } else {
            rows.add(rememberedCursorPosition, insertRow);
        }
        insertRow = null;
        if (isNotifyListener) {
            notifyRowChanged();
        }
    }

    public boolean isAfterLast() throws SQLException {
        if (rows == null || rows.size() == 0) {
            return false;
        }

        return currentRowIndex > rows.size();
    }

    public boolean isBeforeFirst() throws SQLException {
        if (rows == null || rows.size() == 0) {
            return false;
        }

        return currentRowIndex == 0;
    }

    public boolean isFirst() throws SQLException {
        return getRow() == 1;
    }

    public boolean isLast() throws SQLException {
        if (rows == null || rows.size() == 0) {
            return false;
        }

        return getRow() == getValidRowSize();
    }

    public boolean last() throws SQLException {
        return doAbsolute(getIndexIncludeDeletedRows(-1), true);
    }

    public void moveToCurrentRow() throws SQLException {
        if (isCursorOnInsert) {
            currentRowIndex = rememberedCursorPosition;
            if (currentRowIndex >= 1 && currentRowIndex <= rows.size()) {
                currentRow = rows.get(currentRowIndex - 1);
            } else {
                currentRow = null;
            }
            rememberedCursorPosition = -1;
            isCursorOnInsert = false;
        }
    }

    public void moveToInsertRow() throws SQLException {
        if (meta == null) {
            // rowset.26=The object has not been initialized
            throw new SQLException(Messages.getString("rowset.26")); //$NON-NLS-1$
        }
        insertRow = new CachedRow(new Object[columnCount]);
        currentRow = insertRow;
        if (!isCursorOnInsert) {
            rememberedCursorPosition = currentRowIndex;
            currentRowIndex = -1;
            isCursorOnInsert = true;
        }
    }

    public boolean next() throws SQLException {
        /*
         * spec next() is identical with relative(1), but they can't:
         *
         * next() doesn't check TYPE_FORWARD_ONLY property, relative(1) does.
         */
        return doAbsolute(findNextValidRow(), false);
    }

    /**
     * Valid row is row which is visible to users. If <code>showDeleted</code>
     * is true, deleted rows are valid rows, otherwise deleted rows are invalid.
     *
     * @return next valid row
     * @throws SQLException
     */
    private int findNextValidRow() throws SQLException {
        if (isCursorOnInsert) {
            // rowset.0=Not a valid position
            throw new SQLException(Messages.getString("rowset.0")); //$NON-NLS-1$
        }
        if (rows == null || rows.size() == 0) {
            return -1;
        }
        int index = currentRowIndex + 1;

        if (getShowDeleted()) {
            return index;
        }

        if (index > rows.size()) {
            return rows.size() + 1;
        }

        while (index <= rows.size()) {
            if (!rows.get(index - 1).isDelete()) {
                break;
            }
            index++;
        }

        return index;
    }

    public boolean previous() throws SQLException {
        return doAbsolute(findPreviousValidRow(), true);
    }

    /**
     * Valid row is row which is visible to users. If <code>showDeleted</code>
     * is true, deleted rows are valid rows, otherwise deleted rows are invalid.
     *
     * @return previous valid row
     * @throws SQLException
     */
    private int findPreviousValidRow() throws SQLException {
        if (isCursorOnInsert) {
            // rowset.0=Not a valid position
            throw new SQLException(Messages.getString("rowset.0")); //$NON-NLS-1$
        }
        if (rows == null || rows.size() == 0) {
            return -1;
        }
        int index = currentRowIndex - 1;

        if (index <= 0) {
            return 0;
        }

        if (getShowDeleted()) {
            return index;
        }

        while (index > 0) {
            if (!rows.get(index - 1).isDelete()) {
                break;
            }
            index--;
        }

        return index;
    }

    public void refreshRow() throws SQLException {
        checkValidRow();
        if (isCursorOnInsert) {
            // rowset.0=Not a valid position
            throw new SQLException(Messages.getString("rowset.0")); //$NON-NLS-1$
        }
        currentRow.restoreOriginal();
    }

    public boolean relative(int moveRows) throws SQLException {
        checkValidRow();
        // TODO use more effective way to move cursor
        int index = getRow() + moveRows;

        if (isCursorOnInsert || currentRow.isDelete()) {
            if (moveRows > 0) {
                if (next()) {
                    index = getRow() + moveRows - 1;
                } else {
                    return false;
                }
            }

            if (moveRows < 0) {
                if (previous()) {
                    index = getRow() + moveRows + 1;
                } else {
                    return false;
                }
            }
        }

        if (index <= 0) {
            beforeFirst();
            return false;
        }

        if (index > rows.size()) {
            afterLast();
            return false;
        }

        return doAbsolute(getIndexIncludeDeletedRows(index), true);
    }

    public boolean rowDeleted() throws SQLException {
        checkValidRow();
        return currentRow.isDelete();
    }

    public boolean rowInserted() throws SQLException {
        if (currentRow == null) {
            // rowset.7=Not a valid cursor
            throw new SQLException(Messages.getString("rowset.7")); //$NON-NLS-1$
        }
        if (isCursorOnInsert) {
            // rowset.11=Illegal operation on an insert row
            throw new SQLException(Messages.getString("rowset.11")); //$NON-NLS-1$
        }
        return currentRow.isInsert();
    }

    public boolean rowUpdated() throws SQLException {
        if (currentRow == null) {
            // rowset.7=Not a valid cursor
            throw new SQLException(Messages.getString("rowset.7")); //$NON-NLS-1$
        }
        if (isCursorOnInsert) {
            // rowset.11=Illegal operation on an insert row
            throw new SQLException(Messages.getString("rowset.11")); //$NON-NLS-1$
        }

        if (!currentRow.isUpdate()) {
            return false;
        }

        boolean sign = false;
        for (int i = 0; i < meta.getColumnCount(); ++i) {
            sign = currentRow.getUpdateMask(i) | sign;
        }
        return sign;
    }

    public void updateArray(int columnIndex, Array x) throws SQLException {
        updateByType(columnIndex, x);
    }

    public void updateArray(String columnName, Array x) throws SQLException {
        updateArray(getIndexByName(columnName), x);
    }

    public void updateAsciiStream(int columnIndex, InputStream in, int length)
            throws SQLException {
        checkValidRow();
        checkColumnValid(columnIndex);
        initInsertRow(columnIndex, in);

        Class<?> type = columnTypes[columnIndex - 1];
        if (type != null && !type.equals(String.class)
                && !type.equals(byte[].class)) {
            // rowset.10=Data Type Mismatch
            throw new SQLException(Messages.getString("rowset.10")); //$NON-NLS-1$
        }

        if (length < 0) {
            throw new NegativeArraySizeException();
        }

        byte[] byteArray = null;
        try {
            if (length == 0 && in.read() == -1) {
                throw new IndexOutOfBoundsException();
            }

            byteArray = new byte[length];
            for (int i = 0; i < length; ++i) {
                int value = in.read();
                if (value == -1) {
                    throw new IndexOutOfBoundsException();
                }
                byteArray[i] = (byte) value;
            }
        } catch (IOException e) {
            SQLException ex = new SQLException();
            ex.initCause(e);
            throw ex;
        }

        updateString(columnIndex, new String(byteArray));
    }

    public void updateAsciiStream(String columnName, InputStream x, int length)
            throws SQLException {
        updateAsciiStream(getIndexByName(columnName), x, length);
    }

    public void updateBigDecimal(int columnIndex, BigDecimal x)
            throws SQLException {
        if (x == null) {
            throw new NullPointerException();
        }

        updateByType(columnIndex, x);
    }

    public void updateBigDecimal(String columnName, BigDecimal x)
            throws SQLException {
        updateBigDecimal(getIndexByName(columnName), x);
    }

    public void updateBinaryStream(int columnIndex, InputStream in, int length)
            throws SQLException {
        checkValidRow();
        checkColumnValid(columnIndex);
        initInsertRow(columnIndex, in);

        Class<?> type = columnTypes[columnIndex - 1];
        if (type != null && !type.equals(byte[].class)) {
            // rowset.10=Data Type Mismatch
            throw new SQLException(Messages.getString("rowset.10")); //$NON-NLS-1$
        }

        if (length < 0) {
            throw new NegativeArraySizeException();
        }

        byte[] byteArray = new byte[length];
        try {
            for (int i = 0; i < length; ++i) {
                int value = in.read();
                if (value == -1) {
                    byteArray[i] = 0;
                } else {
                    byteArray[i] = (byte) value;
                }
            }

        } catch (IOException e) {
            SQLException ex = new SQLException();
            ex.initCause(e);
            throw ex;
        }

        updateBytes(columnIndex, byteArray);
    }

    public void updateBinaryStream(String columnName, InputStream x, int length)
            throws SQLException {
        updateBinaryStream(getIndexByName(columnName), x, length);
    }

    public void updateBlob(int columnIndex, Blob x) throws SQLException {
        updateByType(columnIndex, x);
    }

    public void updateBlob(String columnName, Blob x) throws SQLException {
        updateBlob(getIndexByName(columnName), x);
    }

    public void updateBoolean(int columnIndex, boolean x) throws SQLException {
        updateByType(columnIndex, Boolean.valueOf(x));
    }

    public void updateBoolean(String columnName, boolean x) throws SQLException {
        updateBoolean(getIndexByName(columnName), x);
    }

    public void updateByte(int columnIndex, byte x) throws SQLException {
        updateByType(columnIndex, Byte.valueOf(x));
    }

    public void updateByte(String columnName, byte x) throws SQLException {
        updateByte(getIndexByName(columnName), x);
    }

    public void updateBytes(int columnIndex, byte[] x) throws SQLException {
        updateByType(columnIndex, x);
    }

    public void updateBytes(String columnName, byte[] x) throws SQLException {
        updateBytes(getIndexByName(columnName), x);
    }

    public void updateCharacterStream(int columnIndex, Reader in, int length)
            throws SQLException {
        checkValidRow();
        checkColumnValid(columnIndex);
        initInsertRow(columnIndex, in);

        Class<?> type = columnTypes[columnIndex - 1];
        if (type != null && !type.equals(String.class)
                && !type.equals(byte[].class)) {
            // rowset.10=Data Type Mismatch
            throw new SQLException(Messages.getString("rowset.10")); //$NON-NLS-1$
        }

        if (length < 0) {
            throw new NegativeArraySizeException();
        }

        StringWriter out = new StringWriter();
        try {
            for (int i = 0; i < length; ++i) {
                int value = in.read();
                if (value == -1) {
                    throw new IndexOutOfBoundsException();
                }
                out.write(value);
            }
        } catch (IOException e) {
            SQLException ex = new SQLException();
            ex.initCause(e);
            throw ex;
        }

        updateString(columnIndex, out.toString());
    }

    public void updateCharacterStream(String columnName, Reader reader,
            int length) throws SQLException {
        updateCharacterStream(getIndexByName(columnName), reader, length);
    }

    public void updateClob(int columnIndex, Clob x) throws SQLException {
        updateByType(columnIndex, x);
    }

    public void updateClob(String columnName, Clob x) throws SQLException {
        updateClob(getIndexByName(columnName), x);
    }

    public void updateDate(int columnIndex, Date x) throws SQLException {
        updateByType(columnIndex, x);
    }

    public void updateDate(String columnName, Date x) throws SQLException {
        updateDate(getIndexByName(columnName), x);
    }

    public void updateDouble(int columnIndex, double x) throws SQLException {
        updateByType(columnIndex, Double.valueOf(x));
    }

    public void updateDouble(String columnName, double x) throws SQLException {
        updateDouble(getIndexByName(columnName), x);
    }

    public void updateFloat(int columnIndex, float x) throws SQLException {
        updateByType(columnIndex, Float.valueOf(x));
    }

    public void updateFloat(String columnName, float x) throws SQLException {
        updateFloat(getIndexByName(columnName), x);
    }

    public void updateInt(int columnIndex, int x) throws SQLException {
        updateByType(columnIndex, Integer.valueOf(x));
    }

    public void updateInt(String columnName, int x) throws SQLException {
        updateInt(getIndexByName(columnName), x);
    }

    public void updateLong(int columnIndex, long x) throws SQLException {
        updateByType(columnIndex, Long.valueOf(x));
    }

    public void updateLong(String columnName, long x) throws SQLException {
        updateLong(getIndexByName(columnName), x);
    }

    public void updateNull(int columnIndex) throws SQLException {
        checkValidRow();
        checkColumnValid(columnIndex);
        currentRow.updateObject(columnIndex, null);
    }

    public void updateNull(String columnName) throws SQLException {
        updateNull(getIndexByName(columnName));
    }

    /**
     * note check type compatibility
     */
    public void updateObject(int columnIndex, Object x) throws SQLException {
        checkValidRow();
        checkColumnValid(columnIndex);
        initInsertRow(columnIndex, x);
        currentRow.updateObject(columnIndex, x);
    }

    public void updateObject(int columnIndex, Object x, int scale)
            throws SQLException {
        checkValidRow();
        checkColumnValid(columnIndex);
        Class<?> type = columnTypes[columnIndex - 1];
        // ava.sql.Types.DECIMA or java.sql.Types.NUMERIC types
        if (type.equals(BigDecimal.class)) {
            /*
             * TODO ri doesn't check type of x and only support BigDecimal,
             * should we follow ri here? If not, uncomment below fragment of
             * code
             */
            // if (x instanceof BigDecimal) {
            x = ((BigDecimal) x).setScale(scale);
            // } else if (x instanceof Double) {
            // x = new BigDecimal(((Double) x).doubleValue());
            // x = ((BigDecimal) x).setScale(scale);
            // } else if (x instanceof Float) {
            // x = new BigDecimal(((Float) x).doubleValue());
            // x = ((BigDecimal) x).setScale(scale);
            // }
        }
        initInsertRow(columnIndex, x);
        currentRow.updateObject(columnIndex, x);
    }

    public void updateObject(String columnName, Object x) throws SQLException {
        updateObject(getIndexByName(columnName), x);
    }

    public void updateObject(String columnName, Object x, int scale)
            throws SQLException {
        updateObject(getIndexByName(columnName), x, scale);
    }

    public void updateRef(int columnIndex, Ref x) throws SQLException {
        updateByType(columnIndex, x);
    }

    public void updateRef(String columnName, Ref x) throws SQLException {
        updateRef(getIndexByName(columnName), x);
    }

    public void updateRow() throws SQLException {
        if (currentRow == null) {
            // rowset.7=Not a valid cursor
            throw new SQLException(Messages.getString("rowset.7")); //$NON-NLS-1$
        }
        if (isCursorOnInsert) {
            // rowset.11=Illegal operation on an insert row
            throw new SQLException(Messages.getString("rowset.11")); //$NON-NLS-1$
        }
        if (getConcurrency() == (ResultSet.CONCUR_READ_ONLY)) {
            // rowset.17=The Result Set is CONCUR_READ_ONLY
            throw new SQLException(Messages.getString("rowset.17")); //$NON-NLS-1$
        }
        currentRow.setUpdate();
        if (isNotifyListener) {
            notifyRowChanged();
        }
    }

    public void updateShort(int columnIndex, short x) throws SQLException {
        updateByType(columnIndex, Short.valueOf(x));
    }

    public void updateShort(String columnName, short x) throws SQLException {
        updateShort(getIndexByName(columnName), x);
    }

    public void updateString(int columnIndex, String x) throws SQLException {
        updateByType(columnIndex, x);
    }

    /**
     * Check type compatibility and update value
     *
     * @param columnIndex
     * @param value
     * @throws SQLException
     */
    private void updateByType(int columnIndex, Object value)
            throws SQLException {
        checkValidRow();
        checkColumnValid(columnIndex);
        initInsertRow(columnIndex, value);
        currentRow.updateObject(columnIndex, convertUpdateValue(columnIndex,
                value));
    }

    /**
     * The implementation of FilteredRowSet would override this method. The
     * parameters also are used for override.
     *
     * @param columnIndex
     * @param value
     */
    protected void initInsertRow(int columnIndex, Object value)
            throws SQLException {
        if (isCursorOnInsert && insertRow == null) {
            insertRow = new CachedRow(new Object[columnCount]);
            currentRow = insertRow;
        }
    }

    /**
     * Convert <code>value</code> to the JDBC type of the
     * <code>columnIndex</code>. The columnIndex is not checked in this
     * method, so caller must be sure the <code>columnIndex</code> is valid,
     * or invoke <code>checkColumnValid</code> before invoke this method.
     *
     * TODO any better ways to do this?
     *
     * @param columnIndex
     *            index of column to be updated
     * @param value
     *            the new value to be updated
     */
    @SuppressWarnings("boxing")
    private Object convertUpdateValue(int columnIndex, Object value)
            throws SQLException {

        if (value == null) {
            return value;
        }

        Class<?> type = columnTypes[columnIndex - 1];

        /*
         * TODO if type == null, the type mapping is not supported by Harmony
         * now, leave this type check to JDBC driver
         */

        if (type == null) {
            return value;
        }

        // convert to serializable object
        if (type.isInstance(value)) {
            if (type.equals(Array.class) && !(value instanceof SerialArray)) {
                return new SerialArray((Array) value);
            }

            if (type.equals(Blob.class) && !(value instanceof SerialBlob)) {
                return new SerialBlob((Blob) value);
            }

            if (type.equals(Clob.class) && !(value instanceof SerialClob)) {
                return new SerialClob((Clob) value);
            }

            if (type.equals(Ref.class) && !(value instanceof SerialRef)) {
                return new SerialRef((Ref) value);
            }

            return value;
        }

        if (type.equals(byte[].class)) {
            return value;
        }

        if (type.equals(String.class)) {
            if (!(value instanceof Array) && !(value instanceof byte[])) {
                return value.toString();
            }
        }

        if (type.equals(Integer.class)) {
            if (value instanceof Integer || value instanceof Short
                    || value instanceof Byte) {
                return value;
            }

            if (value instanceof Long) {
                long l = ((Long) value).longValue();
                if (l >= Integer.MIN_VALUE && l <= Integer.MAX_VALUE) {
                    return (int) l;
                }
            }

            if (value instanceof BigDecimal) {
                BigDecimal bigDecimal = (BigDecimal) value;
                try {
                    return bigDecimal.intValueExact();

                } catch (ArithmeticException e) {
                    // rowset.10=Data Type Mismatch
                    throw new SQLException(Messages.getString("rowset.10")); //$NON-NLS-1$
                }
            }

            if (value instanceof String) {
                return value;
            }
        }

        if (type.equals(Short.class)) {
            if (value instanceof Short || value instanceof Byte) {
                return value;
            }
            if (value instanceof Long) {
                long l = ((Long) value).longValue();
                if (l >= Short.MIN_VALUE && l <= Short.MAX_VALUE) {
                    return (short) l;
                }
            }
            if (value instanceof Integer) {
                int i = ((Integer) value).intValue();
                if (i >= Short.MIN_VALUE && i <= Short.MAX_VALUE) {
                    return (short) i;
                }
            }
            if (value instanceof BigDecimal) {
                BigDecimal bigDecimal = (BigDecimal) value;
                try {
                    return bigDecimal.intValueExact();

                } catch (ArithmeticException e) {
                    // rowset.10=Data Type Mismatch
                    throw new SQLException(Messages.getString("rowset.10")); //$NON-NLS-1$
                }
            }
            if (value instanceof String) {
                return value;
            }
        }

        if (type.equals(Byte.class)) {
            if (value instanceof Byte) {
                return value;
            }
            if (value instanceof Long) {
                long l = ((Long) value).longValue();
                if (l >= Byte.MIN_VALUE && l <= Byte.MAX_VALUE) {
                    return (byte) l;
                }
            }
            if (value instanceof Integer) {
                int i = ((Integer) value).intValue();
                if (i >= Byte.MIN_VALUE && i <= Byte.MAX_VALUE) {
                    return (byte) i;
                }
            }
            if (value instanceof Short) {
                int i = ((Short) value).shortValue();
                if (i >= Byte.MIN_VALUE && i <= Byte.MAX_VALUE) {
                    return (byte) i;
                }
            }
            if (value instanceof BigDecimal) {
                BigDecimal bigDecimal = (BigDecimal) value;
                try {
                    return bigDecimal.byteValueExact();

                } catch (ArithmeticException e) {
                    // rowset.10=Data Type Mismatch
                    throw new SQLException(Messages.getString("rowset.10")); //$NON-NLS-1$
                }
            }
            if (value instanceof String) {
                return value;
            }
        }

        if (type.equals(Long.class)) {
            if (value instanceof Integer || value instanceof Short
                    || value instanceof Byte || value instanceof Long) {
                return value;
            }
            if (value instanceof BigDecimal) {
                BigDecimal bigDecimal = (BigDecimal) value;
                try {
                    return bigDecimal.longValueExact();

                } catch (ArithmeticException e) {
                    // rowset.10=Data Type Mismatch
                    throw new SQLException(Messages.getString("rowset.10")); //$NON-NLS-1$
                }
            }
            if (value instanceof String) {
                return value;
            }
        }

        if (type.equals(Float.class) || type.equals(Double.class)) {
            if (value instanceof Float || value instanceof Double
                    || value instanceof BigDecimal) {
                return value;
            }
            if (value instanceof Number) {
                return ((Number) value).longValue();
            }
            if (value instanceof String) {
                return value;
            }
        }

        if (type.equals(BigDecimal.class)) {
            return value;
        }

        if (type.equals(Date.class)) {
            if (value instanceof Timestamp) {
                Timestamp timestamp = (Timestamp) value;
                return new Date(timestamp.getTime());
            }

            if (value instanceof String) {
                return value;
            }
        }

        if (type.equals(Time.class)) {
            if (value instanceof Timestamp) {
                Timestamp timestamp = (Timestamp) value;
                return new Time(timestamp.getTime());
            }

            if (value instanceof String) {
                return value;
            }
        }

        if (type.equals(Timestamp.class)) {
            if (value instanceof Date) {
                Date date = (Date) value;
                return new Timestamp(date.getTime());
            }
            if (value instanceof Time) {
                Time time = (Time) value;
                return new Timestamp(time.getTime());
            }

            if (value instanceof String) {
                return value;
            }
        }

        // rowset.10=Data Type Mismatch
        throw new SQLException(Messages.getString("rowset.10")); //$NON-NLS-1$
    }

    public void updateString(String columnName, String x) throws SQLException {
        updateString(getIndexByName(columnName), x);
    }

    public void updateTime(int columnIndex, Time x) throws SQLException {
        updateByType(columnIndex, x);
    }

    public void updateTime(String columnName, Time x) throws SQLException {
        updateTime(getIndexByName(columnName), x);
    }

    public void updateTimestamp(int columnIndex, Timestamp x)
            throws SQLException {
        updateByType(columnIndex, x);
    }

    public void updateTimestamp(String columnName, Timestamp x)
            throws SQLException {
        updateTimestamp(getIndexByName(columnName), x);
    }

    public boolean wasNull() throws SQLException {
        return isLastColNull;
    }

    public void execute() throws SQLException {
        Connection preConn = conn;
        execute(retrieveConnection());
        conn = preConn;
    }

    public Connection getConnection() throws SQLException {
        return conn;
    }

    private Connection retrieveConnection() throws SQLException {
        if (getUrl() != null) {
            return DriverManager.getConnection(getUrl(), getUsername(),
                    getPassword());
        } else if (getDataSourceName() != null) {
            try {
                Context contex = new InitialContext();
                DataSource ds = (DataSource) contex.lookup(getDataSourceName());
                return ds.getConnection();
            } catch (Exception e) {
                // rowset.25=(JNDI)Unable to get connection
                SQLException ex = new SQLException(Messages
                        .getString("rowset.25")); //$NON-NLS-1$
                throw ex;
            }
        }
        // rowset.24=Unable to get connection
        throw new SQLException(Messages.getString("rowset.24")); //$NON-NLS-1$
    }

    CachedRow getCurrentRow() {
        return currentRow;
    }

    @Override
    public void setCommand(String cmd) throws SQLException {
        initParams();
        super.setCommand(cmd);
    }

    protected boolean isCursorOnInsert() {
        return isCursorOnInsert;
    }

    protected boolean isNotifyListener() {
        return isNotifyListener;
    }

    protected void setIsNotifyListener(boolean isNotifyListener) {
        this.isNotifyListener = isNotifyListener;
    }

    protected CachedRow getInsertRow() {
        return insertRow;
    }
}
TOP

Related Classes of org.apache.harmony.sql.internal.rowset.CachedRowSetImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.