/**
 * 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.jena.jdbc.statements;

import java.io.InputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.net.URI;
import java.net.URL;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Date;
import java.sql.NClob;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.Ref;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.RowId;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.SQLXML;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Calendar;
import java.util.TimeZone;

import org.apache.jena.datatypes.xsd.XSDDatatype ;
import org.apache.jena.graph.Node ;
import org.apache.jena.graph.NodeFactory ;
import org.apache.jena.iri.IRI;
import org.apache.jena.jdbc.connections.JenaConnection;
import org.apache.jena.jdbc.statements.metadata.JenaParameterMetadata;
import org.apache.jena.jdbc.utils.JdbcNodeUtils;
import org.apache.jena.query.ParameterizedSparqlString ;
import org.apache.jena.rdf.model.RDFNode ;
import org.apache.jena.sparql.util.NodeFactoryExtra ;

/**
 * Abstract Jena JDBC implementation of a prepared statement
 * 
 */
public abstract class JenaPreparedStatement extends JenaStatement implements PreparedStatement {

    private ParameterizedSparqlString sparqlStr = new ParameterizedSparqlString();
    private ParameterMetaData paramMetadata;

    /**
     * Creates a new prepared statement
     * 
     * @param sparql
     * @param connection
     *            Connection
     * @param type
     *            Result Set type for result sets produced by this statement
     * @param fetchDir
     *            Fetch Direction
     * @param fetchSize
     *            Fetch Size
     * @param holdability
     *            Result Set holdability
     * @param autoCommit
     *            Auto-commit
     * @param transactionLevel
     *            Transaction Isolation level
     * @throws SQLException
     *             Thrown if there is a problem preparing the statement
     */
    public JenaPreparedStatement(String sparql, JenaConnection connection, int type, int fetchDir, int fetchSize,
            int holdability, boolean autoCommit, int transactionLevel) throws SQLException {
        super(connection, type, fetchDir, fetchSize, holdability, autoCommit, transactionLevel);

        this.sparqlStr.setCommandText(sparql);
        this.paramMetadata = new JenaParameterMetadata(this.sparqlStr);
    }

    @Override
    public void addBatch() {
        this.addBatch(this.sparqlStr.toString());
    }

    @Override
    public void clearParameters() {
        this.sparqlStr.clearParams();
    }

    @Override
    public boolean execute() throws SQLException {
        return this.execute(this.sparqlStr.toString());
    }

    @Override
    public ResultSet executeQuery() throws SQLException {
        return this.executeQuery(this.sparqlStr.toString());
    }

    @Override
    public int executeUpdate() throws SQLException {
        return this.executeUpdate(this.sparqlStr.toString());
    }

    @Override
    public ResultSetMetaData getMetaData() {
        // Return null because we don't know in advance the column types
        return null;
    }

    @Override
    public ParameterMetaData getParameterMetaData() {
        return this.paramMetadata;
    }

    protected final void setParameter(int parameterIndex, Node n) throws SQLException {
        // Remember that JDBC used a 1 based index
        if (parameterIndex < 1 || parameterIndex > this.paramMetadata.getParameterCount())
            throw new SQLException("Parameter Index is out of bounds");

        this.sparqlStr.setParam(parameterIndex - 1, n);
    }

    @Override
    public void setArray(int parameterIndex, Array value) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setAsciiStream(int parameterIndex, InputStream value) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setAsciiStream(int parameterIndex, InputStream value, int arg2) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setAsciiStream(int parameterIndex, InputStream value, long arg2) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setBigDecimal(int parameterIndex, BigDecimal value) throws SQLException {
        this.setParameter(parameterIndex, NodeFactory.createLiteral(value.toPlainString(), XSDDatatype.XSDdecimal));
    }

