/* * XXL: The eXtensible and fleXible Library for data processing * * Copyright (C) 2000-2011 Prof. Dr. Bernhard Seeger Head of the Database Research Group Department * of Mathematics and Computer Science University of Marburg Germany * * This library 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 3 * of the License, or (at your option) any later version. * * This library 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 library; * If not, see <http://www.gnu.org/licenses/>. * * http://code.google.com/p/xxl/ */ package xxl.core.indexStructures; import static xxl.core.util.ConvertUtils.autoCast; import static xxl.core.util.ConvertUtils.autoComparable; import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.math.BigInteger; import java.sql.Date; import java.sql.SQLException; import java.sql.Time; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.List; import java.util.SortedSet; import xxl.core.cursors.Cursor; import xxl.core.indexStructures.BPlusTree.IndexEntry; import xxl.core.indexStructures.BPlusTree.KeyRange; import xxl.core.indexStructures.keyRanges.BooleanKeyRange; import xxl.core.indexStructures.keyRanges.DateKeyRange; import xxl.core.indexStructures.keyRanges.DoubleKeyRange; import xxl.core.indexStructures.keyRanges.FloatKeyRange; import xxl.core.indexStructures.keyRanges.IntegerKeyRange; import xxl.core.indexStructures.keyRanges.LongKeyRange; import xxl.core.indexStructures.keyRanges.ShortKeyRange; import xxl.core.indexStructures.keyRanges.TimeKeyRange; import xxl.core.indexStructures.keyRanges.TimestampKeyRange; import xxl.core.indexStructures.keyRanges.TupleKeyRangeFunction; import xxl.core.indexStructures.builder.BPlusTree.BPlusConfiguration; import xxl.core.indexStructures.builder.BPlusTree.BPlusTreeBuilder; import xxl.core.indexStructures.builder.BPlusTree.ManagedType; import xxl.core.indexStructures.builder.BPlusTree.PrimitiveType; import xxl.core.indexStructures.builder.BPlusTree.TupleType; import xxl.core.io.converters.BooleanConverter; import xxl.core.io.converters.DateConverter; import xxl.core.io.converters.DoubleConverter; import xxl.core.io.converters.FloatConverter; import xxl.core.io.converters.IntegerConverter; import xxl.core.io.converters.LongConverter; import xxl.core.io.converters.ShortConverter; import xxl.core.io.converters.TimeConverter; import xxl.core.io.converters.TimestampConverter; import xxl.core.io.propertyList.Property; import xxl.core.io.propertyList.PropertyList; import xxl.core.io.propertyList.json.JSONPrinter; import xxl.core.relational.tuples.Tuple; /** * An implementation of the {@link IndexedSet} super class to use sets which are indexed by a * {@link BPlusTree}. * * @author Marcus Pinnecke (pinnecke@mathematik.uni-marburg.de) * * @param <E> The data type which should be managed */ public class BPlusIndexedSet<E> extends IndexedSet<BPlusTree, E> { public static final String BPLUS_TUPLE_MET_EXTENSION = ".met2"; /* * File extension for meta data (table name, content type) and reloading BPlusTree meta data in * JSON-Format for tuple data (index entry, key range) */ public static final String META_FILE_EXTENSION = ".meta"; /* * The count of items inside the indexed set */ private BigInteger mSize = BigInteger.ZERO; /** * Sets up a new instance of <code>BPlusIndexedSet</code> with an instance of * <code>BPlusTree</code> and it's builder object. The builder instance contains different * <i>meta</i> data for the index, e.g. the file path, the data type, containers and others. * * @param tree An instance of an {@link BPlusTree} * @param creator The {@link BPlusTreeBuilder} which created <code>tree</code> */ public BPlusIndexedSet(BPlusTree tree, BPlusTreeBuilder creator) { super(tree, creator); } @Override public boolean add(E e) { try { if (((BPlusConfiguration) mCreator.getIndexConfiguration()) .getManagedType().getContentClass() == ManagedType.ContentClass.CONTENT_CLASS_COMPLEX) { Tuple tuple = (e instanceof Entry) ? ((Entry) e).asTuple() : (Tuple) e; int columnCount = ((BPlusConfiguration) mCreator.getIndexConfiguration()) .getManagedType().getMetaData().getColumnCount(); if (tuple.toArray().length != columnCount) throw new IllegalArgumentException( "Item to add does not match the tables schemas (" + e + "). Expected " + columnCount + " columns but " + tuple.toArray().length + " was given."); } if (contains(e)) return false; if (!mTree.contains((e instanceof Entry) ? ((Entry) e).asTuple() : e)) mTree.insert((e instanceof Entry) ? ((Entry) e).asTuple() : e); mSize = mSize.add(BigInteger.ONE); return true; } catch (IllegalArgumentException | SQLException ex) { return false; } } @Override public boolean addAll(Collection<? extends E> c) { boolean result = true; for (E item : c) result &= add(item); return result; } @Override public void clear() { mTree.clear(); } @Override public Comparator<? super E> comparator() { // TODO: Should a comparator be available? throw new UnsupportedOperationException("Not implemented yet"); } @SuppressWarnings("unchecked") @Override public boolean contains(Object o) { return mTree.contains(autoComparable(autoCast(o), mCreator)); } @Override public boolean containsAll(Collection<?> c) { boolean result = true; for (Object item : c) { result &= contains(item); if (!result) return false; } return result; } @Override public E first() { return (E) ((KeyRange) mTree.rootDescriptor()).minBound(); } @Override public SortedSet<E> headSet(E toElement) { return subSetWithBoundOption(first(), toElement, true); } @Override public boolean isEmpty() { return mSize.equals(BigInteger.ZERO); } @Override public E last() { return (E) ((KeyRange) mTree.rootDescriptor()).maxBound(); } private Comparable max(Comparable a, Comparable b) { return (a.compareTo(b) >= 0) ? a : b; } @Override public boolean remove(Object o) { Object tupleToRemove = o; if (o instanceof Tuple) { tupleToRemove = (Tuple) o; } else if (o instanceof Entry) { tupleToRemove = ((Entry) o).asTuple(); } else if (o instanceof Entry.WithKey) { // Delete each entity which has this key throw new IllegalArgumentException( "Deleting entries only restriction by a given key (subset) is too dangerous."); } if (tupleToRemove instanceof Tuple || tupleToRemove instanceof Entry) { try { if (((tupleToRemove instanceof Tuple) ? (Tuple) tupleToRemove : ((Entry) tupleToRemove).asTuple()).toArray().length != ((BPlusConfiguration) mCreator .getIndexConfiguration()).getManagedType().getMetaData() .getColumnCount()) throw new IllegalArgumentException("The column count of entry \"" + o + "\" does not match the tables column count."); } catch (SQLException _) {} } Object removedObject = mTree.remove(tupleToRemove); if (removedObject != null) { mSize = mSize.subtract(BigInteger.ONE); if (mSize.compareTo(BigInteger.ZERO) == -1) throw new IndexOutOfBoundsException("Set size is less than zero."); return true; } else return false; } @Override public boolean removeAll(Collection<?> c) { boolean setChanged = false; for (Object o : c) setChanged &= remove(o); return setChanged; } @Override public boolean retainAll(Collection<?> c) { boolean setChanged = false; Comparable minBound = ((KeyRange) mTree.rootDescriptor()).minBound(); Comparable maxBound = ((KeyRange) mTree.rootDescriptor()).maxBound(); Cursor cursor = mTree.rangeQuery(minBound, maxBound); while (cursor.hasNext()) { Object o = cursor.next(); if(!c.contains(o)) { cursor.remove(); setChanged = true; } } return setChanged; } /* * Stores the index structure meta data (index entry, key range etc.). This is an implementation * of the super class methods and handles the concrete saving process for the BPlusTree */ @Override protected void saveIndexStructureMetaData() throws IOException { /* * BPlusTree required meta file (see BPlusTree documentation) */ File filePath = new File(this.getFilePath() + META_FILE_EXTENSION); /* * Saving the BPlus root entry, level information, root entry ID independent of the managed typ * to the meta file */ DataOutputStream out = new DataOutputStream(new FileOutputStream(filePath)); IndexEntry rootEntry = (IndexEntry) ((BPlusTree) mTree).rootEntry(); int level = rootEntry.parentLevel(); long rootId = (Long) rootEntry.id(); /* * Save level at first position */ IntegerConverter.DEFAULT_INSTANCE.writeInt(out, level); BPlusConfiguration config = (BPlusConfiguration) mCreator.getIndexConfiguration(); ManagedType managedType = config.getManagedType(); /* * Depending on the data type which is managed by the tree, the data type for the minKey, maxKey * etc. is different. The following lines of code stores the information minKey, maxKey, * rootKey, rootId depending on the data type. */ if (managedType instanceof PrimitiveType) { /* * Storing min and max bound of primitive types which are managed by the three */ PrimitiveType primitiveManaged = (PrimitiveType) managedType; switch (primitiveManaged.getContentClassSubType()) { case BOOLEAN: { boolean minKey = (Boolean) ((BooleanKeyRange) mTree.rootDescriptor()).minBound(); boolean maxKey = (Boolean) ((BooleanKeyRange) mTree.rootDescriptor()).maxBound(); boolean rootKey = (Boolean) rootEntry.separator().sepValue(); /* * Save rootKey at second position */ BooleanConverter.DEFAULT_INSTANCE.writeBoolean(out, rootKey); /* * Save rootID at third position */ LongConverter.DEFAULT_INSTANCE.writeLong(out, rootId); /* * Store min-/max bound at fourth position */ BooleanConverter.DEFAULT_INSTANCE.writeBoolean(out, minKey); BooleanConverter.DEFAULT_INSTANCE.writeBoolean(out, maxKey); } break; case DATE: { Date minKey = new Date( (Long) ((DateKeyRange) mTree.rootDescriptor()).minBound()); Date maxKey = new Date( (Long) ((DateKeyRange) mTree.rootDescriptor()).maxBound()); Date rootKey = new Date((Long) rootEntry.separator().sepValue()); /* * Save rootKey at second position */ DateConverter.DEFAULT_INSTANCE.write(out, rootKey); /* * Save rootID at third position */ LongConverter.DEFAULT_INSTANCE.writeLong(out, rootId); /* * Store min-/max bound at fourth position */ DateConverter.DEFAULT_INSTANCE.write(out, minKey); DateConverter.DEFAULT_INSTANCE.write(out, maxKey); } break; case DOUBLE: { double minKey = (Double) ((DoubleKeyRange) mTree.rootDescriptor()).minBound(); double maxKey = (Double) ((DoubleKeyRange) mTree.rootDescriptor()).maxBound(); double rootKey = (Double) rootEntry.separator().sepValue(); /* * Save rootKey at second position */ DoubleConverter.DEFAULT_INSTANCE.writeDouble(out, rootKey); /* * Save rootID at third position */ LongConverter.DEFAULT_INSTANCE.writeLong(out, rootId); /* * Store min-/max bound at fourth position */ DoubleConverter.DEFAULT_INSTANCE.writeDouble(out, minKey); DoubleConverter.DEFAULT_INSTANCE.writeDouble(out, maxKey); } break; case FLOAT: { float minKey = (Float) ((FloatKeyRange) mTree.rootDescriptor()).minBound(); float maxKey = (Float) ((FloatKeyRange) mTree.rootDescriptor()).maxBound(); float rootKey = (Float) rootEntry.separator().sepValue(); /* * Save rootKey at second position */ FloatConverter.DEFAULT_INSTANCE.write(out, rootKey); /* * Save rootID at third position */ LongConverter.DEFAULT_INSTANCE.writeLong(out, rootId); /* * Store min-/max bound at fourth position */ FloatConverter.DEFAULT_INSTANCE.write(out, minKey); FloatConverter.DEFAULT_INSTANCE.write(out, maxKey); } break; case INT: { int minKey = (Integer) ((IntegerKeyRange) mTree.rootDescriptor()).minBound(); int maxKey = (Integer) ((IntegerKeyRange) mTree.rootDescriptor()).maxBound(); int rootKey = (Integer) rootEntry.separator().sepValue(); /* * Save rootKey at second position */ IntegerConverter.DEFAULT_INSTANCE.writeInt(out, rootKey); /* * Save rootID at third position */ LongConverter.DEFAULT_INSTANCE.writeLong(out, rootId); /* * Store min-/max bound at fourth position */ IntegerConverter.DEFAULT_INSTANCE.writeInt(out, minKey); IntegerConverter.DEFAULT_INSTANCE.writeInt(out, maxKey); } break; case LONG: { long minKey = (Long) ((LongKeyRange) mTree.rootDescriptor()).minBound(); long maxKey = (Long) ((LongKeyRange) mTree.rootDescriptor()).maxBound(); long rootKey = (Long) rootEntry.separator().sepValue(); /* * Save rootKey at second position */ LongConverter.DEFAULT_INSTANCE.writeLong(out, rootKey); /* * Save rootID at third position */ LongConverter.DEFAULT_INSTANCE.writeLong(out, rootId); /* * Store min-/max bound at fourth position */ LongConverter.DEFAULT_INSTANCE.writeLong(out, minKey); LongConverter.DEFAULT_INSTANCE.writeLong(out, maxKey); } break; case SHORT: { short minKey = (Short) ((ShortKeyRange) mTree.rootDescriptor()).minBound(); short maxKey = (Short) ((ShortKeyRange) mTree.rootDescriptor()).maxBound(); short rootKey = (Short) rootEntry.separator().sepValue(); /* * Save rootKey at second position */ ShortConverter.DEFAULT_INSTANCE.writeShort(out, rootKey); /* * Save rootID at third position */ LongConverter.DEFAULT_INSTANCE.writeLong(out, rootId); /* * Store min-/max bound at fourth position */ ShortConverter.DEFAULT_INSTANCE.writeShort(out, minKey); ShortConverter.DEFAULT_INSTANCE.writeShort(out, maxKey); } break; case TIMESTAMP: { Timestamp minKey = new Timestamp( (Long) ((TimestampKeyRange) mTree.rootDescriptor()) .minBound()); Timestamp maxKey = new Timestamp( (Long) ((TimestampKeyRange) mTree.rootDescriptor()) .maxBound()); Timestamp rootKey = new Timestamp((Long) rootEntry.separator().sepValue()); /* * Save rootKey at second position */ TimestampConverter.DEFAULT_INSTANCE.write(out, rootKey); /* * Save rootID at third position */ LongConverter.DEFAULT_INSTANCE.writeLong(out, rootId); /* * Store min-/max bound at fourth position */ TimestampConverter.DEFAULT_INSTANCE.write(out, minKey); TimestampConverter.DEFAULT_INSTANCE.write(out, maxKey); } break; case TIME: { Time minKey = new Time( (Long) ((TimeKeyRange) mTree.rootDescriptor()).minBound()); Time maxKey = new Time( (Long) ((TimeKeyRange) mTree.rootDescriptor()).maxBound()); Time rootKey = new Time((Long) rootEntry.separator().sepValue()); /* * Save rootKey at second position */ TimeConverter.DEFAULT_INSTANCE.write(out, rootKey); /* * Save rootID at third position */ LongConverter.DEFAULT_INSTANCE.writeLong(out, rootId); /* * Store min-/max bound at fourth position */ TimeConverter.DEFAULT_INSTANCE.write(out, minKey); TimeConverter.DEFAULT_INSTANCE.write(out, maxKey); } break; default: throw new UnsupportedOperationException( "Storing data not implemented yet for type \"" + primitiveManaged.getContentClass() + "\""); } } else if (managedType instanceof TupleType) { /* * Storing min and max bound of tuple types which are managed by the three */ PropertyList tupleTableDescription = new PropertyList(); tupleTableDescription.add(new Property("RootKey", ((Tuple) rootEntry .separator().sepValue()).toArray())); tupleTableDescription.add(new Property("RootID", rootId)); tupleTableDescription.add(new Property("MinKey", ((Tuple) ((TupleKeyRangeFunction) mTree.rootDescriptor()).minBound()) .toArray())); tupleTableDescription.add(new Property("MaxKey", ((Tuple) ((TupleKeyRangeFunction) mTree.rootDescriptor()).maxBound()) .toArray())); JSONPrinter printer = new JSONPrinter(tupleTableDescription); File output = new File(config.getFileSystemFilePath() + BPLUS_TUPLE_MET_EXTENSION); if (output.exists()) output.delete(); FileOutputStream fso = new FileOutputStream(output); printer.print(fso); } else throw new UnsupportedOperationException( "Exception while saving the set content. Unknown managed type \"" + managedType.getContentType() + "\"."); } @Override public int size() { return mSize.intValue(); } @Override public BigInteger sizeBigInteger() { return mSize; } @Override public SortedSet<E> subSet(E fromElement, E toElement) { return subSetWithBoundOption(fromElement, toElement, true); } /* * To avoid code duplicates the process of sub setting is done with this methods. Both types, * inclusive or exclusive the upper bound, are needed to satisfy the Java SortedSet specification. * The flag <code>excludeToElement</code> controls this. */ private SortedSet<E> subSetWithBoundOption(E fromElement, E toElement, boolean excludeToElement) { if (fromElement == null || (toElement == null)) throw new NullPointerException("Subset bound have to be non-null"); if ((autoComparable(autoCast(fromElement), mCreator)) .compareTo(autoComparable(autoCast(toElement), mCreator)) > 0) throw new IllegalArgumentException("Predicate from " + fromElement + " > " + " to " + toElement + " is illegal"); Comparable naturalMinBound = ((KeyRange) mTree.rootDescriptor()).minBound(); Comparable naturalMaxBound = ((KeyRange) mTree.rootDescriptor()).maxBound(); if ((autoComparable(fromElement, mCreator)).compareTo(naturalMinBound) < 0) throw new IndexOutOfBoundsException( "fromElement is lesser than the content's minimum"); if ((autoComparable(fromElement, mCreator)).compareTo(naturalMaxBound) > 0) throw new IndexOutOfBoundsException( "fromElement is greater than the content's maximum"); if ((autoComparable(toElement, mCreator)).compareTo(naturalMinBound) < 0) throw new IndexOutOfBoundsException( "toElement is lesser than the content's minimum"); if ((autoComparable(toElement, mCreator)).compareTo(naturalMaxBound) > 0) throw new IndexOutOfBoundsException( "toElement is greater than the content's maximum"); System.err.println(fromElement); return new BPlusIndexedSetView(this, autoComparable(fromElement, mCreator), autoComparable(toElement, mCreator), excludeToElement); } @Override public SortedSet<E> tailSet(E fromElement) { return subSetWithBoundOption(fromElement, last(), false); } @Override public Object[] toArray() { Comparable minBound = ((KeyRange) mTree.rootDescriptor()).minBound(); Comparable maxBound = ((KeyRange) mTree.rootDescriptor()).maxBound(); Cursor allEntries = mTree.query(); List<Object> result = new ArrayList<>(); while (allEntries.hasNext()) { Object entry = allEntries.next(); Comparable item = autoComparable(autoCast(entry), mCreator);// (Comparable) allEntries.next(); if (item.compareTo(minBound) >= 0 && item.compareTo(maxBound) <= 0) { if (entry instanceof Tuple) { // In case of relational managing tree, convert it to an array of arrays. result.add(((Tuple) entry).toArray()); } else { result.add((Object) item); } } } return result.toArray(); } @Override public <T> T[] toArray(T[] a) { Comparable minBound = ((KeyRange) mTree.rootDescriptor()).minBound(); Comparable maxBound = ((KeyRange) mTree.rootDescriptor()).maxBound(); Cursor allEntries = mTree.query(); List<T> result = new ArrayList<>(); while (allEntries.hasNext()) { Comparable item = (Comparable) allEntries.next(); if (item.compareTo(minBound) >= 0 && item.compareTo(maxBound) <= 0) result.add((T) item); } return result.toArray(a); } public String toString() { if (((BPlusConfiguration) mCreator.getIndexConfiguration()) .getManagedType().getContentClass() == ManagedType.ContentClass.CONTENT_CLASS_PRIMITIVE) { return java.util.Arrays.toString(toArray()); } else { StringBuilder result = new StringBuilder(); Object[] rows = toArray(); if (rows.length == 0) result.append("[]"); for (int i = 0; i < rows.length; ++i) { result.append("["); Object[] columns = (Object[]) rows[i]; for (int j = 0; j < columns.length; ++j) { result.append(columns[j]); if (j < columns.length - 1) result.append(", "); } result.append("]"); if (i < rows.length - 1) result.append("\n"); } return result.toString(); } } }