helma/src/main/java/helma/objectmodel/db/SegmentedSubnodeList.java

268 lines
8 KiB
Java

/*
* Helma License Notice
*
* The contents of this file are subject to the Helma License
* Version 2.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://adele.helma.org/download/helma/license.txt
*
* Copyright 2009 Helma Project. All Rights Reserved.
*/
package helma.objectmodel.db;
import java.util.*;
public class SegmentedSubnodeList extends SubnodeList {
transient Segment[] segments = null;
static int SEGLENGTH = 1000;
transient private int subnodeCount = -1;
/**
* Creates a new subnode list
* @param node the node we belong to
*/
public SegmentedSubnodeList(Node node) {
super(node);
}
/**
* Adds the specified object to this list performing
* custom ordering
*
* @param handle element to be inserted.
*/
public synchronized boolean add(NodeHandle handle) {
if (!hasRelationalNodes() || segments == null) {
return super.add(handle);
}
if (subnodeCount == -1) {
update();
}
subnodeCount++;
segments[segments.length - 1].length += 1;
return list.add(handle);
}
/**
* Adds the specified object to the list at the given position
* @param index the index to insert the element at
* @param handle the object to add
*/
public synchronized void add(int index, NodeHandle handle) {
if (!hasRelationalNodes() || segments == null) {
super.add(index, handle);
return;
}
if (subnodeCount == -1) {
update();
}
subnodeCount++;
list.add(index, handle);
// shift segment indices by one
int s = getSegment(index);
segments[s].length += 1;
for (int i = s + 1; i < segments.length; i++) {
segments[i].startIndex += 1;
}
}
public NodeHandle get(int index) {
if (!hasRelationalNodes() || segments == null) {
return super.get(index);
}
if (index < 0 || index >= subnodeCount) {
return null;
}
loadSegment(getSegment(index), false);
return (NodeHandle) list.get(index);
}
public synchronized boolean contains(Object object) {
if (!hasRelationalNodes() || segments == null) {
return super.contains(object);
}
if (list.contains(object)) {
return true;
}
for (int i = 0; i < segments.length; i++) {
if (loadSegment(i, false).contains(object)) {
return true;
}
}
return false;
}
public synchronized int indexOf(Object object) {
if (!hasRelationalNodes() || segments == null) {
return super.indexOf(object);
}
int index;
if ((index = list.indexOf(object)) > -1) {
return index;
}
for (int i = 0; i < segments.length; i++) {
if ((index = loadSegment(i, false).indexOf(object)) > -1) {
return segments[i].startIndex + index;
}
}
return -1;
}
/**
* remove the object specified by the given index-position
* @param index the index-position of the NodeHandle to remove
*/
public synchronized Object remove(int index) {
if (!hasRelationalNodes() || segments == null) {
return super.remove(index);
}
if (subnodeCount == -1) {
update();
}
Object removed = list.remove(index);
int s = getSegment(index);
segments[s].length -= 1;
for (int i = s + 1; i < segments.length; i++) {
segments[i].startIndex -= 1;
}
subnodeCount--;
return removed;
}
/**
* remove the given Object from this List
* @param object the NodeHandle to remove
*/
public synchronized boolean remove(Object object) {
if (!hasRelationalNodes() || segments == null) {
return super.remove(object);
}
if (subnodeCount == -1) {
update();
}
int index = indexOf(object);
if (index > -1) {
list.remove(object);
int s = getSegment(index);
segments[s].length -= 1;
for (int i = s + 1; i < segments.length; i++) {
segments[i].startIndex -= 1;
}
subnodeCount--;
return true;
}
return false;
}
public synchronized Object[] toArray() {
if (!hasRelationalNodes() || segments == null) {
return super.toArray();
}
node.nmgr.logEvent("Warning: toArray() called on large segmented collection: " + node);
for (int i = 0; i < segments.length; i++) {
loadSegment(i, false);
}
return list.toArray();
}
private int getSegment(int index) {
for (int i = 1; i < segments.length; i++) {
if (index < segments[i].startIndex) {
return i - 1;
}
}
return segments.length - 1;
}
private List loadSegment(int seg, boolean deep) {
Segment segment = segments[seg];
if (segment != null && !segment.loaded) {
Relation rel = getSubnodeRelation().getClone();
rel.offset = segment.startIndex;
int expectedSize = rel.maxSize = segment.length;
List seglist = deep ?
node.nmgr.getNodes(node, rel) :
node.nmgr.getNodeIDs(node, rel);
int actualSize = seglist.size();
if (actualSize != expectedSize) {
node.nmgr.logEvent("Inconsistent segment size in " + node + ": " + segment);
}
int listSize = list.size();
for (int i = 0; i < actualSize; i++) {
if (segment.startIndex + i < listSize) {
list.set(segment.startIndex + i, seglist.get(i));
} else {
list.add(seglist.get(i));
}
// FIXME how to handle inconsistencies?
}
segment.loaded = true;
return seglist;
}
return Collections.EMPTY_LIST;
}
protected synchronized void update() {
if (!hasRelationalNodes()) {
segments = null;
super.update();
return;
}
// also reload if the type mapping has changed.
long lastChange = getLastSubnodeChange();
if (lastChange != lastSubnodeFetch) {
// count nodes in db without fetching anything
subnodeCount = node.nmgr.countNodes(node, getSubnodeRelation());
if (subnodeCount > SEGLENGTH) {
float size = subnodeCount;
int nsegments = (int) Math.ceil(size / SEGLENGTH);
int remainder = (int) size % SEGLENGTH;
segments = new Segment[nsegments];
for (int s = 0; s < nsegments; s++) {
int length = (s == nsegments - 1 && remainder > 0) ?
remainder : SEGLENGTH;
segments[s] = new Segment(s * SEGLENGTH, length);
}
list = new ArrayList((int) size + 5);
for (int i = 0; i < size; i++) {
list.add(null);
}
} else {
segments = null;
super.update();
}
lastSubnodeFetch = lastChange;
}
}
public int size() {
if (!hasRelationalNodes() || segments == null) {
return super.size();
}
return subnodeCount;
}
class Segment {
int startIndex, length;
boolean loaded;
Segment(int startIndex, int length) {
this.startIndex = startIndex;
this.length = length;
this.loaded = false;
}
int endIndex() {
return startIndex + length;
}
public String toString() {
return "Segment{startIndex: " + startIndex + ", length: " + length + "}";
}
}
}