    @Override
    public void setBinaryStream(int parameterIndex, InputStream value) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setBinaryStream(int parameterIndex, InputStream value, int arg2) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setBinaryStream(int parameterIndex, InputStream value, long arg2) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setBlob(int parameterIndex, Blob value) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setBlob(int parameterIndex, InputStream value) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setBlob(int parameterIndex, InputStream value, long arg2) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setBoolean(int parameterIndex, boolean value) throws SQLException {
        this.setParameter(parameterIndex, NodeFactory.createLiteral(Boolean.toString(value), XSDDatatype.XSDboolean));
    }

    @Override
    public void setByte(int parameterIndex, byte value) throws SQLException {
        this.setParameter(parameterIndex, NodeFactory.createLiteral(Byte.toString(value), XSDDatatype.XSDbyte));
    }

    @Override
    public void setBytes(int parameterIndex, byte[] value) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setCharacterStream(int parameterIndex, Reader value) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setCharacterStream(int parameterIndex, Reader value, int arg2) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setCharacterStream(int parameterIndex, Reader value, long arg2) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setClob(int parameterIndex, Clob value) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setClob(int parameterIndex, Reader value) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setClob(int parameterIndex, Reader value, long arg2) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setDate(int parameterIndex, Date value) throws SQLException {
        Calendar c = Calendar.getInstance();
        c.setTimeInMillis(value.getTime());
        this.setParameter(parameterIndex, NodeFactoryExtra.dateTimeToNode(c));
    }

    @Override
    public void setDate(int parameterIndex, Date value, Calendar arg2) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setDouble(int parameterIndex, double value) throws SQLException {
        this.setParameter(parameterIndex, NodeFactoryExtra.doubleToNode(value));
    }

    @Override
    public void setFloat(int parameterIndex, float value) throws SQLException {
        this.setParameter(parameterIndex, NodeFactoryExtra.floatToNode(value));
    }

    @Override
    public void setInt(int parameterIndex, int value) throws SQLException {
        this.setParameter(parameterIndex, NodeFactoryExtra.intToNode(value));
    }

    @Override
    public void setLong(int parameterIndex, long value) throws SQLException {
        this.setParameter(parameterIndex, NodeFactoryExtra.intToNode(value));
    }

    @Override
    public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setNCharacterStream(int parameterIndex, Reader value, long arg2) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setNClob(int parameterIndex, NClob value) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setNClob(int parameterIndex, Reader value) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setNClob(int parameterIndex, Reader value, long arg2) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setNString(int parameterIndex, String value) throws SQLException {
        this.setParameter(parameterIndex, NodeFactory.createLiteral(value));
    }

    @Override
    public void setNull(int parameterIndex, int value) throws SQLException {
        throw new SQLFeatureNotSupportedException("Parameters for SPARQL statements are not nullable");
    }

    @Override
    public void setNull(int parameterIndex, int value, String arg2) throws SQLException {
        throw new SQLFeatureNotSupportedException("Parameters for SPARQL statements are not nullable");
    }

