I have decided to create my own CircularListCursor, because I wanted some abstraction and a ListIterator<E> couldn't give enough in my opinion. I'm also using the javafx.beans.Property class, as I really like how it is being used in JavaFX 8 and figured it would be useful.
The basic properties of a cursor are:
- Can go backwards
- Can go forward
- Can get the item under the cursor
My implementation:
public abstract class CircularListCursor<E> {
private final static int MIN_RANDOM_ACCESS_SIZE = 20;
public static <E> CircularListCursor<E> of(final List<E> list) {
Objects.requireNonNull(list, "list");
if (list.isEmpty()) {
throw new IllegalArgumentException("list cannot be empty");
}
if (list.size() < MIN_RANDOM_ACCESS_SIZE || list instanceof RandomAccess) {
return new RandomAccessCicularListCursor<>(list);
}
else {
return new DefaultCircularListCursor<>(list);
}
}
abstract public E getCurrent();
abstract public int getCurrentIndex();
abstract public E next();
abstract public int nextIndex();
abstract public E previous();
abstract public int previousIndex();
abstract public IntegerProperty indexProperty();
abstract public Property<E> elementProperty();
private static class RandomAccessCicularListCursor<E> extends CircularListCursor<E> {
private final List<E> list;
private final int listSize;
private final IntegerProperty indexProperty;
private final Property<E> elementProperty;
private RandomAccessCicularListCursor(final List<E> list) {
this.list = list;
this.listSize = list.size();
this.indexProperty = new SimpleIntegerProperty(0);
this.elementProperty = new SimpleObjectProperty<>(getCurrent());
}
@Override
public E getCurrent() {
return list.get(getCurrentIndex());
}
@Override
public int getCurrentIndex() {
checkForComodification();
return indexProperty.get();
}
@Override
public E next() {
indexProperty.set(nextIndex());
E element = list.get(indexProperty.get());
elementProperty.setValue(element);
return element;
}
@Override
public int nextIndex() {
checkForComodification();
int index = indexProperty.get();
return (++index == list.size() ? 0 : index);
}
@Override
public E previous() {
indexProperty.set(previousIndex());
E element = list.get(indexProperty.get());
elementProperty.setValue(element);
return element;
}
@Override
public int previousIndex() {
checkForComodification();
int index = indexProperty.get();
return ((--index == -1) ? list.size() - 1 : index);
}
private void checkForComodification() {
if (listSize != list.size()) {
throw new ConcurrentModificationException();
}
}
@Override
public IntegerProperty indexProperty() {
return indexProperty;
}
@Override
public Property<E> elementProperty() {
return elementProperty;
}
}
private static class DefaultCircularListCursor<E> extends CircularListCursor<E> {
private final List<E> list;
private final int listSize;
private final IntegerProperty indexProperty;
private final Property<E> elementProperty;
private ListIterator<E> listIterator;
private E element;
private DefaultCircularListCursor(final List<E> list) {
this.list = list;
this.listSize = list.size();
this.listIterator = list.listIterator();
this.element = listIterator.next();
this.indexProperty = new SimpleIntegerProperty(listIterator.previousIndex());
listIterator.previous();
this.elementProperty = new SimpleObjectProperty<>(getCurrent());
}
@Override
public E getCurrent() {
checkForComodification();
return element;
}
@Override
public int getCurrentIndex() {
checkForComodification();
return indexProperty.get();
}
@Override
public E next() {
checkForComodification();
return safeNext();
}
private E safeNext() {
if (listIterator.hasNext()) {
indexProperty.set(listIterator.nextIndex());
element = listIterator.next();
elementProperty.setValue(element);
return element;
}
listIterator = list.listIterator();
return safeNext();
}
@Override
public int nextIndex() {
checkForComodification();
int index = indexProperty.get();
return (++index == list.size() ? 0 : index);
}
@Override
public E previous() {
checkForComodification();
return safePrevious();
}
private E safePrevious() {
if (listIterator.hasPrevious()) {
indexProperty.set(listIterator.previousIndex());
element = listIterator.previous();
elementProperty.setValue(element);
return element;
}
listIterator = list.listIterator(list.size());
return safePrevious();
}
@Override
public int previousIndex() {
checkForComodification();
int index = indexProperty.get();
return ((--index == -1) ? list.size() - 1 : index);
}
private void checkForComodification() {
if (listSize != list.size()) {
throw new ConcurrentModificationException();
}
}
@Override
public IntegerProperty indexProperty() {
return indexProperty;
}
@Override
public Property<E> elementProperty() {
return elementProperty;
}
}
}
I use it for example like this in practice (lots of irrelevant example code stripped down):
public class TemplateInvoiceController implements Initializable {
...
private List<SelectionData> selectionDataList = new LinkedList<>();
private CircularListCursor<SelectionData> selectionDataCursor;
...
@Override
public void initialize(final URL url, final ResourceBundle resourceBundle) {
selectionDataList.add(new SelectionData(invoiceNumberLabel, invoiceNumberImageView));
selectionDataList.add(new SelectionData(invoiceDateLabel, invoiceDateImageView));
selectionDataList.add(new SelectionData(priceExclVatLabel, priceExclVatImageView));
selectionDataList.add(new SelectionData(lowVatLabel, lowVatImageView));
selectionDataList.add(new SelectionData(highVatLabel, highVatImageView));
selectionDataList.add(new SelectionData(priceInclVatLabel, priceInclVatImageView));
selectionDataCursor = CircularListCursor.of(selectionDataList);
selectionDataCursor.elementProperty().getValue().getLabel().setStyle("-fx-text-fill: red; -fx-font-weight: bold");
selectionDataCursor.elementProperty().addListener((observableValue, oldValue, newValue) -> {
oldValue.getLabel().setStyle("-fx-text-fill: black; -fx-font-weight: normal");
newValue.getLabel().setStyle("-fx-text-fill: red; -fx-font-weight: bold");
GraphicsContext graphicsContext = selectionCanvas.getGraphicsContext2D();
graphicsContext.clearRect(0d, 0d, selectionCanvas.getWidth(), selectionCanvas.getHeight());
graphicsContext.setStroke(Paint.valueOf(Color.RED.toString()));
graphicsContext.strokeRect(newValue.getStartX(), newValue.getStartY(), newValue.getWidth(), newValue.getHeight());
});
...
}
...
}
Working together with:
public void registerSceneListeners() {
templateController.getPrimaryStage().getScene().addEventFilter(KeyEvent.KEY_PRESSED, keyEvent -> {
if (keyEvent.getCode() == KeyCode.TAB) {
if (keyEvent.isShiftDown()) {
selectionDataCursor.previous();
}
else {
selectionDataCursor.next();
}
}
});
}
I'd like a full review of my CircularListCursor class.