/*
* JBoss, Home of Professional Open Source.
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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 library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
package org.teiid.client.batch;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.ObjectStreamConstants;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import org.teiid.client.BatchSerializer;
import org.teiid.client.ResizingArrayList;
import org.teiid.core.types.BlobType;
import org.teiid.core.types.ClobType;
import org.teiid.core.types.DataTypeManagerService;
import org.teiid.core.types.DataTypeManagerService.DefaultDataTypes;
import org.teiid.core.types.XMLType;
import org.teiid.designer.runtime.version.spi.ITeiidServerVersion;
import org.teiid.runtime.client.Messages;
/**
* @since 4.2
*/
public class Batch0Serializer extends BatchSerializer {
protected final Map<String, ColumnSerializer[]> serializers = new HashMap<String, ColumnSerializer[]>(128);
protected final Map<String, ColumnSerializer> version1serializers = new HashMap<String, ColumnSerializer>(128);
protected Batch0Serializer(ITeiidServerVersion teiidVersion, byte version) {
super(teiidVersion, version);
initDateNormalizer();
serializers.put(DataTypeManagerService.DefaultDataTypes.BIG_DECIMAL.getId(), new ColumnSerializer[] { new BigDecimalColumnSerializer() });
serializers.put(DataTypeManagerService.DefaultDataTypes.BIG_INTEGER.getId(), new ColumnSerializer[] { new BigIntegerColumnSerializer() });
serializers.put(DataTypeManagerService.DefaultDataTypes.BOOLEAN.getId(), new ColumnSerializer[] { new BooleanColumnSerializer() });
serializers.put(DataTypeManagerService.DefaultDataTypes.BYTE.getId(), new ColumnSerializer[] { new ByteColumnSerializer() });
serializers.put(DataTypeManagerService.DefaultDataTypes.CHAR.getId(), new ColumnSerializer[] { new CharColumnSerializer() });
serializers.put(DataTypeManagerService.DefaultDataTypes.DATE.getId(), new ColumnSerializer[] { new DateColumnSerializer() });
serializers.put(DataTypeManagerService.DefaultDataTypes.DOUBLE.getId(), new ColumnSerializer[] { new DoubleColumnSerializer() });
serializers.put(DataTypeManagerService.DefaultDataTypes.FLOAT.getId(), new ColumnSerializer[] { new FloatColumnSerializer() });
serializers.put(DataTypeManagerService.DefaultDataTypes.INTEGER.getId(), new ColumnSerializer[] { new IntColumnSerializer() });
serializers.put(DataTypeManagerService.DefaultDataTypes.LONG.getId(), new ColumnSerializer[] { new LongColumnSerializer() });
serializers.put(DataTypeManagerService.DefaultDataTypes.SHORT.getId(), new ColumnSerializer[] { new ShortColumnSerializer() });
serializers.put(DataTypeManagerService.DefaultDataTypes.TIME.getId(), new ColumnSerializer[] { new TimeColumnSerializer() });
serializers.put(DataTypeManagerService.DefaultDataTypes.TIMESTAMP.getId(), new ColumnSerializer[] { new TimestampColumnSerializer() });
version1serializers.put(DataTypeManagerService.DefaultDataTypes.DATE.getId(), new DateColumnSerializer1());
version1serializers.put(DataTypeManagerService.DefaultDataTypes.TIME.getId(), new TimeColumnSerializer1());
version1serializers.put(DataTypeManagerService.DefaultDataTypes.STRING.getId(), new StringColumnSerializer1());
version1serializers.put(DataTypeManagerService.DefaultDataTypes.CLOB.getId(), new ClobColumnSerializer1());
version1serializers.put(DataTypeManagerService.DefaultDataTypes.BLOB.getId(), new BlobColumnSerializer1());
version1serializers.put(DataTypeManagerService.DefaultDataTypes.XML.getId(), new XmlColumnSerializer1());
version1serializers.put(DataTypeManagerService.DefaultDataTypes.NULL.getId(), new NullColumnSerializer1());
version1serializers.put(DataTypeManagerService.DefaultDataTypes.OBJECT.getId(), new ObjectColumnSerializer1());
}
/**
* @param teiidVersion
*/
public Batch0Serializer(ITeiidServerVersion teiidVersion) {
this(teiidVersion, (byte) 0);
}
protected class ObjectColumnSerializer1 extends ColumnSerializer {
@Override
public void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version)
throws IOException {
DefaultDataTypes dataType = getDataTypeManager().getDataType(obj.getClass());
int code = dataType.ordinal();
out.writeByte((byte)code);
if (code == DataTypeManagerService.DefaultDataTypes.BOOLEAN.ordinal()) {
if (Boolean.TRUE.equals(obj)) {
out.write((byte)1);
} else {
out.write((byte)0);
}
} else if (code != DataTypeManagerService.DefaultDataTypes.OBJECT.ordinal()) {
dataType = DataTypeManagerService.DefaultDataTypes.valueOf(getTeiidVersion(), code);
ColumnSerializer s = getSerializer(dataType.getId(), (byte)1);
s.writeObject(out, obj, cache, version);
} else {
super.writeObject(out, obj, cache, version);
}
}
@Override
public Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException, ClassNotFoundException {
int code = in.readByte();
return readObject(in, code, cache, version);
}
public Object readObject(ObjectInput in, int code, List<Object> cache, byte version) throws IOException, ClassNotFoundException {
if (code == DataTypeManagerService.DefaultDataTypes.BOOLEAN.ordinal()) {
if (in.readByte() == (byte)0) {
return Boolean.FALSE;
}
return Boolean.TRUE;
}
if (code != DataTypeManagerService.DefaultDataTypes.OBJECT.ordinal()) {
DefaultDataTypes dataType = DataTypeManagerService.DefaultDataTypes.valueOf(getTeiidVersion(), code);
ColumnSerializer s = getSerializer(dataType.getId(), (byte) 1);
return s.readObject(in, cache, version);
}
return super.readObject(in, cache, version);
}
}
protected final static int MAX_UTF = 0xFFFF/3; //this is greater than the expected max length of Teiid Strings
protected class StringColumnSerializer1 extends ColumnSerializer {
@Override
public void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException {
String str = (String)obj;
if (str.length() <= MAX_UTF) {
//skip object serialization if we have a short string
out.writeByte(ObjectStreamConstants.TC_STRING);
out.writeUTF(str);
} else {
out.writeByte(ObjectStreamConstants.TC_LONGSTRING);
out.writeObject(obj);
}
}
@Override
public Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException,
ClassNotFoundException {
if (in.readByte() == ObjectStreamConstants.TC_STRING) {
return in.readUTF();
}
return super.readObject(in, cache, version);
}
}
protected class NullColumnSerializer1 extends ColumnSerializer {
@Override
public void writeColumn(ObjectOutput out, int col,
List<? extends List<?>> batch, Map<Object, Integer> cache, byte version) {
// Nothing Required
}
@Override
public void readColumn(ObjectInput in, int col,
List<List<Object>> batch, byte[] isNull, List<Object> cache, byte version) {
// Nothing Required
}
}
protected class ClobColumnSerializer1 extends ColumnSerializer {
@Override
public void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException {
((Externalizable)obj).writeExternal(out);
}
@Override
public Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException, ClassNotFoundException {
ClobType ct = new ClobType();
ct.readExternal(in);
return ct;
}
}
protected class BlobColumnSerializer1 extends ColumnSerializer {
@Override
public void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException {
((Externalizable)obj).writeExternal(out);
}
@Override
public Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException, ClassNotFoundException {
BlobType bt = new BlobType();
bt.readExternal(in);
return bt;
}
}
protected class XmlColumnSerializer1 extends ColumnSerializer {
@Override
public void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException {
((Externalizable)obj).writeExternal(out);
}
@Override
public Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException, ClassNotFoundException {
XMLType xt = new XMLType();
xt.readExternal(in);
return xt;
}
}
protected class IntColumnSerializer extends ColumnSerializer {
@Override
public void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException {
out.writeInt(((Integer)obj).intValue());
}
@Override
public Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException {
return Integer.valueOf(in.readInt());
}
}
protected class LongColumnSerializer extends ColumnSerializer {
@Override
public void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException {
out.writeLong(((Long)obj).longValue());
}
@Override
public Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException {
return Long.valueOf(in.readLong());
}
}
protected class FloatColumnSerializer extends ColumnSerializer {
@Override
public void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException {
out.writeFloat(((Float)obj).floatValue());
}
@Override
public Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException {
return new Float(in.readFloat());
}
}
protected class DoubleColumnSerializer extends ColumnSerializer {
@Override
public void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException {
out.writeDouble(((Double)obj).doubleValue());
}
@Override
public Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException {
return new Double(in.readDouble());
}
}
protected class ShortColumnSerializer extends ColumnSerializer {
@Override
public void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException {
out.writeShort(((Short)obj).shortValue());
}
@Override
public Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException {
return Short.valueOf(in.readShort());
}
}
protected class BooleanColumnSerializer extends ColumnSerializer {
/* This implementation compacts the isNull and boolean data for non-null values into a byte[]
* by using a 8 bit mask that is bit-shifted to mask each value.
*/
@Override
public void writeColumn(ObjectOutput out, int col, List<? extends List<?>> batch, Map<Object, Integer> cache, byte version) throws IOException {
int currentByte = 0;
int mask = 0x80;
Object obj;
for (int row = 0; row < batch.size(); row++) {
// Write the isNull value
obj = batch.get(row).get(col);
if (obj == null ) {
currentByte |= mask;
}
mask >>= 1; // Shift the mask to the next bit
if (mask == 0) {
// If the current byte has been used up, write it and reset.
out.write(currentByte);
currentByte = 0;
mask = 0x80;
}
if (obj != null) {
// Write the boolean value if it's not null
if (((Boolean)obj).booleanValue()) {
currentByte |= mask;
}
mask >>= 1;
if (mask == 0) {
out.write(currentByte);
currentByte = 0;
mask = 0x80;
}
}
}
// Invariant mask != 0
// If we haven't reached the eight-row mark then the loop would not have written this byte
// Write the final byte containing data for th extra rows, if it exists.
if (mask != 0x80) {
out.write(currentByte);
}
}
@Override
public void readColumn(ObjectInput in, int col,
List<List<Object>> batch, byte[] isNull, List<Object> cache, byte version) throws IOException {
int currentByte = 0, mask = 0; // Initialize the mask so that it is reset in the loop
boolean isNullVal;
for (int row = 0; row < batch.size(); row++) {
if (mask == 0) {
// If we used up the byte, read the next one, and reset the mask
currentByte = in.read();
mask = 0x80;
}
isNullVal = (currentByte & mask) != 0;
mask >>= 1; // Shift the mask to the next bit
if (!isNullVal) {
if (mask == 0) {
currentByte = in.read();
mask = 0x80;
}
batch.get(row).set(col, ((currentByte & mask) == 0) ? Boolean.FALSE : Boolean.TRUE);
mask >>= 1;
}
}
}
}
protected class ByteColumnSerializer extends ColumnSerializer {
@Override
public void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException {
out.writeByte(((Byte)obj).byteValue());
}
@Override
public Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException {
return Byte.valueOf(in.readByte());
}
}
protected class CharColumnSerializer extends ColumnSerializer {
@Override
public void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException {
out.writeChar(((Character)obj).charValue());
}
@Override
public Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException {
return Character.valueOf(in.readChar());
}
}
protected class BigIntegerColumnSerializer extends ColumnSerializer {
@Override
public void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException {
BigInteger val = (BigInteger)obj;
byte[] bytes = val.toByteArray();
out.writeInt(bytes.length);
out.write(bytes);
}
@Override
public Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException {
int length = in.readInt();
byte[] bytes = new byte[length];
in.readFully(bytes);
return new BigInteger(bytes);
}
}
protected class BigDecimalColumnSerializer extends ColumnSerializer {
@Override
public void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException {
BigDecimal val = (BigDecimal)obj;
out.writeInt(val.scale());
BigInteger unscaled = val.unscaledValue();
byte[] bytes = unscaled.toByteArray();
out.writeInt(bytes.length);
out.write(bytes);
}
@Override
public Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException {
int scale = in.readInt();
int length = in.readInt();
byte[] bytes = new byte[length];
in.readFully(bytes);
return new BigDecimal(new BigInteger(bytes), scale);
}
}
protected class DateColumnSerializer extends ColumnSerializer {
@Override
public void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException {
out.writeLong(((java.sql.Date)obj).getTime());
}
@Override
public Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException {
return new java.sql.Date(in.readLong());
}
}
protected class TimeColumnSerializer extends ColumnSerializer {
@Override
public void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException {
out.writeLong(((Time)obj).getTime());
}
@Override
public Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException {
return new Time(in.readLong());
}
}
int dateNormalizer = -1;
protected void initDateNormalizer() {
if (dateNormalizer == -1) {
Calendar c = Calendar.getInstance();
c.setTimeZone(TimeZone.getTimeZone("GMT")); //$NON-NLS-1$
c.set(1900, 0, 1, 0, 0, 0);
c.set(Calendar.MILLISECOND, 0);
dateNormalizer = -(int)(c.getTime().getTime()/60000); //support a 32 bit range starting at this value
}
}
protected class DateColumnSerializer1 extends ColumnSerializer {
@Override
public void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException {
out.writeInt((int)(((java.sql.Date)obj).getTime()/60000) + dateNormalizer);
}
@Override
public Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException {
return new java.sql.Date(((in.readInt()&0xffffffffL) - dateNormalizer)*60000);
}
}
protected class TimeColumnSerializer1 extends ColumnSerializer {
@Override
public void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException {
out.writeInt((int)(((Time)obj).getTime()/1000));
}
@Override
public Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException {
return new Time((in.readInt()&0xffffffffL)*1000);
}
}
protected class TimestampColumnSerializer extends ColumnSerializer {
@Override
public void writeObject(ObjectOutput out, Object obj, Map<Object, Integer> cache, byte version) throws IOException {
Timestamp ts = (Timestamp)obj;
out.writeLong(ts.getTime());
out.writeInt(ts.getNanos());
}
@Override
public Object readObject(ObjectInput in, List<Object> cache, byte version) throws IOException {
Timestamp ts = new Timestamp(in.readLong());
ts.setNanos(in.readInt());
return ts;
}
}
protected ColumnSerializer getSerializer(String type, byte version) {
ColumnSerializer cs = null;
if (version == 1) {
cs = version1serializers.get((type == null) ? DataTypeManagerService.DefaultDataTypes.OBJECT : type);
}
if (cs == null) {
ColumnSerializer[] serializerSet = serializers.get((type == null) ? DataTypeManagerService.DefaultDataTypes.OBJECT : type);
if (serializerSet != null && serializerSet.length > 0)
cs = serializerSet[0];
}
if (cs == null) {
return defaultSerializer;
}
return cs;
}
@Override
public void writeBatch(ObjectOutput out, String[] types, List<? extends List<?>> batch) throws IOException {
writeBatch(out, types, batch, getCurrentVersion());
}
@Override
public void writeBatch(ObjectOutput out, String[] types, List<? extends List<?>> batch, byte version) throws IOException {
if (batch == null) {
out.writeInt(-1);
} else {
if (version > 0 && batch.size() > 0) {
out.writeInt(-batch.size() -1);
out.writeByte(version);
} else {
out.writeInt(batch.size());
}
if (batch.size() > 0) {
int columns = types.length;
out.writeInt(columns);
Map<Object, Integer> cache = null;
for(int i = 0; i < columns; i++) {
ColumnSerializer serializer = getSerializer(types[i], version);
if (cache == null && serializer.usesCache(version)) {
cache = new HashMap<Object, Integer>();
}
try {
serializer.writeColumn(out, i, batch, cache, version);
} catch (ClassCastException e) {
Object obj = null;
String objectClass = null;
objectSearch: for (int row = 0; row < batch.size(); row++) {
obj = batch.get(row).get(i);
if (obj != null) {
objectClass = obj.getClass().getName();
break objectSearch;
}
}
throw new RuntimeException(Messages.gs(Messages.TEIID.TEIID20001, new Object[] {types[i], new Integer(i), objectClass}), e);
}
}
}
}
}
@Override
public List<List<Object>> readBatch(ObjectInput in, String[] types) throws IOException, ClassNotFoundException {
return readBatch(in, types, (byte)0);
}
private List<List<Object>> readBatch(ObjectInput in, String[] types, byte version) throws IOException, ClassNotFoundException {
int rows = in.readInt();
if (rows == 0) {
return new ArrayList<List<Object>>(0);
} else if (rows > 0) {
int columns = in.readInt();
List<List<Object>> batch = new ResizingArrayList<List<Object>>(rows);
int numBytes = rows/8;
int extraRows = rows % 8;
for (int currentRow = 0; currentRow < rows; currentRow++) {
batch.add(currentRow, Arrays.asList(new Object[columns]));
}
byte[] isNullBuffer = new byte[(extraRows > 0) ? numBytes + 1: numBytes];
for (int col = 0; col < columns; col++) {
getSerializer(types[col], version).readColumn(in, col, batch, isNullBuffer, null, version);
}
return batch;
}
return null;
}
}