    @Override
    public void setObject(int parameterIndex, Object value) throws SQLException {
        if (value == null) throw new SQLException("Setting a null value is not permitted");
        
        if (value instanceof Node) {
            this.setParameter(parameterIndex, (Node) value);
        } else if (value instanceof RDFNode) {
            this.setParameter(parameterIndex, ((RDFNode) value).asNode());
        } else if (value instanceof String) {
            this.setParameter(parameterIndex, NodeFactory.createLiteral((String) value));
        } else if (value instanceof Boolean) {
            this.setParameter(parameterIndex,
                    NodeFactory.createLiteral(Boolean.toString((Boolean) value), XSDDatatype.XSDboolean));
        } else if (value instanceof Long) {
            this.setParameter(parameterIndex, NodeFactoryExtra.intToNode((Long) value));
        } else if (value instanceof Integer) {
            this.setParameter(parameterIndex, NodeFactoryExtra.intToNode((Integer) value));
        } else if (value instanceof Short) {
            this.setParameter(parameterIndex, NodeFactory.createLiteral(Short.toString((Short) value), XSDDatatype.XSDshort));
        } else if (value instanceof Byte) {
            this.setParameter(parameterIndex, NodeFactory.createLiteral(Byte.toString((Byte) value), XSDDatatype.XSDbyte));
        } else if (value instanceof BigDecimal) {
            this.setParameter(parameterIndex,
                    NodeFactory.createLiteral(((BigDecimal) value).toPlainString(), XSDDatatype.XSDdecimal));
        } else if (value instanceof Float) {
            this.setParameter(parameterIndex, NodeFactoryExtra.floatToNode((Float) value));
        } else if (value instanceof Double) {
            this.setParameter(parameterIndex, NodeFactoryExtra.doubleToNode((Double) value));
        } else if (value instanceof Date) {
            Calendar c = Calendar.getInstance();
            c.setTimeInMillis(((Date) value).getTime());
            this.setParameter(parameterIndex, NodeFactoryExtra.dateTimeToNode(c));
        } else if (value instanceof Time) {
            Calendar c = Calendar.getInstance();
            c.setTimeInMillis(((Time) value).getTime());
            this.setParameter(parameterIndex, NodeFactoryExtra.timeToNode(c));
        } else if (value instanceof Calendar) {
            this.setParameter(parameterIndex, NodeFactoryExtra.dateTimeToNode((Calendar) value));
        } else if (value instanceof URL) {
            this.setParameter(parameterIndex, NodeFactory.createURI(value.toString()));
        } else if (value instanceof URI) {
            this.setParameter(parameterIndex, NodeFactory.createURI(value.toString()));
        } else if (value instanceof IRI) {
            this.setParameter(parameterIndex, NodeFactory.createURI(value.toString()));
        } else {
            throw new SQLException(
                    "setObject() received a value that could not be converted to a RDF node for use in a SPARQL query");
        }
    }

