/* * eXist Open Source Native XML Database * Copyright (C) 2001-04 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.io.UnsupportedEncodingException; import java.util.GregorianCalendar; import javax.xml.datatype.DatatypeConfigurationException; import javax.xml.datatype.DatatypeFactory; import javax.xml.datatype.XMLGregorianCalendar; import org.apache.log4j.Logger; import org.exist.EXistException; import org.exist.collections.Collection; import org.exist.util.ByteConversion; import org.exist.util.UTF8; import org.exist.xquery.XPathException; import org.exist.xquery.value.AbstractDateTimeValue; import org.exist.xquery.value.BooleanValue; import org.exist.xquery.value.DateTimeValue; import org.exist.xquery.value.DateValue; import org.exist.xquery.value.DoubleValue; import org.exist.xquery.value.FloatValue; import org.exist.xquery.value.IntegerValue; import org.exist.xquery.value.StringValue; import org.exist.xquery.value.Type; /** * @author wolf * */ //TODO : rename as NativeIndexValueFactory ? -pb public class ValueIndexFactory { private static Logger LOG = Logger.getLogger(ValueIndexFactory.class.getName()); //TODO : check public static int OFFSET_COLLECTION_ID = 0; //TODO : check public static int OFFSET_TYPE = OFFSET_COLLECTION_ID + Collection.LENGTH_COLLECTION_ID; //2 public static int LENGTH_VALUE_TYPE = 1; //sizeof byte public static int OFFSET_VALUE = OFFSET_TYPE + ValueIndexFactory.LENGTH_VALUE_TYPE; //3 public final static Indexable deserialize(byte[] data, int start, int len) throws EXistException { 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; try { s = new String(data, start + (ValueIndexFactory.LENGTH_VALUE_TYPE), len - (ValueIndexFactory.LENGTH_VALUE_TYPE), "UTF-8"); } catch (UnsupportedEncodingException e) { LOG.error(e); throw new EXistException(e); } return new StringValue(s); } /* xs:dateTime */ else if(Type.subTypeOf(type, Type.DATE_TIME)) { //get the dateTime back as a long long value = ByteConversion.byteToLong(data, start + (ValueIndexFactory.LENGTH_VALUE_TYPE)); //Create a GregorianCalendar from the long (normalized datetime as milliseconds since the Epoch) GregorianCalendar utccal = new GregorianCalendar(); utccal.setTimeInMillis(value); //Create a XMLGregorianCalendar from the GregorianCalendar try { XMLGregorianCalendar xmlutccal = DatatypeFactory.newInstance().newXMLGregorianCalendar(utccal); return new DateTimeValue(xmlutccal); } catch(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)) { //get the date back as a long long value = ByteConversion.byteToLong(data, start + (ValueIndexFactory.LENGTH_VALUE_TYPE)); //Create a GregorianCalendar from the long (normalized datetime as milliseconds since the Epoch) GregorianCalendar utccal = new GregorianCalendar(); utccal.setTimeInMillis(value); //Create a XMLGregorianCalendar from the GregorianCalendar try { XMLGregorianCalendar xmlutccal = DatatypeFactory.newInstance().newXMLGregorianCalendar(utccal); return new DateValue(xmlutccal); } catch(DatatypeConfigurationException dtce) { throw new EXistException("Could not deserialize xs:date data type for range index key: " + Type.getTypeName(type) + " - " + dtce.getMessage()); } catch(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) { long bits = ByteConversion.byteToLong(data, start + (ValueIndexFactory.LENGTH_VALUE_TYPE)) ^ 0x8000000000000000L; double d = Double.longBitsToDouble(bits); return new DoubleValue(d); } /* xs:float */ else if (type == Type.FLOAT) { int bits = ByteConversion.byteToInt(data, start + (ValueIndexFactory.LENGTH_VALUE_TYPE)) ^ 0x80000000; float f = Float.intBitsToFloat(bits); return new FloatValue(f); } /* 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)); } } /* public final static byte[] serialize(Indexable value, short collectionId) throws EXistException { //TODO : refactor (only strings are case sensitive) return serialize(value, collectionId, true); } */ /** * @ deprecated * @param value * @param collectionId * @param caseSensitive * @throws EXistException */ /* public final static byte[] serialize(Indexable value, short collectionId, 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[UTF8.encoded(val) + (Collection.LENGTH_COLLECTION_ID + ValueIndexFactory.LENGTH_VALUE_TYPE)]; ByteConversion.shortToByte(collectionId, data, OFFSET_COLLECTION_ID); data[OFFSET_TYPE] = (byte) value.getType(); // TODO: cast to byte is not safe UTF8.encode(val, data, OFFSET_VALUE); return data; } // xs:dateTime else if(Type.subTypeOf(value.getType(), Type.DATE_TIME)) { GregorianCalendar utccal = ((AbstractDateTimeValue)value).calendar.normalize().toGregorianCalendar(); //Get the dateTime (XMLGregorianCalendar) normalized to UTC (as a GregorianCalendar) long millis = utccal.getTimeInMillis(); //Get the normalized dateTime as a long (milliseconds since the Epoch) byte[] data = new byte[(Collection.LENGTH_COLLECTION_ID + ValueIndexFactory.LENGTH_VALUE_TYPE) + 8]; //alocate a byte array for holding collectionId,Type,long (11 = (byte)short + byte + (byte)long) ByteConversion.shortToByte(collectionId, data, OFFSET_COLLECTION_ID); //put the collectionId in the byte array //TODO : should we keep the actual type, i.e. value.getType() ? data[OFFSET_TYPE] = (byte) Type.DATE_TIME; //put the Type in the byte array ByteConversion.longToByte(millis, data, OFFSET_VALUE); //put the long in the byte array return(data); } // xs:integer else if(Type.subTypeOf(value.getType(), Type.INTEGER)) { long l = ((IntegerValue)value).getValue() - Long.MIN_VALUE; byte[] data = new byte[(Collection.LENGTH_COLLECTION_ID + ValueIndexFactory.LENGTH_VALUE_TYPE) + 8]; ByteConversion.shortToByte(collectionId, data, OFFSET_COLLECTION_ID); data[OFFSET_TYPE] = (byte) Type.INTEGER; ByteConversion.longToByte(l, data, OFFSET_VALUE); return data; } // xs:double else if (value.getType() == Type.DOUBLE) { final byte[] data = new byte[(Collection.LENGTH_COLLECTION_ID + ValueIndexFactory.LENGTH_VALUE_TYPE) + 8]; ByteConversion.shortToByte(collectionId, data, OFFSET_COLLECTION_ID); data[OFFSET_TYPE] = (byte) Type.DOUBLE; final long bits = Double.doubleToLongBits(((DoubleValue)value).getValue()) ^ 0x8000000000000000L; ByteConversion.longToByte(bits, data, OFFSET_VALUE); return data; } // xs:float else if (value.getType() == Type.FLOAT) { final byte[] data = new byte[(Collection.LENGTH_COLLECTION_ID + ValueIndexFactory.LENGTH_VALUE_TYPE) + 4]; ByteConversion.shortToByte(collectionId, data, OFFSET_COLLECTION_ID); data[OFFSET_TYPE] = (byte) Type.FLOAT; final int bits = (int)(Float.floatToIntBits(((FloatValue)value).getValue()) ^ 0x80000000); ByteConversion.intToByte(bits, data, OFFSET_VALUE); return data; } // xs:boolean else if(value.getType() == Type.BOOLEAN) { byte[] data = new byte[(Collection.LENGTH_COLLECTION_ID + ValueIndexFactory.LENGTH_VALUE_TYPE) + 1]; ByteConversion.shortToByte(collectionId, data, OFFSET_COLLECTION_ID); data[OFFSET_TYPE] = Type.BOOLEAN; data[OFFSET_VALUE] = (byte)(((BooleanValue)value).getValue() ? 1 : 0); return data; } // unknown! else { throw new EXistException("Unknown data type for serialization: " + Type.getTypeName(value.getType())); } } */ /** * @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)) { GregorianCalendar utccal = ((AbstractDateTimeValue)value).calendar.normalize().toGregorianCalendar(); //Get the dateTime (XMLGregorianCalendar) normalized to UTC (as a GregorianCalendar) long millis = utccal.getTimeInMillis(); //Get the normalized dateTime as a long (milliseconds since the Epoch) final byte[] data = new byte[offset + ValueIndexFactory.LENGTH_VALUE_TYPE + 8]; //allocate an appropriately sized byte array for holding Type,long data[offset] = (byte) Type.DATE_TIME; //put the Type in the byte array ByteConversion.longToByte(millis, data, offset+1); //put the long into the byte array return(data); //return the byte array } /* xs:date */ else if(Type.subTypeOf(value.getType(), Type.DATE)) { GregorianCalendar utccal = ((AbstractDateTimeValue)value).calendar.normalize().toGregorianCalendar(); //Get the dateTime (XMLGregorianCalendar) normalized to UTC (as a GregorianCalendar) long millis = utccal.getTimeInMillis(); //Get the normalized dateTime as a long (milliseconds since the Epoch) final byte[] data = new byte[offset + ValueIndexFactory.LENGTH_VALUE_TYPE + 8]; //allocate an appropriately sized byte array for holding Type,long data[offset] = (byte) Type.DATE; //put the Type in the byte array ByteConversion.longToByte(millis, data, offset+1); //put the long into the byte array return(data); //return the byte array } /* 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; 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.intToByte(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; } /* unknown! */ else { throw new EXistException("Unknown data type for serialization: " + Type.getTypeName(value.getType())); } } }