org.apache.activemq.store.kahadb.disk.index.ListNode Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of activemq-osgi Show documentation
Show all versions of activemq-osgi Show documentation
Puts together an ActiveMQ OSGi bundle
/**
* 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.activemq.store.kahadb.disk.index;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import org.apache.activemq.store.kahadb.disk.page.Page;
import org.apache.activemq.store.kahadb.disk.page.Transaction;
import org.apache.activemq.store.kahadb.disk.util.LinkedNode;
import org.apache.activemq.store.kahadb.disk.util.LinkedNodeList;
import org.apache.activemq.store.kahadb.disk.util.Marshaller;
import org.apache.activemq.store.kahadb.disk.util.VariableMarshaller;
/**
* The ListNode class represents a node in the List object graph. It is stored
* in one overflowing Page of a PageFile.
*/
public final class ListNode {
private final static boolean ADD_FIRST = true;
private final static boolean ADD_LAST = false;
// The index that this node is part of.
private ListIndex containingList;
// The page associated with this node
private Page> page;
private LinkedNodeList> entries = new LinkedNodeList>() {
@Override
public String toString() {
return "PageId:" + page.getPageId() + ", index:" + containingList + super.toString();
}
};
// The next page after this one.
private long next = ListIndex.NOT_SET;
static final class KeyValueEntry extends LinkedNode> implements Entry {
private final Key key;
private Value value;
public KeyValueEntry(Key key, Value value) {
this.key = key;
this.value = value;
}
public Key getKey() {
return key;
}
public Value getValue() {
return value;
}
public Value setValue(Value value) {
Value oldValue = this.value;
this.value = value;
return oldValue;
}
@Override
public String toString() {
return "{" + key + ":" + value + "}";
}
}
private final class ListNodeIterator implements Iterator> {
private final Transaction tx;
private final ListIndex index;
ListNode nextEntry;
private ListNodeIterator(Transaction tx, ListNode current) {
this.tx = tx;
nextEntry = current;
index = current.getContainingList();
}
public boolean hasNext() {
return nextEntry != null;
}
public ListNode next() {
ListNode current = nextEntry;
if (current != null) {
if (current.next != ListIndex.NOT_SET) {
try {
nextEntry = index.loadNode(tx, current.next);
} catch (IOException unexpected) {
IllegalStateException e = new IllegalStateException("failed to load next: " + current.next + ", reason: "
+ unexpected.getLocalizedMessage());
e.initCause(unexpected);
throw e;
}
} else {
nextEntry = null;
}
}
return current;
}
public void remove() {
throw new UnsupportedOperationException();
}
}
final class ListIterator implements Iterator> {
private final Transaction tx;
private final ListIndex targetList;
ListNode currentNode, previousNode;
KeyValueEntry nextEntry;
KeyValueEntry entryToRemove;
private ListIterator(Transaction tx, ListNode current, long start) {
this.tx = tx;
this.currentNode = current;
this.targetList = current.getContainingList();
nextEntry = current.entries.getHead();
if (start > 0) {
moveToRequestedStart(start);
}
}
private void moveToRequestedStart(final long start) {
long count = 0;
while (hasNext() && count < start) {
next();
count++;
}
if (!hasNext()) {
throw new NoSuchElementException("Index " + start + " out of current range: " + count);
}
}
private KeyValueEntry getFromNextNode() {
KeyValueEntry result = null;
if (currentNode.getNext() != ListIndex.NOT_SET) {
try {
previousNode = currentNode;
currentNode = targetList.loadNode(tx, currentNode.getNext());
} catch (IOException unexpected) {
NoSuchElementException e = new NoSuchElementException(unexpected.getLocalizedMessage());
e.initCause(unexpected);
throw e;
}
result = currentNode.entries.getHead();
}
return result;
}
public boolean hasNext() {
if (nextEntry == null) {
nextEntry = getFromNextNode();
}
return nextEntry != null;
}
public Entry next() {
if (nextEntry != null) {
entryToRemove = nextEntry;
nextEntry = entryToRemove.getNext();
return entryToRemove;
} else {
throw new NoSuchElementException();
}
}
public void remove() {
if (entryToRemove == null) {
throw new IllegalStateException("can only remove once, call hasNext();next() again");
}
try {
entryToRemove.unlink();
entryToRemove = null;
ListNode toRemoveNode = null;
if (currentNode.entries.isEmpty()) {
// may need to free this node
if (currentNode.isHead() && currentNode.isTail()) {
// store empty list
} else if (currentNode.isHead()) {
// merge next node into existing headNode as we don't want to
// change our headPageId b/c that is our identity
ListNode headNode = currentNode;
nextEntry = getFromNextNode(); // will move currentNode
if (currentNode.isTail()) {
targetList.setTailPageId(headNode.getPageId());
}
// copy next/currentNode into head
headNode.setEntries(currentNode.entries);
headNode.setNext(currentNode.getNext());
headNode.store(tx);
toRemoveNode = currentNode;
currentNode = headNode;
} else if (currentNode.isTail()) {
toRemoveNode = currentNode;
previousNode.setNext(ListIndex.NOT_SET);
previousNode.store(tx);
targetList.setTailPageId(previousNode.getPageId());
} else {
toRemoveNode = currentNode;
previousNode.setNext(toRemoveNode.getNext());
previousNode.store(tx);
currentNode = previousNode;
}
}
targetList.onRemove();
if (toRemoveNode != null) {
tx.free(toRemoveNode.getPage());
} else {
currentNode.store(tx);
}
} catch (IOException unexpected) {
IllegalStateException e = new IllegalStateException(unexpected.getLocalizedMessage());
e.initCause(unexpected);
throw e;
}
}
ListNode getCurrent() {
return this.currentNode;
}
}
/**
* The Marshaller is used to store and load the data in the ListNode into a Page.
*
* @param
* @param
*/
static public final class NodeMarshaller extends VariableMarshaller> {
private final Marshaller keyMarshaller;
private final Marshaller valueMarshaller;
public NodeMarshaller(Marshaller keyMarshaller, Marshaller valueMarshaller) {
this.keyMarshaller = keyMarshaller;
this.valueMarshaller = valueMarshaller;
}
public void writePayload(ListNode node, DataOutput os) throws IOException {
os.writeLong(node.next);
short count = (short) node.entries.size(); // cast may truncate
// value...
if (count != node.entries.size()) {
throw new IOException("short over flow, too many entries in list: " + node.entries.size());
}
os.writeShort(count);
KeyValueEntry entry = node.entries.getHead();
while (entry != null) {
keyMarshaller.writePayload((Key) entry.getKey(), os);
valueMarshaller.writePayload((Value) entry.getValue(), os);
entry = entry.getNext();
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public ListNode readPayload(DataInput is) throws IOException {
ListNode node = new ListNode();
node.setNext(is.readLong());
final short size = is.readShort();
for (short i = 0; i < size; i++) {
node.entries.addLast(new KeyValueEntry(keyMarshaller.readPayload(is), valueMarshaller.readPayload(is)));
}
return node;
}
}
public Value put(Transaction tx, Key key, Value value) throws IOException {
if (key == null) {
throw new IllegalArgumentException("Key cannot be null");
}
entries.addLast(new KeyValueEntry(key, value));
store(tx, ADD_LAST);
return null;
}
public Value addFirst(Transaction tx, Key key, Value value) throws IOException {
if (key == null) {
throw new IllegalArgumentException("Key cannot be null");
}
entries.addFirst(new KeyValueEntry(key, value));
store(tx, ADD_FIRST);
return null;
}
public void storeUpdate(Transaction tx) throws IOException {
store(tx, ADD_LAST);
}
private void store(Transaction tx, boolean addFirst) throws IOException {
try {
// keeping splitting till we get down to a single entry
// then we need to overflow the value
getContainingList().storeNode(tx, this, entries.size() == 1);
if (this.next == -1) {
getContainingList().setTailPageId(getPageId());
}
} catch (Transaction.PageOverflowIOException e) {
// If we get an overflow
split(tx, addFirst);
}
}
private void store(Transaction tx) throws IOException {
getContainingList().storeNode(tx, this, true);
}
private void split(Transaction tx, boolean isAddFirst) throws IOException {
ListNode extension = getContainingList().createNode(tx);
if (isAddFirst) {
// head keeps the first entry, insert extension with the rest
extension.setEntries(entries.getHead().splitAfter());
extension.setNext(this.getNext());
extension.store(tx, isAddFirst);
this.setNext(extension.getPageId());
} else {
extension.setEntries(entries.getTail().getPrevious().splitAfter());
extension.setNext(this.getNext());
extension.store(tx, isAddFirst);
getContainingList().setTailPageId(extension.getPageId());
this.setNext(extension.getPageId());
}
store(tx, true);
}
// called after a split
private void setEntries(LinkedNodeList> list) {
this.entries = list;
}
public Value get(Transaction tx, Key key) {
if (key == null) {
throw new IllegalArgumentException("Key cannot be null");
}
Value result = null;
KeyValueEntry nextEntry = entries.getTail();
while (nextEntry != null) {
if (nextEntry.getKey().equals(key)) {
result = nextEntry.getValue();
break;
}
nextEntry = nextEntry.getPrevious();
}
return result;
}
public boolean isEmpty(final Transaction tx) {
return entries.isEmpty();
}
public Entry getFirst(Transaction tx) {
return entries.getHead();
}
public Entry getLast(Transaction tx) {
return entries.getTail();
}
public Iterator> iterator(final Transaction tx, long pos) throws IOException {
return new ListIterator(tx, this, pos);
}
public Iterator> iterator(final Transaction tx) throws IOException {
return new ListIterator(tx, this, 0);
}
Iterator> listNodeIterator(final Transaction tx) throws IOException {
return new ListNodeIterator(tx, this);
}
public void clear(Transaction tx) throws IOException {
entries.clear();
tx.free(this.getPageId());
}
public boolean contains(Transaction tx, Key key) {
if (key == null) {
throw new IllegalArgumentException("Key cannot be null");
}
boolean found = false;
KeyValueEntry nextEntry = entries.getTail();
while (nextEntry != null) {
if (nextEntry.getKey().equals(key)) {
found = true;
break;
}
nextEntry = nextEntry.getPrevious();
}
return found;
}
// /////////////////////////////////////////////////////////////////
// Implementation methods
// /////////////////////////////////////////////////////////////////
public long getPageId() {
return page.getPageId();
}
public Page> getPage() {
return page;
}
public void setPage(Page> page) {
this.page = page;
}
public long getNext() {
return next;
}
public void setNext(long next) {
this.next = next;
}
public void setContainingList(ListIndex list) {
this.containingList = list;
}
public ListIndex getContainingList() {
return containingList;
}
public boolean isHead() {
return getPageId() == containingList.getHeadPageId();
}
public boolean isTail() {
return getPageId() == containingList.getTailPageId();
}
public int size(Transaction tx) {
return entries.size();
}
@Override
public String toString() {
return "[ListNode(" + (page != null ? page.getPageId() + "->" + next : "null") + ")[" + entries.size() + "]]";
}
}