    @Override
    public void setObject(int parameterIndex, Object value, int targetSqlType) throws SQLException {
        if (value == null) throw new SQLException("Setting a null value is not permitted");
        
        try {
            switch (targetSqlType) {
            case Types.ARRAY:
            case Types.BINARY:
            case Types.BIT:
            case Types.BLOB:
            case Types.CLOB:
            case Types.DATALINK:
            case Types.DISTINCT:
            case Types.LONGNVARCHAR:
            case Types.LONGVARBINARY:
            case Types.LONGVARCHAR:
            case Types.NCHAR:
            case Types.NCLOB:
            case Types.NULL:
            case Types.NUMERIC:
            case Types.OTHER:
            case Types.REAL:
            case Types.REF:
            case Types.ROWID:
            case Types.SQLXML:
            case Types.STRUCT:
            case Types.VARBINARY:
                throw new SQLException("The provided SQL Target Type cannot be translated into an appropriate RDF term type");
            case Types.BIGINT:
                if (value instanceof Long) {
                    this.setParameter(parameterIndex, NodeFactoryExtra.intToNode((Long) value));
                } else if (value instanceof Integer) {
                    this.setParameter(parameterIndex, NodeFactoryExtra.intToNode((long)(Integer)value));
                } else if (value instanceof Short) {
                    this.setParameter(parameterIndex, NodeFactoryExtra.intToNode((long)(Short)value));
                } else if (value instanceof Byte) {
                    this.setParameter(parameterIndex, NodeFactoryExtra.intToNode((long)(Byte)value));
                } else if (value instanceof Node) {
                    long l = JdbcNodeUtils.toLong((Node) value);
                    this.setParameter(parameterIndex, NodeFactoryExtra.intToNode(l));
                } else if (value instanceof String) {
                    this.setParameter(parameterIndex, NodeFactoryExtra.intToNode(Long.parseLong((String)value)));
                } else {
                    throw new SQLException("The given value is not marshallable to the desired target type");
                }
                break;
            case Types.BOOLEAN:
                if (value instanceof Boolean) {
                    this.setParameter(parameterIndex,
                            NodeFactory.createLiteral(Boolean.toString((Boolean) value), XSDDatatype.XSDboolean));
                } else if (value instanceof Node) {
                    boolean b = JdbcNodeUtils.toBoolean((Node) value);
                    this.setParameter(parameterIndex,
                            NodeFactory.createLiteral(Boolean.toString(b), XSDDatatype.XSDboolean));
                } else if (value instanceof String) {
                    this.setParameter(parameterIndex, NodeFactory.createLiteral(Boolean.toString(Boolean.parseBoolean((String)value)), XSDDatatype.XSDboolean));
                } else {
                    throw new SQLException("The given value is not marshallable to the desired target type");
                }
                break;
            case Types.DATE:
                if (value instanceof Date) {
                    Calendar c = Calendar.getInstance();
                    c.setTimeInMillis(((Date) value).getTime());
                    this.setParameter(parameterIndex, NodeFactoryExtra.dateTimeToNode(c));
                } else if (value instanceof Node) {
                    Calendar c = Calendar.getInstance();
                    c.setTimeInMillis(JdbcNodeUtils.toDate((Node)value).getTime());
                    this.setParameter(parameterIndex, NodeFactoryExtra.dateTimeToNode(c));
                } else if (value instanceof Time) {
                    Calendar c = Calendar.getInstance();
                    c.setTimeInMillis(((Time) value).getTime());
                    this.setParameter(parameterIndex, NodeFactoryExtra.timeToNode(c));
                } else if (value instanceof Calendar) {
                    this.setParameter(parameterIndex, NodeFactoryExtra.dateTimeToNode((Calendar) value));
                } else {
                    throw new SQLException("The given value is not marshallable to the desired target type");
                }
                break;
            case Types.DECIMAL:
                if (value instanceof BigDecimal) {
                    this.setParameter(parameterIndex, NodeFactory.createLiteral(((BigDecimal)value).toPlainString(), XSDDatatype.XSDdecimal));
                } else if (value instanceof Node) {
                    BigDecimal d = JdbcNodeUtils.toDecimal((Node)value);
                    this.setParameter(parameterIndex, NodeFactory.createLiteral(d.toPlainString(), XSDDatatype.XSDdecimal));
                } else {
                    throw new SQLException("The given value is not marshallable to the desired target type");
                }
                break;
            case Types.DOUBLE:
                if (value instanceof Double) {
                    this.setParameter(parameterIndex, NodeFactoryExtra.doubleToNode((Double)value));
                } else if (value instanceof Float) {
                    this.setParameter(parameterIndex, NodeFactoryExtra.doubleToNode((Float)value));
                } else if (value instanceof Node) {
                    Double d = JdbcNodeUtils.toDouble((Node)value);
                    this.setParameter(parameterIndex, NodeFactoryExtra.doubleToNode(d));
                } else {
                    throw new SQLException("The given value is not marshallable to the desired target type");
                }
                break;
            case Types.FLOAT:
                if (value instanceof Float) {
                    this.setParameter(parameterIndex, NodeFactoryExtra.floatToNode((Float)value));
                } else if (value instanceof Node) {
                    Float f = JdbcNodeUtils.toFloat((Node)value);
                    this.setParameter(parameterIndex, NodeFactoryExtra.floatToNode(f));
                } else {
                    throw new SQLException("The given value is not marshallable to the desired target type");
                }
                break;
            case Types.INTEGER:
                if (value instanceof Integer) {
                    this.setParameter(parameterIndex, NodeFactoryExtra.intToNode((Integer)value));
                } else if (value instanceof Short) {
                    this.setParameter(parameterIndex, NodeFactoryExtra.intToNode((Short)value));
                } else if (value instanceof Byte) {
                    this.setParameter(parameterIndex, NodeFactoryExtra.intToNode((Byte)value));
                } else if (value instanceof Node) {
                    Integer i = JdbcNodeUtils.toInt((Node)value);
                    this.setParameter(parameterIndex, NodeFactoryExtra.intToNode(i));
                } else {
                    throw new SQLException("The given value is not marshallable to the desired target type");
                }
                break;
            case Types.JAVA_OBJECT:
                if (value instanceof Node) {
                    this.setParameter(parameterIndex, (Node) value);
                } else if (value instanceof RDFNode) {
                    this.setParameter(parameterIndex, ((RDFNode) value).asNode());
                } else if (value instanceof String) {
                    this.setParameter(parameterIndex, NodeFactory.createLiteral((String) value));
                } else if (value instanceof URL) {
                    this.setParameter(parameterIndex, NodeFactory.createURI(((URL) value).toString()));
                } else if (value instanceof URI) {
                    this.setParameter(parameterIndex, NodeFactory.createURI(((URI)value).toString()));
                } else if (value instanceof IRI) {
                    this.setParameter(parameterIndex, NodeFactory.createURI(((IRI)value).toString()));
                } else if (value instanceof BigDecimal) {
                    this.setParameter(parameterIndex, NodeFactory.createLiteral(((BigDecimal)value).toPlainString(), XSDDatatype.XSDdecimal));
                } else if (value instanceof Boolean) {
                    this.setParameter(parameterIndex, NodeFactory.createLiteral(Boolean.toString(((Boolean)value)), XSDDatatype.XSDboolean));
                } else if (value instanceof Byte) {
                    this.setParameter(parameterIndex, NodeFactory.createLiteral(Byte.toString((Byte)value), XSDDatatype.XSDbyte));
                } else if (value instanceof Double) {
                    this.setParameter(parameterIndex, NodeFactoryExtra.doubleToNode((Double)value));
                } else if (value instanceof Float) {
                    this.setParameter(parameterIndex, NodeFactoryExtra.floatToNode((Float)value));
                } else if (value instanceof Short) {
                    this.setParameter(parameterIndex, NodeFactory.createLiteral(Short.toString((Short)value), XSDDatatype.XSDshort));
                } else if (value instanceof Integer) {
                    this.setParameter(parameterIndex, NodeFactoryExtra.intToNode((Integer)value));
                } else if (value instanceof Long) {
                    this.setParameter(parameterIndex, NodeFactoryExtra.intToNode((Long)value));
                } else if (value instanceof Date) {
                    Calendar c = Calendar.getInstance();
                    c.setTimeInMillis(((Date)value).getTime());
                    this.setParameter(parameterIndex, NodeFactoryExtra.dateTimeToNode(c));
                } else if (value instanceof Time) {
                    Calendar c = Calendar.getInstance();
                    c.setTimeInMillis(((Time)value).getTime());
                    this.setParameter(parameterIndex, NodeFactoryExtra.timeToNode(c));
                } else if (value instanceof Calendar) {
                    this.setParameter(parameterIndex, NodeFactoryExtra.dateTimeToNode((Calendar)value));
                } else { 
                    this.setParameter(parameterIndex, NodeFactory.createLiteral(value.toString()));
                }
                break;
            case Types.CHAR:
            case Types.NVARCHAR:
            case Types.VARCHAR:
                this.setParameter(parameterIndex, NodeFactory.createLiteral(value.toString()));
                break;
            case Types.SMALLINT:
                if (value instanceof Short) {
                    Short s = (Short)value;
                    this.setParameter(parameterIndex, NodeFactory.createLiteral(Short.toString(s), XSDDatatype.XSDshort));
                } else if (value instanceof Byte) {
                    this.setParameter(parameterIndex, NodeFactory.createLiteral(Short.toString((Byte)value), XSDDatatype.XSDshort));
                } else if (value instanceof Node) {
                    Short s = JdbcNodeUtils.toShort((Node)value);
                    this.setParameter(parameterIndex, NodeFactory.createLiteral(Short.toString(s), XSDDatatype.XSDshort));
                } else if (value instanceof String) {
                    this.setParameter(parameterIndex, NodeFactory.createLiteral(Short.toString(Short.parseShort((String)value)), XSDDatatype.XSDshort));
                } else {
                    throw new SQLException("The given value is not marshallable to the desired type");
                }
                break;
            case Types.TIME:
                if (value instanceof Time) {
                    Calendar c = Calendar.getInstance();
                    c.setTimeInMillis(((Time)value).getTime());
                    this.setParameter(parameterIndex, NodeFactoryExtra.timeToNode(c));
                } else if (value instanceof Node) {
                    Calendar c = Calendar.getInstance();
                    c.setTimeInMillis(JdbcNodeUtils.toDate((Node)value).getTime());
                    this.setParameter(parameterIndex, NodeFactoryExtra.timeToNode(c));
                } else if (value instanceof Date) {
                    Calendar c = Calendar.getInstance();
                    c.setTimeInMillis(((Date)value).getTime());
                    this.setParameter(parameterIndex, NodeFactoryExtra.timeToNode(c));
                } else if (value instanceof Calendar) {
                    this.setParameter(parameterIndex, NodeFactoryExtra.timeToNode((Calendar)value));
                } else {
                    throw new SQLException("The given value is not marshallable to the desired type");
                }
                break;
            case Types.TINYINT:
                if (value instanceof Byte) {
                    Byte b = (Byte)value;
                    this.setParameter(parameterIndex, NodeFactory.createLiteral(Byte.toString(b), XSDDatatype.XSDbyte));
                } else if (value instanceof Node) {
                    Byte b = JdbcNodeUtils.toByte((Node)value);
                    this.setParameter(parameterIndex, NodeFactory.createLiteral(Byte.toString(b), XSDDatatype.XSDbyte));
                } else {
                    throw new SQLException("The given value is not marshallable to the desired type");
                }
                break;
            default:
                throw new SQLException("Cannot translate an unknown SQL Target Type into an appropriate RDF term type");
            }
        } catch (SQLException e) {
            // Throw as-is
            throw e;
        } catch (Throwable e) {
            // Wrap as SQL Exception
            throw new SQLException("Unexpected error trying to marshal a value to the desired target type", e);
        }
    }

