/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-2010 The eXist Team
*
* http://exist-db.org
*
* 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 2
* 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
package org.exist.storage;
import java.math.BigDecimal;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeConstants;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import org.apache.log4j.Logger;
import org.exist.EXistException;
import org.exist.storage.btree.Value;
import org.exist.util.ByteConversion;
import org.exist.util.UTF8;
import org.exist.xquery.XPathException;
import org.exist.xquery.value.*;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
* @author wolf
*
*/
// TODO : rename as NativeIndexValueFactory ? -pb
public class ValueIndexFactory {
private static Logger LOG = Logger.getLogger(ValueIndexFactory.class.getName());
public static int LENGTH_VALUE_TYPE = 1; // sizeof byte
public final static Indexable deserialize(byte[] data, int start, int len) throws EXistException {
final int type = data[start];
// TODO : improve deserialization (use static methods in the org.exist.xquery.Value package
/* xs:string */
if (Type.subTypeOf(type, Type.STRING)) {
String s = new String(data, start + (ValueIndexFactory.LENGTH_VALUE_TYPE),
len - (ValueIndexFactory.LENGTH_VALUE_TYPE), UTF_8);
return new StringValue(s);
}
/* xs:dateTime */
else if (Type.subTypeOf(type, Type.DATE_TIME)) {
try {
final XMLGregorianCalendar xmlutccal =
DatatypeFactory.newInstance().newXMLGregorianCalendar(
ByteConversion.byteToIntH(data, start + 1),
data[start + 5],
data[start + 6],
data[start + 7],
data[start + 8],
data[start + 9],
ByteConversion.byteToShortH(data, start + 10),
0);
return new DateTimeValue(xmlutccal);
} catch (final DatatypeConfigurationException dtce) {
throw new EXistException("Could not deserialize xs:dateTime data type" +
"for range index key: " + Type.getTypeName(type) + " - " + dtce.getMessage());
}
}
/* xs:date */
else if (Type.subTypeOf(type, Type.DATE)) {
try {
final XMLGregorianCalendar xmlutccal =
DatatypeFactory.newInstance().newXMLGregorianCalendarDate(
ByteConversion.byteToIntH(data, start + 1),
data[start + 5],
data[start + 6],
0);
return new DateValue(xmlutccal);
} catch (final DatatypeConfigurationException dtce) {
throw new EXistException("Could not deserialize xs:date data type" +
" for range index key: " + Type.getTypeName(type) + " - " + dtce.getMessage());
} catch (final XPathException xpe) {
throw new EXistException("Could not deserialize xs:date data type" +
" for range index key: " + Type.getTypeName(type) + " - " + xpe.getMessage());
}
}
/* xs:integer */
else if (Type.subTypeOf(type, Type.INTEGER)) {
return new IntegerValue(ByteConversion.byteToLong(data, start +
(ValueIndexFactory.LENGTH_VALUE_TYPE)) ^ 0x8000000000000000L);
}
/* xs:double */
else if (type == Type.DOUBLE) {
final long bits = ByteConversion.byteToLong(data, start +
(ValueIndexFactory.LENGTH_VALUE_TYPE)) ^ 0x8000000000000000L;
final double d = Double.longBitsToDouble(bits);
return new DoubleValue(d);
}
/* xs:float */
else if (type == Type.FLOAT) {
final int bits = ByteConversion.byteToInt(data, start +
(ValueIndexFactory.LENGTH_VALUE_TYPE)) ^ 0x80000000;
final float f = Float.intBitsToFloat(bits);
return new FloatValue(f);
}
/* xs:decimal */
else if (type == Type.DECIMAL) {
//actually loaded from string data due to the uncertain length
String s = new String(data, start + (ValueIndexFactory.LENGTH_VALUE_TYPE),
len - (ValueIndexFactory.LENGTH_VALUE_TYPE), UTF_8);
return new DecimalValue(new BigDecimal(s));
}
/* xs:boolean */
else if (type == Type.BOOLEAN) {
return new BooleanValue(data[start + (ValueIndexFactory.LENGTH_VALUE_TYPE)] == 1);
}
/* unknown! */
else {
throw new EXistException("Unknown data type for deserialization: " + Type.getTypeName(type));
}
}
/**
* @param value
* @param offset
* @throws EXistException
*/
public final static byte[] serialize(Indexable value, int offset) throws EXistException {
// TODO : refactor (only strings are case sensitive)
return serialize(value, offset, true);
}
/**
* @deprecated
* @param value
* @param offset
* @param caseSensitive
* @throws EXistException
*/
public final static byte[] serialize(Indexable value, int offset, boolean caseSensitive) throws EXistException {
/* xs:string */
if (Type.subTypeOf(value.getType(), Type.STRING)) {
final String val = caseSensitive ?
((StringValue) value).getStringValue() :
((StringValue) value).getStringValue().toLowerCase();
final byte[] data = new byte[offset + ValueIndexFactory.LENGTH_VALUE_TYPE + UTF8.encoded(val)];
data[offset] = (byte) value.getType(); // TODO: cast to byte is not safe
UTF8.encode(val, data, offset + ValueIndexFactory.LENGTH_VALUE_TYPE);
return data;
}
/* xs:dateTime */
else if (Type.subTypeOf(value.getType(), Type.DATE_TIME)) {
final XMLGregorianCalendar utccal = ((AbstractDateTimeValue) value).calendar.normalize();
final byte[] data = new byte[offset + 12]; // allocate an appropriately sized
data[offset] = (byte) Type.DATE_TIME; // put the type in the byte array
ByteConversion.intToByteH(utccal.getYear(), data, offset + 1);
data[offset + 5] = (byte) utccal.getMonth();
data[offset + 6] = (byte) utccal.getDay();
data[offset + 7] = (byte) utccal.getHour();
data[offset + 8] = (byte) utccal.getMinute();
data[offset + 9] = (byte) utccal.getSecond();
final int ms = utccal.getMillisecond();
ByteConversion.shortToByteH((short) (ms == DatatypeConstants.FIELD_UNDEFINED ? 0 : ms),
data, offset + 10);
return (data); // return the byte array
}
/* xs:date */
else if (Type.subTypeOf(value.getType(), Type.DATE)) {
final XMLGregorianCalendar utccal = ((AbstractDateTimeValue) value).calendar.normalize();
final byte[] data = new byte[offset + 7]; // allocate an appropriately sized
data[offset] = (byte) Type.DATE;
ByteConversion.intToByteH(utccal.getYear(), data, offset + 1);
data[offset + 5] = (byte) utccal.getMonth();
data[offset + 6] = (byte) utccal.getDay();
return data;
}
/* xs:integer */
else if (Type.subTypeOf(value.getType(), Type.INTEGER)) {
final byte[] data = new byte[offset + ValueIndexFactory.LENGTH_VALUE_TYPE + 8];
data[offset] = (byte) Type.INTEGER;
final long l = ((IntegerValue) value).getValue() - Long.MIN_VALUE;
ByteConversion.longToByte(l, data, offset + ValueIndexFactory.LENGTH_VALUE_TYPE);
return data;
}
/* xs:double */
else if (value.getType() == Type.DOUBLE) {
final byte[] data = new byte[offset + ValueIndexFactory.LENGTH_VALUE_TYPE + 8];
data[offset] = (byte) Type.DOUBLE;
final long bits = Double.doubleToLongBits(((DoubleValue) value).getValue()) ^ 0x8000000000000000L;
ByteConversion.longToByte(bits, data, offset + ValueIndexFactory.LENGTH_VALUE_TYPE);
return data;
}
/* xs:float */
else if (value.getType() == Type.FLOAT) {
final byte[] data = new byte[offset + ValueIndexFactory.LENGTH_VALUE_TYPE + 4];
data[offset] = (byte) Type.FLOAT;
final int bits = (int) (Float.floatToIntBits(((FloatValue) value).getValue()) ^ 0x80000000);
ByteConversion.intToByteH(bits, data, offset + ValueIndexFactory.LENGTH_VALUE_TYPE);
return data;
}
/* xs:boolean */
else if (value.getType() == Type.BOOLEAN) {
final byte[] data = new byte[offset + ValueIndexFactory.LENGTH_VALUE_TYPE + 1];
data[offset] = Type.BOOLEAN;
data[offset + ValueIndexFactory.LENGTH_VALUE_TYPE] = (byte) (((BooleanValue) value).getValue() ? 1 : 0);
return data;
}
else if(value.getType() == Type.DECIMAL) {
//actually stored as string data due to variable length
final BigDecimal dec = ((DecimalValue)value).getValue();
final String val = dec.toString();
final byte[] data = new byte[offset + ValueIndexFactory.LENGTH_VALUE_TYPE + UTF8.encoded(val)];
data[offset] = (byte) value.getType(); // TODO: cast to byte is not safe
UTF8.encode(val, data, offset + ValueIndexFactory.LENGTH_VALUE_TYPE);
return data;
}
/* unknown! */
else {
throw new EXistException("Unknown data type for serialization: " + Type.getTypeName(value.getType()));
}
}
public static void main(String[] args) {
try {
//******** DateTimeValue ********
final DateTimeValue dtv = new DateTimeValue("0753-04-21T01:00:00+01:00");
final byte[] b1 = ValueIndexFactory.serialize(dtv, 0);
print(dtv, b1);
final DateTimeValue dtv2 = new DateTimeValue("1960-03-19T19:03:59.782+01:00");
final byte[] b2 = ValueIndexFactory.serialize(dtv2, 0);
print(dtv2, b2);
System.out.println(new Value(b1).compareTo(new Value(b2)));
final DateTimeValue dtv2_ = (DateTimeValue) ValueIndexFactory.deserialize(b2, 0, b2.length);
if (!dtv2.equals(dtv2_))
{System.out.println("ERROR! "+dtv2.toString()+" ne "+dtv2_.toString());}
//******** DateValue ********
final DateValue dv = new DateValue("1960-03-19Z");
final byte[] b3 = ValueIndexFactory.serialize(dv, 0);
print(dv, b3);
final DateValue dv_ = (DateValue) ValueIndexFactory.deserialize(b3, 0, b3.length);
if (!dv.equals(dv_))
{System.out.println("ERROR! "+dv.toString()+" ne "+dv_.toString());}
//******** IntegerValue ********
final IntegerValue iv = new IntegerValue(753);
final byte[] i1 = ValueIndexFactory.serialize(iv, 0);
print(iv, i1);
final IntegerValue iv2 = new IntegerValue(1960);
final byte[] i2 = ValueIndexFactory.serialize(iv2, 0);
print(iv2, i2);
System.out.println(new Value(i1).compareTo(new Value(i2)));
} catch (final Exception e) {
e.printStackTrace();
}
}
private static void print(AtomicValue dtv, byte[] data) throws XPathException {
System.out.print(dtv.getStringValue() + " = ");
for (int i = 0; i < data.length; i++) {
System.out.print(" " + Integer.toHexString(data[i] & 0xff));
}
System.out.println();
}
}