/**
*
* Copyright 2004 Protique Ltd
*
* 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.activemq.service.impl;
import org.activemq.service.QueueList;
import org.activemq.service.QueueListEntry;
import org.activemq.util.FastInputStream;
import org.activemq.util.FastOutputStream;
import org.activemq.util.JMSExceptionHelper;
import javax.jms.JMSException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.Serializable;
/**
* A base class which is useful for implementation inheritence when implementing
* a persistent QueueList
*
* @version $Revision: 1.1.1.1 $
*/
public abstract class QueueListSupport implements QueueList {
protected static final Long HEAD_KEY = new Long(0);
public static class Header implements Serializable {
private static final long serialVersionUID = 64734383295040L;
public Long headKey;
public Long tailKey;
public long lastKeyValue;
public int size;
public byte[] asBytes() throws IOException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(buffer);
out.writeLong(unwrapLong(headKey));
out.writeLong(unwrapLong(tailKey));
out.writeLong(lastKeyValue);
out.writeInt(size);
return buffer.toByteArray();
}
public void fromBytes(byte[] data) throws IOException {
DataInputStream in = new DataInputStream(new ByteArrayInputStream(data));
this.headKey = wrapLong(in.readLong());
this.tailKey = wrapLong(in.readLong());
this.lastKeyValue = in.readLong();
this.size = in.readInt();
}
}
public static class Node implements Serializable, QueueListEntry {
private static final long serialVersionUID = 4609474001468609536L;
public Long previousKey;
public Long nextKey;
public Object value;
// not stored, but cached when read from the table
public transient Long key;
public Object getElement() {
return value;
}
public byte[] asBytes() throws IOException {
FastOutputStream buffer = new FastOutputStream();
DataOutputStream out = new DataOutputStream(buffer);
out.writeLong(unwrapLong(previousKey));
out.writeLong(unwrapLong(nextKey));
out.writeUTF((String) value);
return buffer.toByteArray();
}
public void fromBytes(byte[] data) throws IOException {
DataInputStream in = new DataInputStream(new FastInputStream(data));
this.previousKey = wrapLong(in.readLong());
this.nextKey = wrapLong(in.readLong());
this.value = in.readUTF();
}
}
public Object getFirst() throws JMSException {
try {
Long key = getHeader().headKey;
if (key != null) {
Node node = getNode(key);
if (node != null) {
return node.getElement();
}
}
return null;
}
catch (IOException e) {
throw JMSExceptionHelper.newJMSException("Failed to read from table: " + e, e);
}
}
public Object getLast() throws JMSException {
try {
Long key = getHeader().tailKey;
if (key != null) {
Node node = getNode(key);
if (node != null) {
return node.getElement();
}
}
return null;
}
catch (IOException e) {
throw JMSExceptionHelper.newJMSException("Failed to read from table: " + e, e);
}
}
public Object removeFirst() throws JMSException {
try {
Header header = getHeader();
Long key = header.headKey;
if (key != null) {
Node node = getNode(key);
if (node != null) {
doRemoveNode(node);
header.headKey = node.nextKey;
--header.size;
updateHeader(header);
return node.getElement();
}
}
return null;
}
catch (IOException e) {
throw JMSExceptionHelper.newJMSException("Failed to write to table: " + e, e);
}
}
public Object removeLast() throws JMSException {
try {
Header header = getHeader();
Long key = header.tailKey;
if (key != null) {
Node node = getNode(key);
if (node != null) {
doRemoveNode(node);
header.tailKey = node.previousKey;
--header.size;
updateHeader(header);
return node.getElement();
}
}
return null;
}
catch (IOException e) {
throw JMSExceptionHelper.newJMSException("Failed to write to table: " + e, e);
}
}
public QueueListEntry addFirst(Object value) throws JMSException {
try {
Header header = getHeader();
Node node = createNode();
node.value = value;
Long nextKey = header.headKey;
node.nextKey = nextKey;
Long key = createKey(header);
node.key = key;
updateNode(node);
updateNextNode(nextKey, key);
header.headKey = key;
if (header.tailKey == null) {
header.tailKey = key;
}
header.size++;
updateHeader(header);
return node;
}
catch (IOException e) {
throw JMSExceptionHelper.newJMSException("Failed to write to table: " + e, e);
}
}
public QueueListEntry addLast(Object value) throws JMSException {
try {
Header header = getHeader();
return doAddLast(value, header);
}
catch (IOException e) {
throw JMSExceptionHelper.newJMSException("Failed to write to table: " + e, e);
}
}
public boolean contains(Object value) throws JMSException {
return indexOf(value) != -1;
}
public int size() throws JMSException {
try {
return getHeader().size;
}
catch (IOException e) {
throw JMSExceptionHelper.newJMSException("Failed to read from table: " + e, e);
}
}
public boolean isEmpty() throws JMSException {
int size = size();
return size == 0;
}
public QueueListEntry add(Object value) throws JMSException {
return addLast(value);
}
public Object get(int index) throws JMSException {
try {
Node node = getNode(index);
if (node != null) {
return node.value;
}
return null;
}
catch (IOException e) {
throw JMSExceptionHelper.newJMSException("Failed to read from table: " + e, e);
}
}
public Object set(int index, Object element) throws JMSException {
try {
Node node = getNode(index);
if (node != null) {
Object previous = node.value;
node.value = element;
updateNode(node);
return previous;
}
return null;
}
catch (IOException e) {
throw JMSExceptionHelper.newJMSException("Failed to write to table: " + e, e);
}
}
public void add(int index, Object element) throws JMSException {
if (index == 0) {
addFirst(element);
}
else {
try {
Header header = getHeader();
Node nextNode = getNode(header, index);
doAddBefore(header, nextNode, element);
}
catch (IOException e) {
throw JMSExceptionHelper.newJMSException("Failed to write to table: " + e, e);
}
}
}
public Object remove(int index) throws JMSException {
try {
Node node = getNode(index);
if (node != null) {
removeNode(node);
}
return null;
}
catch (IOException e) {
throw JMSExceptionHelper.newJMSException("Failed to write to table: " + e, e);
}
}
public int indexOf(Object value) throws JMSException {
try {
Header header = getHeader();
Long key = header.headKey;
for (int i = 0; key != null; i++) {
Node node = getNode(key);
if (node == null) {
break;
}
if (value == node.value || (value != null && value.equals(node.value))) {
return i;
}
key = node.nextKey;
}
return -1;
}
catch (IOException e) {
throw JMSExceptionHelper.newJMSException("Failed to read from table: " + e, e);
}
}
public int lastIndexOf(Object value) throws JMSException {
try {
Header header = getHeader();
Long key = header.tailKey;
for (int i = header.size - 1; key != null; i--) {
Node node = getNode(key);
if (node == null) {
break;
}
if (value == node.value || (value != null && value.equals(node.value))) {
return i;
}
key = node.previousKey;
}
return -1;
}
catch (IOException e) {
throw JMSExceptionHelper.newJMSException("Failed to read from table: " + e, e);
}
}
public QueueListEntry getFirstEntry() throws JMSException {
try {
return getNode(getHeader().headKey);
}
catch (IOException e) {
throw JMSExceptionHelper.newJMSException("Failed to read from table: " + e, e);
}
}
public QueueListEntry getLastEntry() throws JMSException {
try {
return getNode(getHeader().tailKey);
}
catch (IOException e) {
throw JMSExceptionHelper.newJMSException("Failed to read from table: " + e, e);
}
}
public QueueListEntry getNextEntry(QueueListEntry entry) throws JMSException {
Node node = (Node) entry;
try {
return getNode(node.nextKey);
}
catch (IOException e) {
throw JMSExceptionHelper.newJMSException("Failed to read from table: " + e, e);
}
}
public QueueListEntry getPrevEntry(QueueListEntry entry) throws JMSException {
Node node = (Node) entry;
try {
return getNode(node.previousKey);
}
catch (IOException e) {
throw JMSExceptionHelper.newJMSException("Failed to read from table: " + e, e);
}
}
public QueueListEntry addBefore(Object value, QueueListEntry entry) throws JMSException {
try {
return doAddBefore(getHeader(), (Node) entry, value);
}
catch (IOException e) {
throw JMSExceptionHelper.newJMSException("Failed to write to table: " + e, e);
}
}
public void remove(QueueListEntry entry) throws JMSException {
try {
removeNode((Node) entry);
}
catch (IOException e) {
throw JMSExceptionHelper.newJMSException("Failed to write to table: " + e, e);
}
}
public Object[] toArray() throws JMSException {
try {
Header header = getHeader();
int size = header.size;
if (size == 0) {
return EMPTY_ARRAY;
}
Long key = header.headKey;
Object[] answer = new Object[size];
for (int i = 0; i < size && key != null; i++) {
Node node = getNode(key);
if (node != null) {
answer[i] = node.value;
key = node.nextKey;
}
}
return answer;
}
catch (IOException e) {
throw JMSExceptionHelper.newJMSException("Failed to write to table: " + e, e);
}
}
public void rotate() throws JMSException {
// TODO could tune this by just swizzling pointers
Object obj = removeFirst();
if (obj != null) {
addLast(obj);
}
}
protected Long createKey(Header header) throws IOException, JMSException {
long value = header.lastKeyValue;
while (true) {
if (value == Long.MAX_VALUE) {
value = 1;
}
else {
value++;
}
Long key = new Long(value);
if (getNode(key) == null) {
header.lastKeyValue = value;
return key;
}
}
}
protected boolean removeNode(Node node) throws IOException, JMSException {
Header header = null;
boolean updatedPrevious = false;
if (node.previousKey != null) {
// lets point the previous node to our next node
Node previousNode = getNode(node.previousKey);
if (previousNode != null) {
previousNode.nextKey = node.nextKey;
updateNode(previousNode);
updatedPrevious = true;
}
}
if (!updatedPrevious) {
// lets update the head record
header = getHeader();
header.headKey = node.nextKey;
}
boolean updatedNext = false;
if (node.nextKey != null) {
// lets point the next node to our previous node
Node nextNode = getNode(node.nextKey);
if (nextNode != null) {
nextNode.previousKey = node.previousKey;
updateNode(nextNode);
updatedNext = true;
}
}
if (!updatedNext) {
// lets update the tail record
header = getHeader();
header.tailKey = node.previousKey;
}
return true;
}
/**
* Looks up the header object, lazily creating one if the current table is empty
*
* @return
* @throws java.io.IOException
*/
protected abstract Header getHeader() throws IOException, JMSException;
/**
* Writes the header back to disk after its been changed
*
* @param header
* @throws java.io.IOException
*/
protected abstract void updateHeader(Header header) throws IOException, JMSException;
/**
* Updates the node
*
* @param node
* @throws java.io.IOException
*/
protected abstract void updateNode(Node node) throws IOException, JMSException;
protected abstract Node getNode(Long key) throws IOException, JMSException;
protected Node getNode(int index) throws IOException, JMSException {
Header header = getHeader();
return getNode(header, index);
}
protected Node getNode(Header header, int index) throws IOException, JMSException {
Node node = null;
int middle = header.size / 2;
if (index > middle) {
// lets navigate backwards
Long key = header.tailKey;
for (int i = header.size; i > index && key != null; i--) {
node = getNode(key);
if (node != null) {
key = node.previousKey;
}
}
}
else {
Long key = header.headKey;
for (int i = 0; i <= index && key != null; i++) {
node = getNode(key);
if (node != null) {
key = node.nextKey;
}
}
}
return node;
}
protected Node doAddLast(Object value, Header header) throws IOException, JMSException {
Node node = createNode();
Long key = createKey(header);
node.key = key;
node.value = value;
Long previousKey = header.tailKey;
node.previousKey = previousKey;
updateNode(node);
updatePreviousNode(previousKey, key);
header.tailKey = key;
if (header.headKey == null) {
header.headKey = key;
}
header.size++;
updateHeader(header);
return node;
}
protected void updateNextNode(Long nextKey, Long key) throws IOException, JMSException {
if (nextKey != null) {
Node nextNode = getNode(nextKey);
if (nextNode == null) {
throw new IOException("Missing node for key: " + nextKey);
}
nextNode.previousKey = key;
updateNode(nextNode);
}
}
protected void updatePreviousNode(Long previousKey, Long key) throws IOException, JMSException {
if (previousKey != null) {
Node previousNode = getNode(previousKey);
if (previousNode == null) {
throw new IOException("Missing previous node for key: " + previousKey);
}
previousNode.nextKey = key;
updateNode(previousNode);
}
}
protected Node doAddBefore(Header header, Node nextNode, Object element) throws JMSException, IOException {
if (nextNode == null) {
return doAddLast(element, header);
}
else {
// lets add before this nextNode
Long key = createKey(header);
Node node = createNode();
node.value = element;
node.key = key;
Long previousKey = nextNode.previousKey;
node.previousKey = previousKey;
node.nextKey = nextNode.key;
nextNode.previousKey = key;
header.size++;
updateNode(node);
updateNode(nextNode);
updatePreviousNode(previousKey, key);
updateHeader(header);
return node;
}
}
protected abstract void doRemoveNode(Node node) throws IOException, JMSException;
protected static Long wrapLong(long value) {
// lets use 0 for null
if (value == 0) {
return null;
}
// TODO use a cache?
return new Long(value);
}
protected static long unwrapLong(Long key) {
if (key != null) {
return key.longValue();
}
// lets use 0 for null
return 0;
}
protected Node createNode() {
return new Node();
}
}