/*
* JBoss, Home of Professional Open Source.
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
*
* 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 2.1 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
package org.teiid.common.buffer;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import org.teiid.client.ResizingArrayList;
import org.teiid.core.TeiidComponentException;
import org.teiid.core.TeiidRuntimeException;
import org.teiid.query.QueryPlugin;
/**
* A linked list Page entry in the tree
*
* State cloning allows a single storage reference to be shared in many trees.
* A phantom reference is used for proper cleanup once cloned.
*
* TODO: a better purging strategy for managedbatchs.
*
*/
@SuppressWarnings("unchecked")
class SPage implements Cloneable {
static class SearchResult {
int index;
SPage page;
List<List<?>> values;
public SearchResult(int index, SPage page, List<List<?>> values) {
this.index = index;
this.page = page;
this.values = values;
}
}
static final Map<Long, PhantomReference<Object>> REFERENCES = new ConcurrentHashMap<Long, PhantomReference<Object>>();
private static ReferenceQueue<Object> QUEUE = new ReferenceQueue<Object>();
static class CleanupReference extends PhantomReference<Object> {
private Long batch;
private Reference<? extends BatchManager> ref;
public CleanupReference(Object referent, Long batch, Reference<? extends BatchManager> ref) {
super(referent, QUEUE);
this.batch = batch;
this.ref = ref;
}
public void cleanup() {
try {
BatchManager batchManager = ref.get();
if (batchManager != null) {
batchManager.remove(batch);
}
} finally {
this.clear();
}
}
}
private static AtomicLong counter = new AtomicLong();
STree stree;
private long id;
protected SPage next;
protected SPage prev;
protected Long managedBatch;
protected Object trackingObject;
protected List<List<?>> values;
protected List<SPage> children;
SPage(STree stree, boolean leaf) {
this.stree = stree;
this.id = counter.getAndIncrement();
stree.pages.put(this.id, this);
this.values = new ResizingArrayList<List<?>>();
if (!leaf) {
children = new ResizingArrayList<SPage>();
}
}
public SPage clone(STree tree) {
try {
if (this.managedBatch != null && trackingObject == null) {
this.trackingObject = new Object();
CleanupReference managedBatchReference = new CleanupReference(trackingObject, managedBatch, stree.getBatchManager(children == null).getBatchManagerReference());
REFERENCES.put(managedBatch, managedBatchReference);
}
SPage clone = (SPage) super.clone();
clone.stree = tree;
if (children != null) {
clone.children = new ResizingArrayList<SPage>(children);
}
if (values != null) {
clone.values = new ResizingArrayList<List<?>>(values);
}
return clone;
} catch (CloneNotSupportedException e) {
throw new TeiidRuntimeException(QueryPlugin.Event.TEIID30038, e);
}
}
public long getId() {
return id;
}
static SearchResult search(SPage page, List k, List<SearchResult> parent) throws TeiidComponentException {
List<List<?>> previousValues = null;
for (;;) {
List<List<?>> values = page.getValues();
int index = Collections.binarySearch(values, k, page.stree.comparator);
int flippedIndex = - index - 1;
if (previousValues != null) {
if (flippedIndex == 0) {
//systemic weakness of the algorithm
return new SearchResult(-previousValues.size() - 1, page.prev, previousValues);
}
if (parent != null && index != 0) {
page.stree.updateLock.lock();
try {
index = Collections.binarySearch(values, k, page.stree.comparator);
if (index != 0) {
//for non-matches move the previous pointer over to this page
SPage childPage = page;
List oldKey = null;
List newKey = page.stree.extractKey(values.get(0));
for (ListIterator<SearchResult> desc = parent.listIterator(); desc.hasPrevious();) {
SearchResult sr = desc.previous();
int parentIndex = Math.max(0, -sr.index - 2);
if (oldKey == null) {
oldKey = sr.values.set(parentIndex, newKey);
} else if (page.stree.comparator.compare(oldKey, sr.values.get(parentIndex)) == 0 ) {
sr.values.set(parentIndex, newKey);
} else {
break;
}
sr.page.children.set(parentIndex, childPage);
sr.page.setValues(sr.values);
childPage = sr.page;
}
}
} finally {
page.stree.updateLock.unlock();
}
}
}
if (flippedIndex != values.size() || page.next == null) {
return new SearchResult(index, page, values);
}
previousValues = values;
page = page.next;
}
}
protected void setValues(List<List<?>> values) throws TeiidComponentException {
if (values instanceof LightWeightCopyOnWriteList<?>) {
values = ((LightWeightCopyOnWriteList<List<?>>)values).getList();
}
if (values.size() < stree.minPageSize || stree.getRowCount() < stree.minStorageSize) {
setDirectValues(values);
return;
} else if (stree.batchInsert && children == null && values.size() < stree.leafSize) {
setDirectValues(values);
stree.incompleteInsert = this;
return;
}
this.values = null;
managedBatch = stree.getBatchManager(children == null).createManagedBatch(values, managedBatch, trackingObject == null);
this.trackingObject = null;
}
private void setDirectValues(List<List<?>> values) {
if (managedBatch != null && trackingObject == null) {
stree.getBatchManager(children == null).remove(managedBatch);
managedBatch = null;
trackingObject = null;
}
this.values = values;
}
protected void remove(boolean force) {
if (managedBatch != null) {
if (force || trackingObject == null) {
stree.getBatchManager(children == null).remove(managedBatch);
}
managedBatch = null;
trackingObject = null;
}
values = null;
children = null;
}
protected List<List<?>> getValues() throws TeiidComponentException {
if (values != null) {
return values;
}
if (managedBatch == null) {
throw new AssertionError("Batch removed"); //$NON-NLS-1$
}
for (int i = 0; i < 10; i++) {
CleanupReference ref = (CleanupReference)QUEUE.poll();
if (ref == null) {
break;
}
if (REFERENCES.remove(ref.batch) != null) {
ref.cleanup();
}
}
List<List<?>> result = stree.getBatchManager(children == null).getBatch(managedBatch, true);
if (trackingObject != null) {
return new LightWeightCopyOnWriteList<List<?>>(result);
}
return result;
}
static void merge(LinkedList<SearchResult> places, List<List<?>> nextValues, SPage current, List<List<?>> currentValues)
throws TeiidComponentException {
SearchResult parent = places.peekLast();
if (parent != null) {
correctParents(parent.page, nextValues.get(0), current.next, current);
}
currentValues.addAll(nextValues);
if (current.children != null) {
current.children.addAll(current.next.children);
}
current.next.remove(false);
current.next = current.next.next;
if (current.next != null) {
current.next.prev = current;
}
current.setValues(currentValues);
}
/**
* Remove the usage of page in favor of nextPage
* @param parent
* @param page
* @param nextPage
* @throws TeiidComponentException
*/
static void correctParents(SPage parent, List key, SPage page, SPage nextPage) throws TeiidComponentException {
SearchResult location = SPage.search(parent, key, null);
while (location.index == -1 && location.page.prev != null ) {
parent = location.page.prev;
location = SPage.search(parent, key, null);
}
parent = location.page;
int index = location.index;
if (index < 0) {
index = -index - 1;
}
while (parent != null) {
while (index < parent.children.size()) {
if (parent.children.get(index) != page) {
return;
}
parent.children.set(index++, nextPage);
}
index = 0;
parent = parent.next;
}
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
try {
List<List<?>> tb = getValues();
result.append(id);
if (children == null) {
if (tb.size() <= 1) {
result.append(tb);
} else {
result.append("[").append(tb.get(0)).append(" . ").append(tb.size()). //$NON-NLS-1$ //$NON-NLS-2$
append(" . ").append(tb.get(tb.size() - 1)).append("]"); //$NON-NLS-1$ //$NON-NLS-2$
}
} else {
result.append("["); //$NON-NLS-1$
for (int i = 0; i < children.size(); i++) {
result.append(tb.get(i)).append("->").append(children.get(i).getId()); //$NON-NLS-1$
if (i < children.size() - 1) {
result.append(", "); //$NON-NLS-1$
}
}
result.append("]");//$NON-NLS-1$
}
} catch (Throwable e) {
result.append("Removed"); //$NON-NLS-1$
}
return result.toString();
}
}