/usr/local/CPAN/DBD-Redbase/DBD/Redbase/DataStream.pm
###############################################################################
#
# BUNGISOFT, INC.
#
# PROPRIETARY DATA
#
# THIS DOCUMENT CONTAINS TRADE SECRET DATA WHICH IS THE PROPERTY OF
# BUNGISOFT, INC. THIS DOCUMENT IS SUBMITTED TO RECIPIENT IN
# CONFIDENCE. INFORMATION CONTAINED HEREIN MAY NOT BE USED, COPIED OR
# DISCLOSED IN WHOLE OR IN PART EXCEPT AS PERMITTED BY WRITTEN AGREEMENT
# SIGNED BY AN OFFICER OF BUNGISOFT, INC.
#
# THIS MATERIAL IS ALSO COPYRIGHTED AS AN UNPUBLISHED WORK UNDER
# SECTIONS 104 AND 408 OF TITLE 17 OF THE UNITED STATES CODE.
# UNAUTHORIZED USE, COPYING OR OTHER REPRODUCTION IS PROHIBITED BY LAW.
#
# Copyright (c) 2002 Bungisoft, Inc.
#
# Version: $Id: DataStream.pm,v 1.2 2002/08/20 21:22:21 ilya Exp $
#
###############################################################################
#Note: Functions currently not implemented:
# writeDate($$)
# writeTime($$)
# writeTimestamp($$)
# writeButeArray($$)
# writeDecimal($$)
package DBD::Redbase::DataStream;
use strict;
use warnings;
use Math::BigInt;
use Math::BigFloat;
use POSIX;
use IO::Handle;
use Unicode::String qw(utf8);
use Bit::Vector::Overload;
#Global variables
my $CONST = 2102; #1 carry bit + 1023 + 1 + 1022 + 53 + 2 (round bits)
my $BIAS = 1024;
my $NORMAL = 1;
my $UNDERFLOW = 2;
my $OVERFLOW = 3;
my $DENORMALIZED = 4;
my $FLOAT_MAX_EXP = 38;
my $FLOAT_MAX_NUM = 3.40282347;
my $DOUBLE_MAX_EXP = 308;
my $DOUBLE_MAX_NUM = 1.7976931348623157;
my $EXPBIAS;
my $MAXEXP;
my $MINEXP;
my $MINUNNORMEXP;
my $FLOATSTATUS;
#Input/output filehandles
my $FHOUT;
my $FHIN;
#Resettable bytes counter
my $byte_count;
###############################################################################
# Constructor for DataStream, takes in two arguments input filehandle and
# output file handle
###############################################################################
sub new($$$)
{
my $this = shift();
$FHIN = shift();
$FHOUT = shift();
$byte_count = 0;
my $class = ref($this) || $this;
return bless({}, $class);
}
###############################################################################
# This routine resets the byte counter
###############################################################################
sub resetByteCount()
{
$byte_count = 0;
}
###############################################################################
# This routine is used to query byte counter
###############################################################################
sub getByteCount()
{
return $byte_count;
}
###############################################################################
# The following is public write functions for the object
###############################################################################
sub writeUTF($)
{
my $this = shift;
my $string = _writeUTF(shift);
$FHOUT->print(_writeShort(length($string)));
$FHOUT->print($string);
}
sub writeString($)
{
my $this = shift;
my $string = _writeUTF(shift);
$FHOUT->print(_writeInt(length($string)));
$FHOUT->print($string);
}
sub writeChar($)
{
my $this = shift;
$FHOUT->print(_writeChar(shift()));
}
sub writeBoolean($)
{
my $this = shift;
$FHOUT->print(_writeBoolean(shift()));
}
sub writeByte($)
{
my $this = shift;
$FHOUT->print(_writeByte(shift()));
}
sub writeUnsignedByte($)
{
my $this = shift;
$FHOUT->print(_writeUnsignedByte(shift()));
}
sub writeShort($)
{
my $this = shift;
$FHOUT->print(_writeShort(shift()));
}
sub writeUnsignedShort($)
{
my $this = shift;
$FHOUT->print(_writeUnsignedShort(shift()));
}
sub writeInt($)
{
my $this = shift;
$FHOUT->print(_writeInt(shift()));
}
sub writeLong($)
{
my $this = shift;
$FHOUT->print(_writeLong(shift()));
}
sub writeFloat($)
{
my $this = shift;
$FHOUT->print(_writeFloat(shift()));
}
sub writeDouble($)
{
my $this = shift;
$FHOUT->print(_writeDouble(shift()));
}
sub readUTF($)
{
my $this = shift;
my $bytes_to_read;
my $buf;
$bytes_to_read = $this->readShort();
$FHIN->read($buf, $bytes_to_read);
$byte_count += $bytes_to_read;
return _readUTF($buf);
}
sub readString($)
{
my $this = shift;
my $bytes_to_read;
my $buf;
$bytes_to_read = $this->readInt();
$FHIN->read($buf, $bytes_to_read);
$byte_count += $bytes_to_read;
return _readUTF($buf);
}
sub readChar($)
{
my $this = shift;
my $buf;
$FHIN->read($buf, 2);
$byte_count += 2;
return _readChar($buf);
}
sub readBoolean($)
{
my $this = shift;
my $buf;
$FHIN->read($buf, 1);
$byte_count += 1;
return _readBoolean($buf);
}
sub readByte($)
{
my $this = shift;
my $buf;
$FHIN->read($buf, 1);
$byte_count += 1;
return _readByte($buf);
}
sub readUnsignedByte($)
{
my $this = shift;
my $buf;
$FHIN->read($buf, 1);
$byte_count += 1;
return _readUnsignedByte($buf);
}
sub readShort($)
{
my $this = shift;
my $buf;
$FHIN->read($buf, 2);
$byte_count += 2;
return _readShort($buf);
}
sub readUnsignedShort($)
{
my $this = shift;
my $buf;
$FHIN->read($buf, 2);
$byte_count += 2;
return _readUnsignedShort($buf);
}
sub readInt($)
{
my $this = shift;
my $buf;
$FHIN->read($buf, 4);
$byte_count += 4;
return _readInt($buf);
}
sub readLong($)
{
my $this = shift;
my $buf;
$FHIN->read($buf, 8);
$byte_count += 8;
return _readLong($buf);
}
sub readFloat($)
{
my $this = shift;
my $buf;
$FHIN->read($buf, 4);
$byte_count += 4;
return _readFloat($buf);
}
sub readDouble($)
{
my $this = shift;
my $buf;
$FHIN->read($buf, 8);
$byte_count += 8;
return _readDouble($buf);
}
sub readDate($)
{
my $this = shift;
my $long;
my @time;
$long = $this->readLong();
#Since number is in milliseconds and not seconds
@time = localtime(substr($long, 0, length($long) - 3));
return sprintf("%04d-%02d-%02d", ($time[5] + 1900), ($time[4] + 1), $time[3]);
}
sub readTime($)
{
my $this = shift;
my $long;
my @time;
$long = $this->readLong();
#Since number is in milliseconds and not seconds
@time = localtime(substr($long,0, length($long) - 3));
return sprintf("%02d:%02d:%02d", $time[2], $time[1], $time[0]);
}
sub readTimestamp($)
{
my $this = shift;
my $stamp;
my $nanos;
my @time;
$stamp = $this->readLong();
$nanos = $this->readInt();
#Since number is in milliseconds and not seconds
@time = localtime(substr($stamp, 0, length($stamp) - 3));
return sprintf("%04d-%02d-%02d %02d:%02d:%02d.%d",
($time[5] + 1900),
($time[4] + 1),
$time[3],
$time[2],
$time[1],
$time[0],
$nanos);
}
sub readByteArray($)
{
my $this = shift;
my $buf;
my $size;
$size = $this->readInt();
$FHIN->read($buf, $size);
$byte_count += $size;
return $buf;
}
sub readDecimal($)
{
my $this = shift;
my $bytes;
my $scale;
return _readDecimal($this->readByteArray(), $this->readInt());
}
###############################################################################
# PRIVATE FUNCTIONS
###############################################################################
###############################################################################
# This function return string compatible with with javas Input/OutputStream
# readUTF method
###############################################################################
sub _writeUTF($)
{
my $unicode_string = utf8(shift());
while ((my $pos = index($unicode_string, "\000")) > -1)
{
$unicode_string = substr($unicode_string, 0, $pos) . chr(192) . chr(128) . substr($unicode_string, $pos + 1);
}
return $unicode_string;
}
###############################################################################
# This method writes binary char compatible with Java
###############################################################################
sub _writeChar($)
{
my $u = new Unicode::String(shift());
my $f = $u->hex();
#Chopping to have only a single char
$f =~ s/ .*$//;
$f =~ s/^U\+/0x/;
return _writeShort(oct($f));
}
###############################################################################
# This method writes binary boolean compatible with Java
###############################################################################
sub _writeBoolean($)
{
my $b = shift();
if ((!defined($b)) || ($b == 0) || ($b =~ /^false$/i))
{
return pack("x");
}
else
{
return pack("C", 0x01);
}
}
###############################################################################
# This method writes binary byte compatible with Java
###############################################################################
sub _writeUnsignedByte($)
{
return _writeByte(shift());
}
###############################################################################
# This method writes binary byte compatible with Java
###############################################################################
sub _writeByte($)
{
my $i = int(shift());
#Chopping integer if too big
if ($i > 0xff)
{
$i = $i & 0xff;
}
return pack("C", $i);
}
###############################################################################
# This method writes binary short compatible with Java
###############################################################################
sub _writeShort($)
{
my $i = int(shift());
#Chopping integer if too big
if ($i > 0xffff)
{
$i = $i & 0xffff;
}
return pack("CC", (($i & 0xff00) >> 8), ($i & 0x00ff));
}
###############################################################################
# This method writes unsigned binary short compatible with Java
###############################################################################
sub _writeUnsignedShort($)
{
return _writeShort(shift());
}
###############################################################################
# This method writes binary int compatible with Java
###############################################################################
sub _writeInt($)
{
my $i = int(shift());
#Chopping integer if too big
if ($i > 0xffffffff)
{
$i = $i & 0xffffffff;
}
return pack("CCCC", (($i & 0xff000000) >> 24), (($i & 0x00ff0000) >> 16), (($i & 0x0000ff00) >> 8), ($i & 0x000000ff));
}
###############################################################################
# This method writes binary long compatible with Java
###############################################################################
sub _writeLong($)
{
my $bvector = Bit::Vector->new_Dec(64, shift());
return pack("B64", $bvector->to_Bin());
}
###############################################################################
# This method writes float number compatible with java and
# IEEE 754 single precision
###############################################################################
sub _writeFloat($)
{
return pack("B32", _convert_to_ieee(shift(), 32));
}
###############################################################################
# This method writes float number compatible with java and
# IEEE 754 single precision
###############################################################################
sub _writeDouble($)
{
return pack("B64", _convert_to_ieee(shift(), 64));
}
###############################################################################
# The following functions support ieee 754 conversion from strings
###############################################################################
sub _convert_to_ieee($$)
{
my $number = shift;
my $size = shift;
my @Result;
my @BinValue;
$FLOATSTATUS = $NORMAL;
if($size == 32)
{
$EXPBIAS = 127;
$MAXEXP = 127;
$MINEXP = -126;
$MINUNNORMEXP = -149;
}
else
{
$EXPBIAS = 1023;
$MAXEXP = 1023;
$MINEXP = -1022;
$MINUNNORMEXP = -1074;
}
#Intializing @BinValue
for(my $i = 0; $i < $CONST; $i++)
{
$BinValue[$i] = 0;
}
#Initializing @Result
for(my $i = 0; $i < $size; $i++)
{
$Result[$i] = 0;
}
_dec_2_bin($number, $size, \@BinValue, \@Result);
if ($FLOATSTATUS == $NORMAL)
{
_convert_2_bin($number,$size, \@BinValue, \@Result);
}
if ($FLOATSTATUS == $OVERFLOW)
{
if ($Result[0] == 1)
{
#Negative infinity
if ($size == 32)
{
return "11111111100000000000000000000000";
}
else
{
return "1111111111110000000000000000000000000000000000000000000000000000";
}
}
else
{
#Positive Infinity
if ($size == 32)
{
return "1111111100000000000000000000000";
}
else
{
return "111111111110000000000000000000000000000000000000000000000000000";
}
}
}
elsif ($FLOATSTATUS == $UNDERFLOW)
{
if ($size == 32)
{
return "00000000000000000000000000000000";
}
else
{
return "0000000000000000000000000000000000000000000000000000000000000000";
}
}
else
{
return join ("", @Result);
}
}
sub _dec_2_bin($$$$)
{
my ($number, $size, $BinValue, $Result) = @_;
my $value;
my $intpart;
my $decpart;
my $binexpnt;
my $index1;
my $sign;
my $exp;
my $num;
$number = _canonical($number);
#Sign bit
if($number < 0)
{
$Result->[0] = 1;
}
else
{
$Result->[0] = 0;
}
#Checking for overflow
$exp = $number;
$num = $number;
$exp =~ s/^.*E//;
$exp =~ s/^\+//;
$num =~ s/E.*$//;
if ($size == 32)
{
if (($exp > $FLOAT_MAX_EXP) || (($exp == $FLOAT_MAX_EXP) && ($num >= $FLOAT_MAX_NUM)))
{
$FLOATSTATUS = $OVERFLOW;
return;
}
}
else
{
if (($exp > $DOUBLE_MAX_EXP) || (($exp == $DOUBLE_MAX_EXP) && ($num >= $DOUBLE_MAX_NUM)))
{
$FLOATSTATUS = $OVERFLOW;
return;
}
}
$value = abs($number);
($decpart, $intpart) = modf($value);
#converting integer part
for($index1 = $BIAS; ((($intpart / 2) != 0) && ($index1 >= 0)); $index1--)
{
$BinValue->[$index1] = $intpart % 2;
if (($intpart % 2) == 0)
{
$intpart = $intpart / 2;
}
else
{
$intpart = ($intpart - 1) / 2;
}
}
#converting decimal part
for($index1 = $BIAS + 1; (($decpart > 0) && ($index1 < $CONST)); $index1++)
{
$decpart *= 2;
if ($decpart >= 1)
{
$BinValue->[$index1] = 1;
$decpart--;
}
else
{
$BinValue->[$index1] = 0;
}
}
return;
}
sub _convert_2_bin($$)
{
my ($number, $size, $BinValue, $Result) = @_;
my $binexp;
my $i1;
my $i2;
#Find most significant bit of the the mantissa
for($i1 = 0; (($i1 < $CONST) && ($BinValue->[$i1] != 1)); $i1++) {};
$binexp = $BIAS - $i1;
if ($FLOATSTATUS == $NORMAL)
{
#regular normalized numbers
if ($binexp >= $MINEXP && $binexp <= $MAXEXP)
{
$i1++;
}
#Support for 0 and de-normalized numbers
#exponent underflow at this precision
elsif($binexp < $MINEXP)
{
if ($binexp == $BIAS - $CONST)
{
#Value is trully 0
$FLOATSTATUS = $NORMAL;
return;
}
elsif($binexp < $MINUNNORMEXP)
{
$FLOATSTATUS = $UNDERFLOW;
return;
}
else
{
$FLOATSTATUS = $DENORMALIZED;
}
$binexp = $MINEXP - 1;
$i1 = $BIAS - $binexp;
}
else #$binexp > $MAXEXP
{
$FLOATSTATUS = $OVERFLOW;
return;
}
}
if ($size == 32)
{
$i2 = 9;
}
else
{
$i2 = 12;
}
#copy the Result mantissa
for(; (($i2 < $size) && ($i1 < $CONST)); $i2++, $i1++)
{
$Result->[$i2] = $BinValue->[$i1];
}
#Convert result exponent
if ($size == 32)
{
$i1 = 8;
}
else
{
$i1 = 11;
}
$binexp += $EXPBIAS;
for(; ($binexp / 2) != 0; $i1--)
{
my $r = $binexp % 2;
$Result->[$i1] = $r;
if ($r == 0)
{
$binexp = $binexp / 2;
}
else
{
$binexp = ($binexp - 1) / 2;
}
}
}
###############################################################################
# This function canonizes float of arbitrary length into scientific notation
# of form [+,-][0-9].[0-9]*E[+,-][0-9][0-9][0-9][0-9]+
###############################################################################
sub _canonical($)
{
my $number = shift();
my $sign;
my $exp;
my $mantissa;
my $index;
$number = uc($number); #In case we have exponential notation
if ($number >= 0)
{
$sign = "+";
}
else
{
#if sign is negative sprintf will produce it
$sign = "-"
}
$number =~ s/^\+//;
$number =~ s/^-//;
if ($number =~ /E/)
{
$exp = $number;
$exp =~ s/[+,-]*[0-9,.]+E//;
$exp =~ s/^\+0*//;
$exp =~ s/^-0*/-/;
$mantissa = $number;
$mantissa =~ s/E.*$//;
}
else
{
$exp = 0;
$mantissa = $number;
}
$mantissa .= "." if (!($mantissa =~ /\./));
$mantissa = "0" . $mantissa if ($mantissa =~ /^\./);
$index = index($mantissa, '.');
if ($index != 1)
{
$exp += $index - 1;
}
$mantissa =~ s/\.//;
if ($mantissa =~ /^0+$/)
{
$mantissa = 0;
}
elsif ($mantissa =~ /^0/)
{
while($mantissa =~ /^0/)
{
$exp -= 1;
$mantissa =~ s/^0//;
}
}
$mantissa = substr($mantissa, 0, 1) . "." . substr($mantissa, 1);
$mantissa =~ s/0*$//;
$number = $sign . $mantissa . "E" . sprintf("%05d", $exp);
return $number;
}
###############################################################################
# This method converts Java UTF-8 string into current encoding
###############################################################################
sub _readUTF($)
{
my $unicode_string = utf8(shift());
while ((my $pos = index($unicode_string, chr(192) . chr(128))) > -1)
{
$unicode_string = substr($unicode_string, 0, $pos) . chr(0) . substr($unicode_string, $pos + 2);
}
return $unicode_string->latin1();
}
###############################################################################
# This method reads binary char compatible with Java
###############################################################################
sub _readChar($)
{
return chr(_readShort(shift) & 0x00ff);
}
###############################################################################
# This method reads binary boolean compatible with Java
###############################################################################
sub _readBoolean($)
{
return unpack("C", shift());
}
###############################################################################
# This method reads binary byte compatible with Java
###############################################################################
sub _readByte($)
{
return unpack("c", shift());
}
###############################################################################
# This method reads binary unsigned byte compatible with Java
###############################################################################
sub _readUnsignedByte($)
{
return unpack("C", shift());
}
###############################################################################
# This method reads binary short compatible with Java
###############################################################################
sub _readShort($)
{
my $i = shift;
my $a = unpack("C", substr($i,0,1));
my $b = unpack("C", substr($i,1,1));
#Trick to make perl treat this as signed
return unpack("s", pack("s", (($a << 8) | ($b & 0xff))));
}
###############################################################################
# This method reads binary unsigned short compatible with Java
###############################################################################
sub _readUnsignedShort($)
{
my $i = shift;
return (
((unpack("C", substr($i,0,1)) & 0xff) << 8) |
(unpack("C", substr($i,1,1)) & 0xff))
}
###############################################################################
# This method reads binary int compatible with Java
###############################################################################
sub _readInt($)
{
my $i = shift;
#Trick to make perl treat this as signed
return unpack("i", pack("i", (
((unpack("C", substr($i,1,1)) & 0xff) << 24) |
((unpack("C", substr($i,1,1)) & 0xff) << 16) |
((unpack("C", substr($i,2,1)) & 0xff) << 8) |
(unpack("C", substr($i,3,1)) & 0xff))))
}
###############################################################################
# This method reads binary long compatible with Java
###############################################################################
sub _readLong($)
{
my $input = shift;
my $bvector;
my $bstring;
$bvector = Bit::Vector->new_Bin(64, unpack("B64", $input));
return $bvector->to_Dec();
}
###############################################################################
# This method reads Java's BigDecimal
###############################################################################
sub _readDecimal($$)
{
my $bytes = shift;
my $scale = shift;
my $bin_string;
my $bvector;
my $bvector_scale;
my $decstring;
my $negative = 0;
$bin_string = unpack("B*", $bytes);
$bvector = Bit::Vector->new_Bin(length($bin_string), $bin_string);
$decstring = $bvector->to_Dec();
$decstring =~ s/^\+//;
if ($decstring =~ /^-/)
{
$negative = 1;
$decstring =~ s/^-//;
}
if (length($decstring) < $scale)
{
$decstring = ('0' x ($scale - length($decstring))) . $decstring;
}
$decstring = substr($decstring, 0, length($decstring) - $scale) . "." . substr($decstring, length($decstring) - $scale);
$decstring = "-" . $decstring if ($negative);
return $decstring;
}
###############################################################################
# This method reads binary float compatible with Java
###############################################################################
#XXX need to check for positive and negative infinity
sub _readFloat($)
{
my $bitvec = Bit::Vector->new_Bin(32, unpack("B32", shift()));
my $evec = new Bit::Vector(8);
my $mvec = new Bit::Vector(24);
my $s;
my $m;
my $e;
if ($bitvec->bit_test(31))
{
$s = -1;
}
else
{
$s = 1;
}
$evec->Interval_Copy($bitvec, 0, 23, 8);
$e = oct("0x" . $evec->to_Hex());
$mvec->Interval_Copy($bitvec, 0, 0, 23);
if ($e == 0)
{
$mvec <<= 1;
}
else
{
$mvec->Bit_On(23);
}
$m = oct("0x". $mvec->to_Hex());
return $s * $m * pow(2, ($e - 150));
}
###############################################################################
# This method reads binary double compatible with Java
###############################################################################
#XXX need to check for positive and negative infinity
sub _readDouble($)
{
my $bitvec = Bit::Vector->new_Bin(64, unpack("B64", shift()));
my $evec = new Bit::Vector(11);
my $mvec = new Bit::Vector(54);
my $s;
my $m;
my $e;
my $result;
if ($bitvec->bit_test(63))
{
$s = -1;
}
else
{
$s = 1;
}
$evec->Interval_Copy($bitvec, 0, 52, 11);
$e = oct("0x" . $evec->to_Hex());
$mvec->Interval_Copy($bitvec, 0, 0, 52);
if ($e == 0)
{
$mvec <<= 1;
}
else
{
$mvec->Bit_On(52);
}
$m = new Math::BigFloat($mvec->to_Dec());
return $s * $m * big_pow2($e - 1075);
}
sub big_pow2($)
{
my $pow = shift;
my $base = Bit::Vector->new_Dec(2048, 2);
Bit::Vector->Configuration("in=dec,ops=arithmetic,out=dec");
$base **= abs($pow);
$base = new Math::BigFloat($base->to_Dec());
if ($pow < 0)
{
$base = 1 / $base;
}
return $base;
}
1;