/*
* Copyright 2003-2016 JetBrains s.r.o.
*
* 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 jetbrains.mps.nodeEditor.cells.collections;
import org.jetbrains.annotations.NotNull;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* User: shatalin
* Date: 10/02/16
*/
abstract class AbstractContainer<T> implements Container<T> {
private Object myModificationId = new Object();
private Entry<T> myFirst;
private int mySize = 0;
/**
* Returning Entry instance associated with specified item in this container.
* <p>
* Returning null if this container does not associate any Entry with the specified item.
*
* @param item the item from this container
* @return Entry or null if this item was not associated with any Entry in this container
*/
protected abstract Entry<T> getEntry(@NotNull T item);
/**
* Creating new Entry and associating it with the specified item in this container.
* <p>
* Returning null if this container already associates another Entry with the specified item.
* In this case existing association will not be modified.
*
* @param item the item from this container
* @return Entry or null if this item was already added to this container
*/
protected abstract Entry<T> createEntry(@NotNull T item);
/**
* Dissociating specified Entry from associated item in this container.
* <p>
* Return null if this container does not associate specified Entry with any item
* (if <code>getEntry(entry.myItem) == null</code>)
*
* @param entry dissociated entry
*/
protected abstract Entry<T> deleteEntry(@NotNull Entry<T> entry);
private Entry<T> getAnchorEntry(T anchor) {
return anchor == null ? null : getExistingEntry(anchor);
}
@NotNull
private Entry<T> getExistingEntry(@NotNull T item) {
Entry<T> anchorEntry = getEntry(item);
if (anchorEntry == null) {
throw new NoSuchElementException();
}
return anchorEntry;
}
@Override
public int size() {
return mySize;
}
private void incSize() {
myModificationId = new Object();
if (mySize == Integer.MAX_VALUE) {
throw new OutOfMemoryError("Maximum size of this Container is " + Integer.MAX_VALUE);
}
mySize++;
}
private void decSize() {
myModificationId = new Object();
mySize--;
}
@Override
public boolean isEmpty() {
return getFirstEntry() == null;
}
@Override
public T add(@NotNull T item) {
return addBefore(item, null);
}
@Override
public T addBefore(@NotNull T item, T anchor) {
Entry<T> entry = createEntry(item);
if (entry == null) {
throw new IllegalArgumentException();
}
return addEntryBefore(entry, getAnchorEntry(anchor)).getItem();
}
@Override
public T remove(@NotNull T item) {
removeEntry(getExistingEntry(item));
return item;
}
@Override
public boolean contains(@NotNull T item) {
return getEntry(item) != null;
}
@NotNull
@Override
public Iterator<T> iterator() {
return iterator(null, true);
}
@NotNull
@Override
public Iterator<T> iterator(T anchor, boolean forward) {
if (isEmpty()) {
if (anchor != null) {
throw new NoSuchElementException();
}
return EmptyIterator.getInstance();
}
return new ContentsIterator(getAnchorEntry(anchor), forward);
}
@Override
public T getFirst() {
Entry<T> firstEntry = getFirstEntry();
if (firstEntry == null) {
throw new NoSuchElementException();
}
return firstEntry.getItem();
}
@Override
public T getLast() {
Entry<T> lastEntry = getLastEntry();
if (lastEntry == null) {
throw new NoSuchElementException();
}
return lastEntry.getItem();
}
Entry<T> getFirstEntry() {
return myFirst;
}
private Entry<T> getLastEntry() {
return myFirst == null ? null : myFirst.getPrev();
}
private Entry<T> addEntryBefore(@NotNull Entry<T> entry, Entry<T> anchor) {
assert entry.getPrev() == null;
assert entry.getNext() == null;
Entry<T> firstEntry = getFirstEntry();
// anchor should be null for empty containers
assert firstEntry != null || anchor == null;
Entry<T> lastEntry = getLastEntry();
// lastEntry should not be null for non-empty containers
assert firstEntry == null || lastEntry != null;
incSize();
if (firstEntry == null) {
myFirst = entry;
entry.setNext(null);
entry.setPrev(entry);
return entry;
}
if (anchor == null) {
entry.setNext(null);
entry.setPrev(lastEntry);
firstEntry.setPrev(entry);
lastEntry.setNext(entry);
return entry;
}
entry.setNext(anchor);
entry.setPrev(anchor.getPrev());
if (anchor != firstEntry) {
anchor.getPrev().setNext(entry);
} else {
myFirst = entry;
}
anchor.setPrev(entry);
return entry;
}
private void removeEntry(@NotNull Entry<T> entry) {
Entry<T> removedEntry = deleteEntry(entry);
assert removedEntry != null;
decSize();
Entry<T> firstEntry = getFirstEntry();
if (firstEntry == entry) {
myFirst = entry.getNext();
if (myFirst != null) {
myFirst.setPrev(entry.getPrev());
}
} else {
entry.getPrev().setNext(entry.getNext());
if (entry.getNext() != null) {
entry.getNext().setPrev(entry.getPrev());
} else {
firstEntry.setPrev(entry.getPrev());
}
}
entry.setPrev(null);
entry.setNext(null);
}
private class ContentsIterator implements Iterator<T> {
private Object myExpectedModificationId;
private Entry<T> myCurrentEntry;
private final boolean myForward;
private ContentsIterator(Entry<T> start, boolean forward) {
myCurrentEntry = start;
myForward = forward;
myExpectedModificationId = myModificationId;
}
@Override
public boolean hasNext() {
checkForComodification();
return getNextEntry() != null;
}
private Entry<T> getNextEntry() {
if (myCurrentEntry == null) {
return myForward ? getFirstEntry() : getLastEntry();
}
if (!myForward && myCurrentEntry == getFirstEntry()) {
return null;
}
return myForward ? myCurrentEntry.getNext() : myCurrentEntry.getPrev();
}
@Override
public T next() {
checkForComodification();
Entry<T> nextEntry = getNextEntry();
if (nextEntry == null) {
throw new NoSuchElementException();
}
return (myCurrentEntry = nextEntry).getItem();
}
@Override
public void remove() {
checkForComodification();
if (getEntry(myCurrentEntry.getItem()) == null) {
// entry was already removed from this container
throw new IllegalStateException();
}
removeEntry(myCurrentEntry);
myExpectedModificationId = myModificationId;
}
private void checkForComodification() {
if (myModificationId != myExpectedModificationId) {
throw new ConcurrentModificationException();
}
}
}
}