/*
* Copyright 2009-2016 Tilmann Zaeschke. All rights reserved.
*
* This file is part of ZooDB.
*
* ZooDB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ZooDB 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ZooDB. If not, see <http://www.gnu.org/licenses/>.
*
* See the README and COPYING files for further information.
*/
package org.zoodb.internal;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import org.zoodb.internal.server.index.BitTools;
/**
* This class contains utility methods for (de-)serialization.
*
* @author Tilmann Zaeschke
*/
public class SerializerTools {
// ********************** persistent types enums *******************************
//Avoiding 'if' cascades reduced time in e.g. serializePrimitive by 25%
public enum PRIMITIVE {
/** BOOL */ BOOLEAN,
/** BYTE */ BYTE,
/** CHAR */ CHAR,
/** DOUBLE */ DOUBLE,
/** FLOAT */ FLOAT,
/** INT */ INT,
/** LONG */ LONG,
/** SHORT */ SHORT}
static final IdentityHashMap<Class<?>, PRIMITIVE> PRIMITIVE_CLASSES =
new IdentityHashMap<Class<?>, PRIMITIVE>();
static {
PRIMITIVE_CLASSES.put(Boolean.class, PRIMITIVE.BOOLEAN);
PRIMITIVE_CLASSES.put(Byte.class, PRIMITIVE.BYTE);
PRIMITIVE_CLASSES.put(Character.class, PRIMITIVE.CHAR);
PRIMITIVE_CLASSES.put(Double.class, PRIMITIVE.DOUBLE);
PRIMITIVE_CLASSES.put(Float.class, PRIMITIVE.FLOAT);
PRIMITIVE_CLASSES.put(Integer.class, PRIMITIVE.INT);
PRIMITIVE_CLASSES.put(Long.class, PRIMITIVE.LONG);
PRIMITIVE_CLASSES.put(Short.class, PRIMITIVE.SHORT);
}
static final IdentityHashMap<Class<?>, PRIMITIVE> PRIMITIVE_TYPES =
new IdentityHashMap<Class<?>, PRIMITIVE>();
static {
PRIMITIVE_TYPES.put(Boolean.TYPE, PRIMITIVE.BOOLEAN);
PRIMITIVE_TYPES.put(Byte.TYPE, PRIMITIVE.BYTE);
PRIMITIVE_TYPES.put(Character.TYPE, PRIMITIVE.CHAR);
PRIMITIVE_TYPES.put(Double.TYPE, PRIMITIVE.DOUBLE);
PRIMITIVE_TYPES.put(Float.TYPE, PRIMITIVE.FLOAT);
PRIMITIVE_TYPES.put(Integer.TYPE, PRIMITIVE.INT);
PRIMITIVE_TYPES.put(Long.TYPE, PRIMITIVE.LONG);
PRIMITIVE_TYPES.put(Short.TYPE, PRIMITIVE.SHORT);
}
// ********************** persistent classes dictionary *******************************
private static final class RefDummy {};
private static final class RefNull {};
private static final class RefPersistent {};
private static final class RefArray {};
//dummy is used, because 0 means undefined class
public static final Class<?> REF_DUMMY = RefDummy.class;
public static final Class<?> REF_NULL = RefNull.class;
public static final Class<?> REF_PERS = RefPersistent.class;
public static final Class<?> REF_ARRAY = RefArray.class;
public static final byte REF_NULL_ID = -1;
public static final byte REF_CUSTOM_CLASS_ID = 0;
public static final byte REF_PERS_ID = 1;
public static final byte REF_ARRAY_ID = 2;
public static final int REF_CLS_OFS;
// Here is how class information is transmitted:
// If the class does not exist in the hashMap, then it is added and its
// name is written to the stream. Otherwise only the id of the class in
// the List in written.
// The class information is required because it can be any sub-type of the
// Field type, but the exact type is required for instantiation.
// This can't be static. To make sure that the IDs are the same for
// server and client, the map has to be rebuild for every Transaction,
//or at least for every new connection.
// Otherwise problems would occur if e.g. one of the processes crash
// and has to rebuild it's map, or if the Sender uses the same Map for
// all receivers, regardless whether they all get the same data.
static final IdentityHashMap<Class<?>, Byte> PRE_DEF_CLASSES_MAP;
static final ArrayList<Class<?>> PRE_DEF_CLASSES_ARRAY;
static {
IdentityHashMap<Class<?>, Byte> map = new IdentityHashMap<Class<?>, Byte>();
ArrayList<Class<?>> list = new ArrayList<Class<?>>();
list.add(REF_DUMMY); //Not used.
list.add(REF_PERS); //Field type is non-persistent-capable, but referenced object is FCO.
list.add(REF_ARRAY); //Indicates array. There is no need to serialize array type names,
//because they consist only of depth and component type name.
//primitive classes
list.add(Boolean.TYPE);
list.add(Byte.TYPE);
list.add(Character.TYPE);
list.add(Double.TYPE);
list.add(Float.TYPE);
list.add(Integer.TYPE);
list.add(Long.TYPE);
list.add(Short.TYPE);
//primitive array classes
list.add(boolean[].class);
list.add(byte[].class);
list.add(char[].class);
list.add(double[].class);
list.add(float[].class);
list.add(int[].class);
list.add(long[].class);
list.add(short[].class);
list.add(boolean[][].class);
list.add(byte[][].class);
list.add(char[][].class);
list.add(double[][].class);
list.add(float[][].class);
list.add(int[][].class);
list.add(long[][].class);
list.add(short[][].class);
//primitive classes
list.add(Boolean.class);
list.add(Byte.class);
list.add(Character.class);
list.add(Double.class);
list.add(Float.class);
list.add(Integer.class);
list.add(Long.class);
list.add(Short.class);
//primitive array classes
list.add(Boolean[].class);
list.add(Byte[].class);
list.add(Character[].class);
list.add(Double[].class);
list.add(Float[].class);
list.add(Integer[].class);
list.add(Long[].class);
list.add(Short[].class);
//other java classes
list.add(String.class);
list.add(String[].class);
list.add(Date.class);
list.add(Date[].class);
list.add(URL.class);
list.add(URL[].class);
list.add(UUID.class);
list.add(UUID[].class);
list.add(Enum.class);
//persistent classes
//We don't list persistent capable classes such as DBVector here. It would not safe much,
//as we only store the oid of the schema anyway.
//for future improvements
list.add(REF_DUMMY);
list.add(REF_DUMMY);
list.add(REF_DUMMY);
list.add(REF_DUMMY);
list.add(REF_DUMMY);
list.add(REF_DUMMY);
list.add(REF_DUMMY);
list.add(REF_DUMMY);
list.add(REF_DUMMY);
list.add(REF_DUMMY);
list.add(REF_DUMMY);
list.add(REF_DUMMY);
list.add(REF_DUMMY);
list.add(REF_DUMMY);
list.add(REF_DUMMY);
list.add(REF_DUMMY);
list.add(REF_DUMMY);
list.add(REF_DUMMY);
list.add(REF_DUMMY);
list.add(REF_DUMMY);
//Map, List, Set, ...?
list.add(List.class);
list.add(Set.class);
list.add(Map.class);
list.add(HashMap.class);
list.add(HashSet.class);
list.add(LinkedList.class);
list.add(ArrayList.class);
list.add(Hashtable.class);
list.add(Vector.class);
//fill map
for (int i = 0; i < list.size(); i++) {
map.put(list.get(i), (byte)i);
}
//TODO use real array?
//PRE_DEF_CLASSES_ARRAY = Collections.unmodifiableList(list);
PRE_DEF_CLASSES_ARRAY = list;
//PRE_DEF_CLASSES_MAP = (IdentityHashMap<Class<?>, Short>) Collections.unmodifiableMap(map);
PRE_DEF_CLASSES_MAP = map;
//TODO set fixed, e.g. 100?
REF_CLS_OFS = list.size();
//REF_PERS_ID = map.get(REF_PERS);
if (REF_PERS_ID != map.get(REF_PERS)) {
throw new IllegalStateException("" + REF_PERS_ID + " / " + map.get(REF_PERS));
}
if (REF_ARRAY_ID != map.get(REF_ARRAY)) {
throw new IllegalStateException("" + REF_ARRAY_ID + " / " + map.get(REF_ARRAY));
}
}
// ********************** persistent classes fields *******************************
// Synchronised to allow concurrent access from different Threads.
private static final ConcurrentHashMap<Class<?>, List<Field>> _seenClasses =
new ConcurrentHashMap<Class<?>, List<Field>>();
static final List<Field> getFields(Class<?> cl) {
List<Field> fields = _seenClasses.get(cl);
if (fields != null) {
return _seenClasses.get(cl);
}
// TODO the following could be optimised:
// Instead of filling a List with all attributes and then removing the
// undesired ones, the List should be build up only with desired
// attributes
// while looping through the individual arrays from getDeclaredFields();
// Add attributes of super-classes for jar,
// these are already included in the database class.
Field[] fieldA = cl.getDeclaredFields();
fields = new LinkedList<Field>(Arrays.asList(fieldA));
Class<?> cSuper = cl.getSuperclass();
while (cSuper != null) {
Collections.addAll(fields, cSuper.getDeclaredFields());
cSuper = cSuper.getSuperclass();
}
// Remove transient and static attrs from list (they are not in the DB)
ListIterator<Field> vil = fields.listIterator();
vil = fields.listIterator(0);
int modifiers;
while (vil.hasNext()) {
try {
Field field = vil.next();
modifiers = field.getModifiers();
if (Modifier.isTransient(modifiers)
|| Modifier.isStatic(modifiers)) {
vil.remove();
continue;
}
field.setAccessible(true);
} catch (RuntimeException e) {
throw e;
}
}
//Because we are not in a synchronised block, this may actually
//overwrite an existing entry. But it's very unlikely and would not do
//any harm, so we do not synchronise here. Using a synchronised Map
//should be sufficient.
_seenClasses.put(cl, fields);
return fields;
}
public static final long primitiveToLong(Object raw, PRIMITIVE prim) {
switch (prim) {
case BOOLEAN: return (Boolean)raw ? 1L : 0L;
case BYTE: return (Byte)raw;
case CHAR: return (Character)raw;
case DOUBLE: return BitTools.toSortableLong((Double)raw);
case FLOAT: return BitTools.toSortableLong((Float)raw);
case INT: return (Integer)raw;
case LONG: return (Long)raw;
case SHORT: return (Short)raw;
default:
throw new UnsupportedOperationException(prim.toString());
}
}
public static final long primitiveFieldToLong(Object parent, Field field, PRIMITIVE prim)
throws IllegalArgumentException, IllegalAccessException {
switch (prim) {
case BOOLEAN: return field.getBoolean(parent) ? 1L : 0L;
case BYTE: return field.getByte(parent);
case CHAR: return field.getChar(parent);
case DOUBLE: return BitTools.toSortableLong(field.getDouble(parent));
case FLOAT: return BitTools.toSortableLong(field.getFloat(parent));
case INT: return field.getInt(parent);
case LONG: return field.getLong(parent);
case SHORT: return field.getShort(parent);
default:
throw new UnsupportedOperationException(prim.toString());
}
}
}