/* * ModeShape (http://www.modeshape.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.modeshape.jcr.index.local; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.io.Serializable; import java.math.BigDecimal; import java.net.URI; import java.util.Comparator; import java.util.HashMap; import java.util.Map; import org.mapdb.BTreeKeySerializer; import org.mapdb.DataInput2; import org.mapdb.DataOutput2; import org.mapdb.Fun; import org.mapdb.Fun.Tuple2; import org.mapdb.Serializer; import org.modeshape.common.util.ObjectUtil; import org.modeshape.jcr.api.value.DateTime; import org.modeshape.jcr.cache.NodeKey; import org.modeshape.jcr.value.Name; import org.modeshape.jcr.value.Path; import org.modeshape.jcr.value.Reference; import org.modeshape.jcr.value.ValueFactories; /** * @author Randall Hauch (rhauch@redhat.com) */ public class MapDB { public static interface Serializers { /** * Obtain a serializer for the given value type. * * @param type the type; may not be null * @return the serializer */ Serializer<?> serializerFor( Class<?> type ); /** * Obtain a serializer for the given key type. * * @param type the type; may not be null * @param comparator the comparator; may not be null * @param pack true if the serializer can/should pack keys together when possible, or false otherwise * @return the serializer */ BTreeKeySerializer<?> bTreeKeySerializerFor( Class<?> type, Comparator<?> comparator, boolean pack ); /** * Obtain a serializer for the given value type that can handle nulls. * * @param type the type; may not be null * @return the serializer that can handle nulls */ Serializer<?> nullSafeSerializerFor( Class<?> type ); } public static Serializers serializers( ValueFactories factories ) { return new SerializerSupplier(factories); } public final static Serializer<NodeKey> NODE_KEY_SERIALIZER = new NodeKeySerializer(); protected final static Serializer<?> DEFAULT_SERIALIZER = Serializer.BASIC; protected final static BTreeKeySerializer<?> DEFAULT_BTREE_KEY_SERIALIZER = BTreeKeySerializer.BASIC; public static final class SerializerSupplier implements Serializers { private final Map<Class<?>, Serializer<?>> serializersByClass; private final Map<Class<?>, Serializer<?>> nullSafeSerializersByClass; private final Map<Class<?>, BTreeKeySerializer<?>> bTreeKeySerializersByClass; private final Serializer<?> DEFAULT_NULL_SAFE_SERIALIZER; protected SerializerSupplier( ValueFactories factories ) { DEFAULT_NULL_SAFE_SERIALIZER = (isNullSafe(DEFAULT_SERIALIZER) ? DEFAULT_SERIALIZER : new NullSafeSerializer<>(DEFAULT_SERIALIZER)); // Create the serializers ... serializersByClass = new HashMap<Class<?>, Serializer<?>>(); serializersByClass.put(String.class, Serializer.STRING); serializersByClass.put(Long.class, Serializer.LONG); serializersByClass.put(Boolean.class, Serializer.BOOLEAN); serializersByClass.put(Double.class, new DoubleSerializer()); serializersByClass.put(BigDecimal.class, Serializer.JAVA); serializersByClass.put(URI.class, Serializer.JAVA); serializersByClass.put(DateTime.class, Serializer.JAVA); serializersByClass.put(Path.class, Serializer.JAVA); serializersByClass.put(Name.class, Serializer.JAVA); serializersByClass.put(Reference.class, Serializer.JAVA); serializersByClass.put(NodeKey.class, NODE_KEY_SERIALIZER); bTreeKeySerializersByClass = new HashMap<Class<?>, BTreeKeySerializer<?>>(); nullSafeSerializersByClass = new HashMap<Class<?>, Serializer<?>>(); for (Map.Entry<Class<?>, Serializer<?>> entry : serializersByClass.entrySet()) { Serializer<?> serializer = entry.getValue(); @SuppressWarnings( {"rawtypes", "unchecked"} ) BTreeKeySerializer<?> bTreeSerializer = new DelegatingKeySerializer(serializer); bTreeKeySerializersByClass.put(entry.getKey(), bTreeSerializer); Serializer<?> nullsafe = (isNullSafe(serializer) ? serializer : new NullSafeSerializer<>(serializer)); nullSafeSerializersByClass.put(entry.getKey(), nullsafe); } } private static <T> boolean isNullSafe(Serializer<T> serializer) { try { serializer.serialize(new DataOutput2(), null); return true; } catch(IOException | NullPointerException e) { return false; } } @Override public Serializer<?> serializerFor( Class<?> type ) { Serializer<?> result = serializersByClass.get(type); if (result != null) return result; return DEFAULT_SERIALIZER; } public Serializer<?> nullSafeSerializerFor( Class<?> type ) { Serializer<?> result = nullSafeSerializersByClass.get(type); if (result != null) return result; return DEFAULT_NULL_SAFE_SERIALIZER; } @Override public BTreeKeySerializer<?> bTreeKeySerializerFor( Class<?> type, final Comparator<?> comparator, boolean pack ) { Map<Class<?>, BTreeKeySerializer<?>> byClass = bTreeKeySerializersByClass; final BTreeKeySerializer<?> result = byClass.containsKey(type) ? byClass.get(type) : DEFAULT_BTREE_KEY_SERIALIZER; // Make sure the serializer uses the type's comparator ... if (result instanceof KeySerializerWithComparator) { KeySerializerWithComparator<?> serializer = (KeySerializerWithComparator<?>)result; assert comparator != null; return serializer.withComparator(comparator); } return bTreeKeySerializerWith(result, comparator); } private <T> BTreeKeySerializer<T> bTreeKeySerializerWith( final BTreeKeySerializer<?> original, final Comparator<T> comparator ) { return new BTreeKeySerializerWitheComparator<T>(original, comparator); } private static class NullSafeSerializer<T> implements Serializer<T>, Serializable { private Serializer<T> baseSerializer; public NullSafeSerializer(Serializer<T> baseSerializer) { this.baseSerializer = baseSerializer; } @Override public void serialize(DataOutput dataOutput, T t) throws IOException { if (t == null) { dataOutput.writeBoolean(false); } else { dataOutput.writeBoolean(true); baseSerializer.serialize(dataOutput, t); } } @Override public T deserialize(DataInput dataInput, int i) throws IOException { if (!dataInput.readBoolean()) { return null; } else { return baseSerializer.deserialize(dataInput, i); } } @Override public int fixedSize() { return -1; } } } /** * NodeKey serializer for MapDB (which must be in turn, Serializable) */ private static class NodeKeySerializer implements Serializer<NodeKey>, Serializable { private static final long serialVersionUID = 1L; protected NodeKeySerializer() { } @Override public void serialize( DataOutput out, NodeKey value ) throws IOException { out.writeUTF(value.toString()); } @Override public NodeKey deserialize( DataInput in, int available ) throws IOException { String keyStr = in.readUTF(); return new NodeKey(keyStr); } @Override public boolean equals( Object obj ) { if (obj == this) return true; return obj instanceof NodeKeySerializer; } @Override public int hashCode() { return 1; } @Override public int fixedSize() { return -1; // not fixed size } } private static class BTreeKeySerializerWitheComparator<T> extends BTreeKeySerializer<T> implements Serializable { private static final long serialVersionUID = 1L; private final BTreeKeySerializer<?> original; private final Comparator<T> comparator; protected BTreeKeySerializerWitheComparator( BTreeKeySerializer<?> original, Comparator<T> comparator ) { this.original = original; this.comparator = comparator; } @Override public Object[] deserialize( DataInput in, int start, int end, int size ) throws IOException { return original.deserialize(in, start, end, size); } @Override public Comparator<T> getComparator() { return comparator; } @Override public void serialize( DataOutput out, int start, int end, Object[] keys ) throws IOException { original.serialize(out, start, end, keys); } @Override public boolean equals( Object obj ) { if (obj == this) return true; if (obj instanceof BTreeKeySerializerWitheComparator) { @SuppressWarnings( "unchecked" ) BTreeKeySerializerWitheComparator<T> that = (BTreeKeySerializerWitheComparator<T>)obj; return original.equals(that.original) && comparator.equals(comparator); } return false; } @Override public int hashCode() { return 1; } } public static <T> BTreeKeySerializer<UniqueKey<T>> uniqueKeyBTreeSerializer( Serializer<T> serializer, Comparator<T> comparator ) { return new UniqueKeyBTreeSerializer<T>(serializer, uniqueKeyComparator(comparator)); } public static <T> Serializer<UniqueKey<T>> uniqueKeySerializer( Serializer<T> serializer, Comparator<T> comparator ) { return new UniqueKeySerializer<T>(serializer, uniqueKeyComparator(comparator)); } public static <T> Comparator<UniqueKey<T>> uniqueKeyComparator( Comparator<T> comparator ) { return new UniqueKeyComparator<T>(comparator); } public static <A, B> Comparator<Fun.Tuple2<A, B>> tupleComparator( Comparator<A> aComparator, Comparator<B> bComparator ) { return new TupleComparator<A, B>(aComparator, bComparator); } public static <A, B> BTreeKeySerializer<Fun.Tuple2<A, B>> tupleBTreeSerializer( Comparator<A> aComparator, Serializer<A> aSerializer, Serializer<B> bSerializer, Comparator<Fun.Tuple2<A, B>> tupleComparator ) { return new LocalTuple2KeySerializer<>(aComparator, aSerializer, bSerializer, tupleComparator); } public static final class UniqueKey<K> implements Serializable { private static final long serialVersionUID = 1L; protected final K actualKey; protected final long id; private final int hc; public UniqueKey( K actualKey, long id ) { this.actualKey = actualKey; this.id = id; this.hc = actualKey != null ? actualKey.hashCode() : 0; } @Override public int hashCode() { return hc; } @Override public boolean equals( Object obj ) { if (this == obj) return true; if (obj instanceof UniqueKey) { @SuppressWarnings( "unchecked" ) UniqueKey<K> that = (UniqueKey<K>)obj; if (this.actualKey == null && that.actualKey != null) return false; if (!this.actualKey.equals(that.actualKey)) return false; return this.id == that.id; } return false; } @Override public String toString() { return "[" + actualKey + "," + id + "]"; } } public static final class UniqueKeyComparator<K> implements Comparator<UniqueKey<K>>, Serializable { private static final long serialVersionUID = 1L; private final Comparator<K> valueComparator; public UniqueKeyComparator( Comparator<K> valueComparator ) { this.valueComparator = valueComparator; } @Override public int compare( UniqueKey<K> o1, UniqueKey<K> o2 ) { if (o1 == o2) return 0; if (o1 == null) { return o2 == null ? 0 : 1; } else if (o2 == null) { return -1; } int diff = valueComparator.compare(o1.actualKey, o2.actualKey); if (diff != 0) return diff; long ldiff = o1.id - o2.id; return ldiff == 0L ? 0 : (ldiff <= 0L ? -1 : 1); } @Override public boolean equals( Object obj ) { if (obj == this) return true; if (obj instanceof UniqueKeyComparator) { @SuppressWarnings( "unchecked" ) UniqueKeyComparator<K> that = (UniqueKeyComparator<K>)obj; return valueComparator.equals(that.valueComparator); } return false; } @Override public int hashCode() { return 1; } } public static final class ComparableUniqueKeyComparator<K> implements Comparator<UniqueKey<K>>, Serializable { private static final long serialVersionUID = 1L; @Override public int compare( UniqueKey<K> o1, UniqueKey<K> o2 ) { if (o1 == o2) return 0; int diff = ObjectUtil.compareWithNulls((Comparable<?>)o1.actualKey, (Comparable<?>)o2.actualKey); if (diff != 0) return diff; long ldiff = o1.id - o2.id; return ldiff == 0L ? 0 : (ldiff <= 0L ? -1 : 1); } @Override public boolean equals( Object obj ) { if (obj == this) return true; if (obj instanceof ComparableUniqueKeyComparator) { return true; } return false; } @Override public int hashCode() { return 1; } } public static final class UniqueKeySerializer<K> implements Serializer<UniqueKey<K>>, Serializable { private static final long serialVersionUID = 1L; protected final Serializer<K> keySerializer; protected final Comparator<UniqueKey<K>> comparator; public UniqueKeySerializer( Serializer<K> keySerializer, Comparator<UniqueKey<K>> comparator ) { this.keySerializer = keySerializer; this.comparator = comparator; } @Override public UniqueKey<K> deserialize( DataInput in, int available ) throws IOException { K actualKey = keySerializer.deserialize(in, available); long id = in.readLong(); return new UniqueKey<K>(actualKey, id); } @Override public void serialize( DataOutput out, UniqueKey<K> value ) throws IOException { keySerializer.serialize(out, value.actualKey); out.writeLong(value.id); } @Override public int fixedSize() { return -1; } @Override public boolean equals( Object obj ) { if (obj == this) return true; if (obj instanceof UniqueKeySerializer) { @SuppressWarnings( "unchecked" ) UniqueKeySerializer<K> that = (UniqueKeySerializer<K>)obj; return keySerializer.equals(that.keySerializer) && comparator.equals(that.comparator); } return false; } @Override public int hashCode() { return 1; } @Override public String toString() { return "UniqueKeySerializer<" + keySerializer + ">"; } } public static final class UniqueKeyBTreeSerializer<K> extends BTreeKeySerializer<UniqueKey<K>> implements Serializable { private static final long serialVersionUID = 1L; protected final Serializer<K> keySerializer; protected final Comparator<UniqueKey<K>> comparator; public UniqueKeyBTreeSerializer( Serializer<K> keySerializer, Comparator<UniqueKey<K>> comparator ) { this.keySerializer = keySerializer; this.comparator = comparator; } @Override public Comparator<UniqueKey<K>> getComparator() { return comparator; } @SuppressWarnings( "unchecked" ) @Override public void serialize( DataOutput out, int start, int end, Object[] keys ) throws IOException { for (int i = start; i < end; i++) { UniqueKey<K> key = (UniqueKey<K>)keys[i]; keySerializer.serialize(out, key.actualKey); out.writeLong(key.id); } } @Override public Object[] deserialize( DataInput in, int start, int end, int size ) throws IOException { Object[] ret = new Object[size]; for (int i = start; i < end; i++) { K key = keySerializer.deserialize(in, -1); long id = in.readLong(); ret[i] = new UniqueKey<K>(key, id); } return ret; } @Override public boolean equals( Object obj ) { if (obj == this) return true; if (obj instanceof UniqueKeyBTreeSerializer) { @SuppressWarnings( "unchecked" ) UniqueKeyBTreeSerializer<K> that = (UniqueKeyBTreeSerializer<K>)obj; return keySerializer.equals(that.keySerializer) && comparator.equals(that.comparator); } return false; } @Override public int hashCode() { return 1; } @Override public String toString() { return "UniqueKeyBTreeSerializer<" + keySerializer + ">"; } } public static class NaturalComparator<K extends Comparable<K>> implements Comparator<K>, Serializable { private static final long serialVersionUID = 1L; @Override public int compare( K o1, K o2 ) { return o1.compareTo(o2); } @Override public boolean equals( Object obj ) { if (obj == this) return true; if (obj instanceof NaturalComparator) { return true; } return false; } @Override public int hashCode() { return 1; } } /** * A key serializer that just writes data without applying any compression. * * @param <K> the type to be serialized */ public static final class DelegatingKeySerializer<K extends Comparable<K>> extends BTreeKeySerializer<K> implements Serializable, KeySerializerWithComparator<K> { private static final long serialVersionUID = 1L; protected final Serializer<K> defaultSerializer; protected final Comparator<K> comparator; public DelegatingKeySerializer( Serializer<K> defaultSerializer ) { this(defaultSerializer, null); } public DelegatingKeySerializer( Serializer<K> defaultSerializer, Comparator<K> comparator ) { this.defaultSerializer = defaultSerializer; this.comparator = comparator != null ? comparator : new NaturalComparator<K>(); } @Override public Comparator<K> getComparator() { return comparator; } @SuppressWarnings( "unchecked" ) @Override public BTreeKeySerializer<K> withComparator( Comparator<?> comparator ) { if (comparator == null) return this; return new DelegatingKeySerializer<K>(defaultSerializer, (Comparator<K>)comparator); } @SuppressWarnings( "unchecked" ) @Override public void serialize( DataOutput out, int start, int end, Object[] keys ) throws IOException { for (int i = start; i < end; i++) { defaultSerializer.serialize(out, (K)keys[i]); } } @Override public Object[] deserialize( DataInput in, int start, int end, int size ) throws IOException { Object[] ret = new Object[size]; for (int i = start; i < end; i++) { ret[i] = defaultSerializer.deserialize(in, -1); } return ret; } @Override public boolean equals( Object obj ) { if (obj == this) return true; if (obj instanceof DelegatingKeySerializer) { @SuppressWarnings( "unchecked" ) DelegatingKeySerializer<K> that = (DelegatingKeySerializer<K>)obj; return defaultSerializer.equals(that.defaultSerializer) && comparator.equals(that.comparator); } return false; } @Override public int hashCode() { return 1; } @Override public String toString() { return "DelegatingBTreeSerializer<" + defaultSerializer + ">"; } } public static interface KeySerializerWithComparator<K> { BTreeKeySerializer<K> withComparator( Comparator<?> comparator ); } public static class DoubleSerializer implements Serializer<Double>, Serializable { private static final long serialVersionUID = 1L; @Override public void serialize( DataOutput out, Double value ) throws IOException { out.writeDouble(value.doubleValue()); } @Override public Double deserialize( DataInput in, int available ) throws IOException { if (available == 0) return null; return in.readDouble(); } @Override public boolean equals( Object obj ) { if (obj == this) return true; if (obj instanceof DoubleSerializer) { return true; } return false; } @Override public int hashCode() { return 1; } @Override public int fixedSize() { return -1; } } /** * Applies delta compression on array of tuple. First tuple value may be shared between consequentive tuples, so only first * occurrence is serialized. An example: * * <pre> * Value Serialized as * ------------------------- * Tuple(1, 1) 1, 1 * Tuple(1, 2) 2 * Tuple(1, 3) 3 * Tuple(1, 4) 4 * </pre> * * @param <A> first tuple value * @param <B> second tuple value */ @SuppressWarnings( "unchecked" ) protected final static class LocalTuple2KeySerializer<A, B> extends BTreeKeySerializer<Fun.Tuple2<A, B>> implements Serializable { private static final long serialVersionUID = 0L; protected final Comparator<A> aComparator; protected final Serializer<A> aSerializer; protected final Serializer<B> bSerializer; protected final Comparator<Fun.Tuple2<A, B>> comparator; /** * Construct new Tuple2 Key Serializer. You may pass null for some value, In that case 'default' value will be used, * Comparable comparator and Default Serializer from DB. * * @param aComparator comparator used for first tuple value * @param aSerializer serializer used for first tuple value * @param bSerializer serializer used for second tuple value * @param comparator the comparator for the tuple */ public LocalTuple2KeySerializer( Comparator<A> aComparator, Serializer<A> aSerializer, Serializer<B> bSerializer, Comparator<Fun.Tuple2<A, B>> comparator ) { this.aComparator = aComparator; this.aSerializer = aSerializer; this.bSerializer = bSerializer; this.comparator = comparator; } @Override public void serialize( DataOutput out, int start, int end, Object[] keys ) throws IOException { int acount = 0; for (int i = start; i < end; i++) { Fun.Tuple2<A, B> t = (Fun.Tuple2<A, B>)keys[i]; if (acount == 0) { // write new A aSerializer.serialize(out, t.a); // count how many A are following acount = 1; while (i + acount < end && aComparator.compare(t.a, ((Fun.Tuple2<A, B>)keys[i + acount]).a) == 0) { acount++; } DataOutput2.packInt(out, acount); } bSerializer.serialize(out, t.b); acount--; } } @Override public Object[] deserialize( DataInput in, int start, int end, int size ) throws IOException { Object[] ret = new Object[size]; A a = null; int acount = 0; for (int i = start; i < end; i++) { if (acount == 0) { // read new A a = aSerializer.deserialize(in, -1); acount = DataInput2.unpackInt(in); } B b = bSerializer.deserialize(in, -1); ret[i] = Fun.t2(a, b); acount--; } assert (acount == 0); return ret; } @Override public Comparator<Fun.Tuple2<A, B>> getComparator() { return comparator; } @Override public boolean equals( Object o ) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; LocalTuple2KeySerializer<A, B> t = (LocalTuple2KeySerializer<A, B>)o; return Fun.eq(aComparator, t.aComparator) && Fun.eq(aSerializer, t.aSerializer) && Fun.eq(bSerializer, t.bSerializer); } @Override public int hashCode() { int result = aComparator != null ? aComparator.hashCode() : 0; result = 31 * result + (aSerializer != null ? aSerializer.hashCode() : 0); result = 31 * result + (bSerializer != null ? bSerializer.hashCode() : 0); return result; } } protected final static class TupleComparator<A, B> implements Comparator<Fun.Tuple2<A, B>>, Serializable { private static final long serialVersionUID = 1L; private final Comparator<A> aComparator; private final Comparator<B> bComparator; protected TupleComparator( Comparator<A> aComparator, Comparator<B> bComparator ) { this.aComparator = aComparator; this.bComparator = bComparator; } @Override public int compare( Tuple2<A, B> o1, Tuple2<A, B> o2 ) { int i = aComparator.compare(o1.a, o2.a); if (i != 0) return i; // Before we check the second value in the tuples, check them against the "positive infinity" values ... if (o1.b == null) { // This is negative infinity ... if (o2.b == null) { // Both are negative infinity ... return 0; } // o1.b is negative infinity, but o2.b is not return -1; } else if (o2.b == null) { // This is negative infinity, but o1.b is not ... return 1; } if (o1.b == Fun.HI) { if (o2.b == Fun.HI) { // Both are positive infinity ... return 0; } // o1.b is positive infinity, and o2.b is not ... return 1; } else if (o2.b == Fun.HI) { // o1.b is not positive infinity, and o1.b is ... return -1; } // Neither is positive infinity, so use the actual comparator ... i = bComparator.compare(o1.b, o2.b); return i; } @Override public boolean equals( Object obj ) { if (obj == this) return true; if (obj instanceof TupleComparator) { @SuppressWarnings( "unchecked" ) TupleComparator<A, B> that = (TupleComparator<A, B>)obj; return aComparator.equals(that.aComparator) && bComparator.equals(that.bComparator); } return false; } @Override public int hashCode() { return 1; } } private MapDB() { } }