![JAR search and dependency download from the Maven repository](/logo.png)
xdev.vt.XdevClob Maven / Gradle / Ivy
/*
* XDEV Application Framework - XDEV Application Framework
* Copyright © 2003 XDEV Software (https://xdev.software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*/
package xdev.vt;
import java.io.CharArrayReader;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Reader;
import java.io.Serializable;
import java.io.Writer;
import java.sql.Clob;
import java.sql.SQLException;
import java.util.Arrays;
import javax.sql.rowset.serial.SerialException;
import xdev.db.DBException;
import xdev.io.CharHolder;
import xdev.io.IOUtils;
import xdev.util.logging.LoggerFactory;
import xdev.util.logging.XdevLogger;
/**
* The mapping in XDEV for the SQL CLOB type. A XdevClob stores a Character
* Large Object as a column value in a row of a {@link VirtualTable}.
*
* The XdevClob holds either an reference to a {@link Clob} object or plain
* chars.
*
* @author XDEV Software Corp.
* @see Clob
*
*/
public class XdevClob implements CharHolder, Serializable, Comparable
{
/**
* Logger instance for this class.
*/
private static final XdevLogger log = LoggerFactory.getLogger(XdevClob.class);
private static final long serialVersionUID = 7975847043132313545L;
private char[] chars;
private Clob clob;
/**
* Initializes a new instance of {@link XdevClob}.
*/
public XdevClob()
{
this(new char[0]);
}
/**
* Initializes a new instance of {@link XdevBlob}.
*
* @param chars
* a character array initially to be set
*/
public XdevClob(char[] chars)
{
super();
this.clob = null;
this.chars = chars;
}
/**
* Initializes a new instance of {@link XdevClob}.
*
* @param clob
* a {@link Clob} initially to be set.
* @throws DBException
* if the specified clob could not be set.
*/
public XdevClob(Clob clob) throws DBException
{
super();
this.chars = null;
this.clob = clob;
}
void readFully() throws DBException
{
if(chars == null && clob != null)
{
int length = length();
if(length >= 0)
{
chars = new char[length];
Reader reader = getCharacterStream();
try
{
int read;
int offset = 0;
while(offset < length
&& (read = reader.read(chars,offset,length - offset)) != -1)
{
offset += read;
}
}
catch(IOException ioe)
{
throw new DBException(null,ioe);
}
finally
{
IOUtils.closeSilent(reader);
}
}
else
{
chars = new char[0];
}
clob = null;
}
}
/**
* Returns the number of characters of this {@link XdevBlob}.
*
* @return the number of characters or -1 if the length couldn't be
* retrieved
*/
public int length()
{
if(clob != null)
{
try
{
return (int)clob.length();
}
catch(SQLException e)
{
return -1;
}
}
else
{
return chars.length;
}
}
/**
* Truncates the CLOB value that this {@link XdevClob} object represents to
* be len characters in length.
*
* @param length
* the length, in characters, to which the CLOB value that this
* Blob object represents should be truncated
* @throws DBException
* if truncate was not successful.
*/
public void truncate(long length) throws DBException
{
if(clob != null)
{
try
{
clob.truncate(length);
}
catch(SQLException e)
{
throw new DBException(null,e);
}
}
else
{
if(length < chars.length && length >= 0)
{
char[] chars = new char[(int)length];
System.arraycopy(this.chars,0,chars,0,chars.length);
this.chars = chars;
}
}
}
/**
* Retrieves the CLOB value designated by this Clob object as a
* {@link Reader} object (or as a stream of characters).
*
* @return a {@link Reader} object containing the CLOB data
* @throws DBException
* if there is an error accessing the CLOB value
*/
public Reader getCharacterStream() throws DBException
{
if(clob != null)
{
try
{
return clob.getCharacterStream();
}
catch(SQLException e)
{
throw new DBException(null,e);
}
}
else
{
return new CharArrayReader(chars);
}
}
/**
* Returns a Reader object that contains a partial Clob value, starting with
* the character specified by pos, which is length characters in length.
*
* @param position
* the offset to the first character of the partial value to be
* retrieved
* @param length
* the length in characters of the partial value to be retrieved
* @return a {@link Reader} through which the partial Clob value can be read
* @throws DBException
* if there is an error accessing the CLOB value
*/
public Reader getCharacterStream(int position, int length) throws DBException
{
if(clob != null)
{
try
{
return clob.getCharacterStream(position + 1,length);
}
catch(SQLException e)
{
throw new DBException(null,e);
}
}
else
{
return new CharArrayReader(chars,position,length);
}
}
/**
* Retrieves a stream to be used to write a stream of Unicode characters to
* the CLOB value that this Clob object represents, at position pos.
* Characters written to the stream will overwrite the existing characters
* in the Clob object starting at the position pos. If the end of the Clob
* value is reached while writing characters to the stream, then the length
* of the Clob value will be increased to accomodate the extra characters.
*
* @param position
* the position at which to start writing to the CLOB value
* @return a {@link Writer} to which Unicode encoded characters can be
* written
* @throws DBException
* if there is an error accessing the CLOB value
*/
public Writer setCharacterStream(final int position) throws DBException
{
if(clob != null)
{
try
{
return clob.setCharacterStream(position + 1);
}
catch(SQLException e)
{
throw new DBException(null,e);
}
}
else
{
return new CharArrayWriter()
{
@Override
public void close()
{
byte[] result = new byte[position + count];
System.arraycopy(chars,0,result,0,position);
System.arraycopy(buf,0,result,position,count);
XdevClob.this.chars = chars;
}
};
}
}
/**
* Returns a part of this {@link XdevClob} as a {@link String}.
*
* @param pos
* the position of the first character to be returned.
*
* @param length
* the number of characters to be returned.
*
* @return a {@link String} representation for a part of this
* {@link XdevClob}.
*
* @throws DBException
*/
public String getSubString(int pos, int length) throws DBException
{
if(clob != null)
{
try
{
return clob.getSubString(pos + 1,length);
}
catch(SQLException e)
{
throw new DBException(null,e);
}
}
else
{
return new String(chars,pos,length);
}
}
/**
* Writes the given Java String to the CLOB value that this Clob object
* designates at the position pos. The string will overwrite the existing
* characters in the Clob object starting at the position pos. If the end of
* the Clob value is reached while writing the given string, then the length
* of the Clob value will be increased to accomodate the extra characters.
*
* @param position
* the position at which to start writing to the CLOB value that
* this Clob object represents
* @param str
* the string to be written to the CLOB value that this Clob
* designates
* @return the number of characters written
* @throws DBException
* if there is an error accessing the CLOB value
*/
public int setString(int position, String str) throws DBException
{
if(clob != null)
{
try
{
return clob.setString(position + 1,str);
}
catch(SQLException e)
{
throw new DBException(null,e);
}
}
else
{
str.getChars(0,str.length(),chars,position);
return str.length();
}
}
/**
* Writes len characters of str, starting at character offset, to the CLOB
* value that this Clob represents. The string will overwrite the existing
* characters in the Clob object starting at the position pos. If the end of
* the Clob value is reached while writing the given string, then the length
* of the Clob value will be increased to accomodate the extra characters.
*
* @param position
* the position at which to start writing to this CLOB object
* @param str
* the string to be written to the CLOB value that this Clob
* object represents
* @param offset
* the offset into str to start reading the characters to be
* written
* @param length
* the number of characters to be written
* @return the number of characters written
* @throws DBException
* if there is an error accessing the CLOB value
*/
public int setString(int position, String str, int offset, int length) throws DBException
{
if(clob != null)
{
try
{
return clob.setString(position + 1,str,offset,length);
}
catch(SQLException e)
{
throw new DBException(null,e);
}
}
else
{
str.getChars(offset,length,chars,position);
return length;
}
}
private String toString = null;
/**
* {@inheritDoc}
*/
@Override
public String toString()
{
if(toString == null)
{
try
{
toString = getSubString(0,length());
}
catch(DBException e)
{
log.error(e);
toString = "CLOB, length = " + length();
}
}
return toString;
}
/**
* Returns a copied char array
of this {@link XdevClob}.
*
* @return a char array
*/
public char[] toCharArray()
{
if(clob != null)
{
try
{
return clob.getSubString(1,(int)clob.length()).toCharArray();
}
catch(Exception e)
{
return new char[0];
}
}
else
{
char[] array = new char[chars.length];
System.arraycopy(chars,0,array,0,chars.length);
return array;
}
}
/**
* Compares this {@link XdevClob} with another {@link CharHolder}.
*
* The length of the {@link CharHolder} objects is used for this comparison.
*
*
* @param other
* the other {@link CharHolder}.
*
* - 0 - if the length of both objects is equal
* - -1 - if the length of this {@link CharHolder} is greater
* - 1 - if the length of this {@link CharHolder} is smaller
*
*/
public int compareTo(CharHolder other)
{
long thisLength = length();
long otherLength = other.length();
return thisLength < otherLength ? -1 : thisLength == otherLength ? 0 : 1;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj)
{
if(this == obj)
{
return true;
}
if(obj instanceof CharHolder)
{
CharHolder other = (CharHolder)obj;
if(length() == other.length())
{
return Arrays.equals(toCharArray(),other.toCharArray());
}
}
return false;
}
/**
* Returns a copied {@link Clob} for this {@link XdevClob}.
*
* @return a {@link Clob}
* @throws DBException
* if {@link Clob} could not be created.
*/
public Clob toJDBCClob() throws DBException
{
if(clob != null)
{
return clob;
}
try
{
return new ExtendedSerialClob(chars);
}
catch(SerialException e)
{
throw new DBException(null,e);
}
catch(SQLException e)
{
throw new DBException(null,e);
}
}
/**
* Generates a hashcode for this {@link XdevBlob}.
*
* @return a hashcode
*/
@Override
public int hashCode()
{
return Arrays.hashCode(toCharArray());
}
private void writeObject(ObjectOutputStream out) throws IOException
{
if(clob != null)
{
try
{
out.writeInt((int)clob.length());
char[] buffer = new char[1024];
Reader in = clob.getCharacterStream();
try
{
int read;
while((read = in.read(buffer)) != -1)
{
for(int i = 0; i < read; i++)
{
out.writeChar(buffer[i]);
}
}
}
finally
{
in.close();
}
}
catch(SQLException e)
{
throw new IOException(e);
}
}
else
{
int len = chars.length;
out.writeInt(len);
for(int i = 0; i < len; i++)
{
out.writeChar(chars[i]);
}
}
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
{
int len = in.readInt();
this.chars = new char[len];
for(int i = 0; i < len; i++)
{
this.chars[i] = in.readChar();
}
}
private static class ExtendedSerialClob implements Clob, Serializable, Cloneable
{
private static final long serialVersionUID = 6734977304211158086L;
/**
* @serial
*/
private char buf[];
private Clob clob;
/**
* @serial
*/
private long len;
/**
* @serial
*/
private long origLen;
public ExtendedSerialClob(char ch[]) throws SerialException, SQLException
{
len = ch.length;
buf = new char[(int)len];
for(int i = 0; i < len; i++)
{
buf[i] = ch[i];
}
origLen = len;
}
public long length() throws SerialException
{
return len;
}
public Reader getCharacterStream() throws SerialException
{
return (java.io.Reader)new CharArrayReader(buf);
}
public InputStream getAsciiStream() throws SerialException, SQLException
{
if(this.clob != null)
{
return this.clob.getAsciiStream();
}
else
{
return new java.io.InputStream()
{
int pos = 0;
@Override
public int read() throws IOException
{
return pos < buf.length ? buf[pos++] : -1;
}
};
}
}
public String getSubString(long pos, int length) throws SerialException
{
if(pos < 1 || pos > this.length())
{
throw new SerialException("Invalid position in BLOB object set");
}
if((pos - 1) + length > this.length())
{
throw new SerialException("Invalid position and substring length");
}
try
{
return new String(buf,(int)pos - 1,length);
}
catch(StringIndexOutOfBoundsException e)
{
throw new SerialException("StringIndexOutOfBoundsException: " + e.getMessage());
}
}
public long position(String searchStr, long start) throws SerialException, SQLException
{
if(start < 1 || start > len)
{
return -1;
}
char pattern[] = searchStr.toCharArray();
int pos = (int)start - 1;
int i = 0;
long patlen = pattern.length;
while(pos < len)
{
if(pattern[i] == buf[pos])
{
if(i + 1 == patlen)
{
return (pos + 1) - (patlen - 1);
}
i++;
pos++; // increment pos, and i
}
else if(pattern[i] != buf[pos])
{
pos++; // increment pos only
}
}
return -1; // not found
}
public long position(Clob searchStr, long start) throws SerialException, SQLException
{
return position(searchStr.getSubString(1,(int)searchStr.length()),start);
}
public int setString(long pos, String str) throws SerialException
{
return(setString(pos,str,0,str.length()));
}
public int setString(long pos, String str, int offset, int length) throws SerialException
{
String temp = str.substring(offset);
char cPattern[] = temp.toCharArray();
if(offset < 0 || offset > str.length())
{
throw new SerialException("Invalid offset in byte array set");
}
if(pos < 1 || pos > this.length())
{
throw new SerialException("Invalid position in BLOB object set");
}
if((long)(length) > origLen)
{
throw new SerialException("Buffer is not sufficient to hold the value");
}
if((length + offset) > str.length())
{
// need check to ensure length + offset !> bytes.length
throw new SerialException("Invalid OffSet. Cannot have combined offset "
+ " and length that is greater that the Blob buffer");
}
int i = 0;
pos--; // values in the array are at position one less
while(i < length || (offset + i + 1) < (str.length() - offset))
{
this.buf[(int)pos + i] = cPattern[offset + i];
i++;
}
return i;
}
public java.io.OutputStream setAsciiStream(long pos) throws SerialException, SQLException
{
if(this.clob.setAsciiStream(pos) != null)
{
return this.clob.setAsciiStream(pos);
}
else
{
throw new SerialException(
"Unsupported operation. SerialClob cannot "
+ "return a writable ascii stream\n unless instantiated with a Clob object "
+ "that has a setAsciiStream() implementation");
}
}
public java.io.Writer setCharacterStream(long pos) throws SerialException, SQLException
{
if(this.clob.setCharacterStream(pos) != null)
{
return this.clob.setCharacterStream(pos);
}
else
{
throw new SerialException(
"Unsupported operation. SerialClob cannot "
+ "return a writable character stream\n unless instantiated with a Clob object "
+ "that has a setCharacterStream implementation");
}
}
public void truncate(long length) throws SerialException
{
if(length > len)
{
throw new SerialException("Length more than what can be truncated");
}
else
{
len = length;
// re-size the buffer
if(len == 0)
{
buf = new char[]{};
}
else
{
buf = (this.getSubString(1,(int)len)).toCharArray();
}
}
}
public Reader getCharacterStream(long pos, long length) throws SQLException
{
return new CharArrayReader(buf);
}
public void free() throws SQLException
{
throw new java.lang.UnsupportedOperationException("Not supported");
}
}
}