/*
* Copyright (c) 2008, Matthias Mann
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Matthias Mann nor the names of its contributors may
* be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package de.matthiasmann.twl.model;
import java.util.Comparator;
import java.util.Random;
/**
* A reordering list model - forwards changes of the base model.
*
* @param <T> The type of the list entries
* @author Matthias Mann
*/
public class ReorderListModel<T> extends AbstractListModel<T> {
private final ListModel<T> base;
private final ListModel.ChangeListener listener;
private int[] reorderList;
private int size;
public ReorderListModel(ListModel<T> base) {
this.base = base;
this.reorderList = new int[0];
this.listener = new ListModel.ChangeListener() {
public void entriesInserted(int first, int last) {
ReorderListModel.this.entriesInserted(first, last);
}
public void entriesDeleted(int first, int last) {
ReorderListModel.this.entriesDeleted(first, last);
}
public void entriesChanged(int first, int last) {
}
public void allChanged() {
ReorderListModel.this.buildNewList();
}
};
base.addChangeListener(listener);
buildNewList();
}
public void destroy() {
base.removeChangeListener(listener);
}
public int getNumEntries() {
return size;
}
public T getEntry(int index) {
int remappedIndex = reorderList[index];
return base.getEntry(remappedIndex);
}
public Object getEntryTooltip(int index) {
int remappedIndex = reorderList[index];
return base.getEntryTooltip(remappedIndex);
}
public boolean matchPrefix(int index, String prefix) {
int remappedIndex = reorderList[index];
return base.matchPrefix(remappedIndex, prefix);
}
public int findEntry(Object o) {
int[] list = this.reorderList;
for(int i=0,n=size ; i<n ; i++) {
T entry = base.getEntry(list[i]);
if(entry == o || (entry != null && entry.equals(o))) {
return i;
}
}
return -1;
}
public void shuffle() {
Random r = new Random();
for(int i=size ; i>1 ;) {
int j = r.nextInt(i--);
int temp = reorderList[i];
reorderList[i] = reorderList[j];
reorderList[j] = temp;
}
fireAllChanged();
}
public void sort(Comparator<T> c) {
// need to use own version of sort because we need to sort a int[] with a sort callback
int[] aux = new int[size];
System.arraycopy(reorderList, 0, aux, 0, size);
mergeSort(aux, reorderList, 0, size, c);
fireAllChanged();
}
/**
* Tuning parameter: list size at or below which insertion sort will be
* used in preference to mergesort.
*/
private static final int INSERTIONSORT_THRESHOLD = 7;
/**
* Src is the source array that starts at index 0
* Dest is the (possibly larger) array destination with a possible offset
* low is the index in dest to start sorting
* high is the end index in dest to end sorting
* off is the offset into src corresponding to low in dest
*/
private void mergeSort(int[] src, int[] dest,
int low, int high, Comparator<T> c) {
int length = high - low;
// Insertion sort on smallest arrays
if(length < INSERTIONSORT_THRESHOLD) {
for(int i = low; i < high; i++) {
for(int j = i; j > low && compare(dest, j - 1, j, c) > 0; j--) {
swap(dest, j, j - 1);
}
}
return;
}
// Recursively sort halves of dest into src
int mid = (low + high) >>> 1;
mergeSort(dest, src, low, mid, c);
mergeSort(dest, src, mid, high, c);
// If list is already sorted, just copy from src to dest. This is an
// optimization that results in faster sorts for nearly ordered lists.
if(compare(src, mid-1, mid, c) <= 0) {
System.arraycopy(src, low, dest, low, length);
return;
}
// Merge sorted halves (now in src) into dest
for(int i = low, p = low, q = mid; i < high; i++) {
if(q >= high || p < mid && compare(src, p, q, c) <= 0) {
dest[i] = src[p++];
} else {
dest[i] = src[q++];
}
}
}
private int compare(int[] list, int a, int b, Comparator<T> c) {
int aIdx = list[a];
int bIdx = list[b];
T objA = base.getEntry(aIdx);
T objB = base.getEntry(bIdx);
return c.compare(objA, objB);
}
/**
* Swaps x[a] with x[b].
*/
private static void swap(int x[], int a, int b) {
int t = x[a];
x[a] = x[b];
x[b] = t;
}
private void buildNewList() {
size = base.getNumEntries();
reorderList = new int[size + 1024];
for(int i = 0; i < size; i++) {
reorderList[i] = i;
}
fireAllChanged();
}
private void entriesInserted(int first, int last) {
final int delta = last - first + 1;
for(int i = 0; i < size; i++) {
if(reorderList[i] >= first) {
reorderList[i] += delta;
}
}
if(size + delta > reorderList.length) {
int[] newList = new int[Math.max(size*2, size+delta+1024)];
System.arraycopy(reorderList, 0, newList, 0, size);
reorderList = newList;
}
int oldSize = size;
for(int i = 0; i < delta; i++) {
reorderList[size++] = first + i;
}
fireEntriesInserted(oldSize, size-1);
}
private void entriesDeleted(int first, int last) {
final int delta = last - first + 1;
for(int i = 0; i < size; i++) {
final int entry = reorderList[i];
if(entry >= first) {
if(entry <= last) {
// we have to remove entries - enter copy loop
entriesDeletedCopy(first, last, i);
return;
}
reorderList[i] = entry - delta;
}
}
}
private void entriesDeletedCopy(int first, int last, int i) {
int j, delta = last - first + 1;
int oldSize = size;
for(j=i ; i<oldSize ; i++) {
int entry = reorderList[i];
if(entry >= first) {
if(entry <= last) {
size--;
fireEntriesDeleted(j, j);
continue;
}
entry -= delta;
}
reorderList[j++] = entry;
}
assert size == j;
}
}