    @Override
    public void setObject(int parameterIndex, Object value, int arg2, int arg3) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setRef(int parameterIndex, Ref value) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setRowId(int parameterIndex, RowId value) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setSQLXML(int parameterIndex, SQLXML value) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setShort(int parameterIndex, short value) throws SQLException {
        this.setParameter(parameterIndex, NodeFactory.createLiteral(Short.toString(value), XSDDatatype.XSDshort));
    }

    @Override
    public void setString(int parameterIndex, String value) throws SQLException {
        this.setParameter(parameterIndex, NodeFactory.createLiteral(value));
    }

    @Override
    public void setTime(int parameterIndex, Time value) throws SQLException {
        Calendar c = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
        c.setTimeInMillis(value.getTime());
        this.setParameter(parameterIndex, NodeFactoryExtra.timeToNode(c));
    }

    @Override
    public void setTime(int parameterIndex, Time value, Calendar arg2) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setTimestamp(int parameterIndex, Timestamp value) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setTimestamp(int parameterIndex, Timestamp value, Calendar arg2) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setURL(int parameterIndex, URL value) throws SQLException {
        this.setParameter(parameterIndex, NodeFactory.createURI(value.toString()));
    }

    @Deprecated
    @Override
    public void setUnicodeStream(int parameterIndex, InputStream value, int arg2) throws SQLException {
        throw new SQLFeatureNotSupportedException();
    }

    /**
     * Gets a copy of the underlying {@link ParameterizedSparqlString} used to
     * implement this prepared statement
     * 
     * @return Copy of the underlying string
     */
    public ParameterizedSparqlString getParameterizedString() {
        return this.sparqlStr.copy();
    }
}
