481 lines
15 KiB
Java
481 lines
15 KiB
Java
/*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership.
|
|
* The ASF licenses this file to You 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.apache.jdbm;
|
|
|
|
|
|
import java.io.*;
|
|
import java.util.*;
|
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
|
|
|
/**
|
|
* LinkedList2 which stores its nodes on disk.
|
|
*
|
|
* @author Jan Kotek
|
|
*/
|
|
class LinkedList2<E> extends AbstractSequentialList<E> {
|
|
|
|
private DBAbstract db;
|
|
|
|
final long rootRecid;
|
|
/** size limit, is not currently used, but needs to be here for future compatibility.
|
|
* Zero means no limit.
|
|
*/
|
|
long sizeLimit = 0;
|
|
|
|
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
|
|
|
|
static final class Root{
|
|
long first;
|
|
long last;
|
|
long size;
|
|
}
|
|
|
|
private static final Serializer<Root> ROOT_SERIALIZER= new Serializer<Root>(){
|
|
|
|
public void serialize(DataOutput out, Root obj) throws IOException {
|
|
LongPacker.packLong(out,obj.first);
|
|
LongPacker.packLong(out,obj.last);
|
|
LongPacker.packLong(out,obj.size);
|
|
}
|
|
|
|
public Root deserialize(DataInput in) throws IOException, ClassNotFoundException {
|
|
Root r = new Root();
|
|
r.first = LongPacker.unpackLong(in);
|
|
r.last = LongPacker.unpackLong(in);
|
|
r.size = LongPacker.unpackLong(in);
|
|
return r;
|
|
}
|
|
};
|
|
|
|
private Serializer<E> valueSerializer;
|
|
|
|
/**
|
|
* indicates that entry values should not be loaded during deserialization, used during defragmentation
|
|
*/
|
|
protected boolean loadValues = true;
|
|
|
|
/** constructor used for deserialization */
|
|
LinkedList2(DBAbstract db,long rootRecid, Serializer<E> valueSerializer) {
|
|
this.db = db;
|
|
this.rootRecid = rootRecid;
|
|
this.valueSerializer = valueSerializer;
|
|
}
|
|
|
|
/** constructor used to create new empty list*/
|
|
LinkedList2(DBAbstract db, Serializer<E> valueSerializer) throws IOException {
|
|
this.db = db;
|
|
if (valueSerializer != null && !(valueSerializer instanceof Serializable))
|
|
throw new IllegalArgumentException("Serializer does not implement Serializable");
|
|
this.valueSerializer = valueSerializer;
|
|
//create root
|
|
this.rootRecid = db.insert(new Root(), ROOT_SERIALIZER,false);
|
|
}
|
|
|
|
void setPersistenceContext(DBAbstract db) {
|
|
this.db = db;
|
|
}
|
|
|
|
|
|
public ListIterator<E> listIterator(int index) {
|
|
lock.readLock().lock();
|
|
try{
|
|
Root r = getRoot();
|
|
if (index < 0 || index > r.size)
|
|
throw new IndexOutOfBoundsException();
|
|
|
|
|
|
Iter iter = new Iter();
|
|
iter.next = r.first;
|
|
|
|
|
|
//scroll to requested position
|
|
//TODO scroll from end, if beyond half
|
|
for (int i = 0; i < index; i++) {
|
|
iter.next();
|
|
}
|
|
return iter;
|
|
}finally {
|
|
lock.readLock().unlock();
|
|
}
|
|
|
|
}
|
|
|
|
Root getRoot(){
|
|
//expect that caller already holds lock
|
|
try {
|
|
return db.fetch(rootRecid,ROOT_SERIALIZER);
|
|
} catch (IOException e) {
|
|
throw new IOError(e);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public int size() {
|
|
lock.readLock().lock();
|
|
try{
|
|
return (int) getRoot().size;
|
|
}finally {
|
|
lock.readLock().unlock();
|
|
}
|
|
|
|
|
|
}
|
|
|
|
public Iterator<E> descendingIterator() {
|
|
return null; //To change body of implemented methods use File | Settings | File Templates.
|
|
}
|
|
|
|
public boolean add(Object value) {
|
|
lock.writeLock().lock();
|
|
try {
|
|
Root r = getRoot();
|
|
Entry e = new Entry(r.last, 0, value);
|
|
long recid = db.insert(e, entrySerializer,false);
|
|
|
|
//update old last Entry to point to new record
|
|
if (r.last != 0) {
|
|
Entry oldLast = db.fetch(r.last, entrySerializer);
|
|
if (oldLast.next != 0) throw new Error();
|
|
oldLast.next = recid;
|
|
db.update(r.last, oldLast, entrySerializer);
|
|
}
|
|
|
|
//update linked list
|
|
r.last = recid;
|
|
if (r.first == 0) r.first = recid;
|
|
r.size++;
|
|
db.update(rootRecid, r, ROOT_SERIALIZER);
|
|
modCount++;
|
|
return true;
|
|
} catch (IOException e) {
|
|
throw new IOError(e);
|
|
}finally {
|
|
lock.writeLock().unlock();
|
|
}
|
|
|
|
|
|
}
|
|
|
|
private Entry<E> fetch(long recid) {
|
|
lock.readLock().lock();
|
|
try {
|
|
return db.fetch(recid, entrySerializer);
|
|
} catch (IOException e) {
|
|
throw new IOError(e);
|
|
}finally {
|
|
lock.readLock().unlock();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* called from Serialization object
|
|
*/
|
|
static LinkedList2 deserialize(DataInput is, Serialization ser) throws IOException, ClassNotFoundException {
|
|
long rootrecid = LongPacker.unpackLong(is);
|
|
long sizeLimit = LongPacker.unpackLong(is);
|
|
if(sizeLimit!=0) throw new InternalError("LinkedList.sizeLimit not supported in this JDBM version");
|
|
Serializer serializer = (Serializer) ser.deserialize(is);
|
|
return new LinkedList2(ser.db,rootrecid, serializer);
|
|
}
|
|
|
|
void serialize(DataOutput out) throws IOException {
|
|
LongPacker.packLong(out, rootRecid);
|
|
LongPacker.packLong(out, sizeLimit);
|
|
db.defaultSerializer().serialize(out, valueSerializer);
|
|
}
|
|
|
|
private final Serializer<Entry> entrySerializer = new Serializer<Entry>() {
|
|
|
|
public void serialize(DataOutput out, Entry e) throws IOException {
|
|
LongPacker.packLong(out, e.prev);
|
|
LongPacker.packLong(out, e.next);
|
|
if (valueSerializer != null)
|
|
valueSerializer.serialize(out, (E) e.value);
|
|
else
|
|
db.defaultSerializer().serialize(out, e.value);
|
|
}
|
|
|
|
public Entry<E> deserialize(DataInput in) throws IOException, ClassNotFoundException {
|
|
long prev = LongPacker.unpackLong(in);
|
|
long next = LongPacker.unpackLong(in);
|
|
Object value = null;
|
|
if (loadValues)
|
|
value = valueSerializer == null ? db.defaultSerializer().deserialize(in) : valueSerializer.deserialize(in);
|
|
return new LinkedList2.Entry(prev, next, value);
|
|
}
|
|
};
|
|
|
|
static class Entry<E> {
|
|
long prev = 0;
|
|
long next = 0;
|
|
|
|
E value;
|
|
|
|
public Entry(long prev, long next, E value) {
|
|
this.prev = prev;
|
|
this.next = next;
|
|
this.value = value;
|
|
}
|
|
}
|
|
|
|
private final class Iter implements ListIterator<E> {
|
|
|
|
private int expectedModCount = modCount;
|
|
private int index = 0;
|
|
|
|
private long prev = 0;
|
|
private long next = 0;
|
|
|
|
private byte lastOper = 0;
|
|
|
|
public boolean hasNext() {
|
|
return next != 0;
|
|
}
|
|
|
|
|
|
public E next() {
|
|
if (next == 0) throw new NoSuchElementException();
|
|
checkForComodification();
|
|
|
|
Entry<E> e = fetch(next);
|
|
|
|
prev = next;
|
|
next = e.next;
|
|
index++;
|
|
lastOper = +1;
|
|
return e.value;
|
|
}
|
|
|
|
public boolean hasPrevious() {
|
|
return prev != 0;
|
|
}
|
|
|
|
public E previous() {
|
|
checkForComodification();
|
|
Entry<E> e = fetch(prev);
|
|
next = prev;
|
|
prev = e.prev;
|
|
index--;
|
|
lastOper = -1;
|
|
return e.value;
|
|
}
|
|
|
|
public int nextIndex() {
|
|
return index;
|
|
}
|
|
|
|
public int previousIndex() {
|
|
return index - 1;
|
|
}
|
|
|
|
public void remove() {
|
|
checkForComodification();
|
|
lock.writeLock().lock();
|
|
try {
|
|
if (lastOper == 1) {
|
|
//last operation was next() so remove previous element
|
|
lastOper = 0;
|
|
|
|
Entry<E> p = db.fetch(prev, entrySerializer);
|
|
//update entry before previous
|
|
if (p.prev != 0) {
|
|
Entry<E> pp = db.fetch(p.prev, entrySerializer);
|
|
pp.next = p.next;
|
|
db.update(p.prev, pp, entrySerializer);
|
|
}
|
|
//update entry after next
|
|
if (p.next != 0) {
|
|
Entry<E> pn = db.fetch(p.next, entrySerializer);
|
|
pn.prev = p.prev;
|
|
db.update(p.next, pn, entrySerializer);
|
|
}
|
|
//remove old record from db
|
|
db.delete(prev);
|
|
//update list
|
|
Root r = getRoot();
|
|
if (r.first == prev)
|
|
r.first = next;
|
|
if (r.last == prev)
|
|
r.last = next;
|
|
r.size--;
|
|
db.update(rootRecid, r,ROOT_SERIALIZER);
|
|
modCount++;
|
|
expectedModCount++;
|
|
//update iterator
|
|
prev = p.prev;
|
|
|
|
} else if (lastOper == -1) {
|
|
//last operation was prev() so remove next element
|
|
lastOper = 0;
|
|
|
|
Entry<E> n = db.fetch(next, entrySerializer);
|
|
//update entry before next
|
|
if (n.prev != 0) {
|
|
Entry<E> pp = db.fetch(n.prev, entrySerializer);
|
|
pp.next = n.next;
|
|
db.update(n.prev, pp, entrySerializer);
|
|
}
|
|
//update entry after previous
|
|
if (n.next != 0) {
|
|
Entry<E> pn = db.fetch(n.next, entrySerializer);
|
|
pn.prev = n.prev;
|
|
db.update(n.next, pn, entrySerializer);
|
|
}
|
|
//remove old record from db
|
|
db.delete(next);
|
|
//update list
|
|
Root r = getRoot();
|
|
if (r.last == next)
|
|
r.last = prev;
|
|
if (r.first == next)
|
|
r.first = prev;
|
|
r.size--;
|
|
db.update(rootRecid, r,ROOT_SERIALIZER);
|
|
modCount++;
|
|
expectedModCount++;
|
|
//update iterator
|
|
next = n.next;
|
|
|
|
} else
|
|
throw new IllegalStateException();
|
|
} catch (IOException e) {
|
|
throw new IOError(e);
|
|
}finally {
|
|
lock.writeLock().unlock();
|
|
}
|
|
|
|
}
|
|
|
|
public void set(E value) {
|
|
checkForComodification();
|
|
lock.writeLock().lock();
|
|
try {
|
|
if (lastOper == 1) {
|
|
//last operation was next(), so update previous item
|
|
lastOper = 0;
|
|
Entry<E> n = db.fetch(prev, entrySerializer);
|
|
n.value = value;
|
|
db.update(prev, n, entrySerializer);
|
|
} else if (lastOper == -1) {
|
|
//last operation was prev() so update next item
|
|
lastOper = 0;
|
|
Entry<E> n = db.fetch(next, entrySerializer);
|
|
n.value = value;
|
|
db.update(next, n, entrySerializer);
|
|
} else
|
|
throw new IllegalStateException();
|
|
} catch (IOException e) {
|
|
throw new IOError(e);
|
|
}finally {
|
|
lock.writeLock().unlock();
|
|
}
|
|
|
|
}
|
|
|
|
public void add(E value) {
|
|
checkForComodification();
|
|
|
|
//use more efficient method if possible
|
|
if (next == 0) {
|
|
LinkedList2.this.add(value);
|
|
expectedModCount++;
|
|
return;
|
|
}
|
|
lock.writeLock().lock();
|
|
try {
|
|
//insert new entry
|
|
Entry<E> e = new Entry<E>(prev, next, value);
|
|
long recid = db.insert(e, entrySerializer,false);
|
|
|
|
//update previous entry
|
|
if (prev != 0) {
|
|
Entry<E> p = db.fetch(prev, entrySerializer);
|
|
if (p.next != next) throw new Error();
|
|
p.next = recid;
|
|
db.update(prev, p, entrySerializer);
|
|
}
|
|
|
|
//update next entry
|
|
Entry<E> n = fetch(next);
|
|
if (n.prev != prev) throw new Error();
|
|
n.prev = recid;
|
|
db.update(next, n, entrySerializer);
|
|
|
|
//update List
|
|
Root r = getRoot();
|
|
r.size++;
|
|
db.update(rootRecid, r, ROOT_SERIALIZER);
|
|
|
|
//update iterator
|
|
expectedModCount++;
|
|
modCount++;
|
|
prev = recid;
|
|
|
|
} catch (IOException e) {
|
|
throw new IOError(e);
|
|
}finally {
|
|
lock.writeLock().unlock();
|
|
}
|
|
|
|
}
|
|
|
|
final void checkForComodification() {
|
|
if (modCount != expectedModCount)
|
|
throw new ConcurrentModificationException();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Copyes collection from one db to other, while keeping logical recids unchanged
|
|
*/
|
|
static void defrag(long recid, DBStore r1, DBStore r2) throws IOException {
|
|
try {
|
|
//move linked list itself
|
|
byte[] data = r1.fetchRaw(recid);
|
|
r2.forceInsert(recid, data);
|
|
DataInputOutput in = new DataInputOutput();
|
|
in.reset(data);
|
|
LinkedList2 l = (LinkedList2) r1.defaultSerializer().deserialize(in);
|
|
l.loadValues = false;
|
|
//move linkedlist root
|
|
if(l.rootRecid == 0) //empty list, done
|
|
return;
|
|
|
|
data = r1.fetchRaw(l.rootRecid);
|
|
r2.forceInsert(l.rootRecid, data);
|
|
in.reset(data);
|
|
Root r = ROOT_SERIALIZER.deserialize(in);
|
|
//move all other nodes in linked list
|
|
long current = r.first;
|
|
while (current != 0) {
|
|
data = r1.fetchRaw(current);
|
|
in.reset(data);
|
|
r2.forceInsert(current, data);
|
|
|
|
Entry e = (Entry) l.entrySerializer.deserialize(in);
|
|
current = e.next;
|
|
}
|
|
} catch (ClassNotFoundException e) {
|
|
throw new IOError(e);
|
|
}
|
|
|
|
}
|
|
|
|
}
|