Bladeren bron

new: CopyList - copy on write

Ranides Atterwim 4 jaren geleden
bovenliggende
commit
45fa8e149a

+ 513 - 0
assira.core/src/main/java/net/ranides/assira/collection/lists/CopyList.java

@@ -0,0 +1,513 @@
+package net.ranides.assira.collection.lists;
+
+import net.ranides.assira.generic.SerializableCode;
+
+import java.io.Serializable;
+import java.util.*;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.function.UnaryOperator;
+import java.util.stream.Stream;
+
+public class CopyList<T> implements List<T>, Serializable {
+
+    private transient List<T> source;
+
+    private transient boolean original;
+
+    public CopyList(T[] source) {
+        this.source = Arrays.asList(source);
+        this.original = true;
+    }
+
+    public CopyList(List<T> source) {
+        this.source = source;
+        this.original = true;
+    }
+
+    @Override
+    public int size() {
+        return source.size();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return source.isEmpty();
+    }
+
+    @Override
+    public boolean contains(Object o) {
+        return source.contains(o);
+    }
+
+    @Override
+    public void forEach(Consumer<? super T> action) {
+        source.forEach(action);
+    }
+
+    @Override
+    public Object[] toArray() {
+        return source.toArray();
+    }
+
+    @Override
+    public <E> E[] toArray(E[] a) {
+        return source.toArray(a);
+    }
+
+    @Override
+    public boolean add(T t) {
+        detach();
+        return source.add(t);
+    }
+
+    @Override
+    public boolean remove(Object o) {
+        detach();
+        return source.remove(o);
+    }
+
+    @Override
+    public boolean containsAll(Collection<?> c) {
+        return source.containsAll(c);
+    }
+
+    @Override
+    public boolean addAll(Collection<? extends T> c) {
+        detach();
+        return source.addAll(c);
+    }
+
+    @Override
+    public boolean addAll(int index, Collection<? extends T> c) {
+        detach();
+        return source.addAll(index, c);
+    }
+
+    @Override
+    public boolean removeAll(Collection<?> c) {
+        detach();
+        return source.removeAll(c);
+    }
+
+    @Override
+    public boolean removeIf(Predicate<? super T> filter) {
+        detach();
+        return source.removeIf(filter);
+    }
+
+    @Override
+    public boolean retainAll(Collection<?> c) {
+        detach();
+        return source.retainAll(c);
+    }
+
+    @Override
+    public void replaceAll(UnaryOperator<T> operator) {
+        detach();
+        source.replaceAll(operator);
+    }
+
+    @Override
+    public void sort(Comparator<? super T> c) {
+        detach();
+        source.sort(c);
+    }
+
+    @Override
+    public void clear() {
+        if (!original) {
+            source.clear();
+        } else {
+            source = Collections.emptyList();
+        }
+    }
+
+    @Override
+    public T get(int index) {
+        return source.get(index);
+    }
+
+    @Override
+    public T set(int index, T element) {
+        detach();
+        return source.set(index, element);
+    }
+
+    @Override
+    public void add(int index, T element) {
+        detach();
+        source.add(index, element);
+    }
+
+    @Override
+    public T remove(int index) {
+        detach();
+        return source.remove(index);
+    }
+
+    @Override
+    public int indexOf(Object o) {
+        return source.indexOf(o);
+    }
+
+    @Override
+    public int lastIndexOf(Object o) {
+        return source.lastIndexOf(o);
+    }
+
+    @Override
+    public Iterator<T> iterator() {
+        if (!original) {
+            return source.iterator();
+        } else {
+            return new CopyIterator(source.listIterator());
+        }
+    }
+
+    @Override
+    public ListIterator<T> listIterator() {
+        if (!original) {
+            return source.listIterator();
+        } else {
+            return new CopyIterator(source.listIterator());
+        }
+    }
+
+    @Override
+    public ListIterator<T> listIterator(int index) {
+        if (!original) {
+            return source.listIterator(index);
+        } else {
+            return new CopyIterator(source.listIterator(index));
+        }
+    }
+
+    @Override
+    public List<T> subList(int fromIndex, int toIndex) {
+        if(!original) {
+            return source.subList(fromIndex, toIndex);
+        } else {
+            return new CopyFragment(null, fromIndex, toIndex);
+        }
+    }
+
+    @Override
+    public Spliterator<T> spliterator() {
+        return source.spliterator();
+    }
+
+    @Override
+    public Stream<T> stream() {
+        return source.stream();
+    }
+
+    @Override
+    public Stream<T> parallelStream() {
+        return source.parallelStream();
+    }
+
+    @Override
+    public int hashCode() {
+        return source.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj instanceof CopyList) {
+            return source.equals(((CopyList<?>)obj).source);
+        }
+        return source.equals(obj);
+    }
+
+    @Override
+    public String toString() {
+        return source.toString();
+    }
+
+    private void writeObject(java.io.ObjectOutputStream ostream) throws java.io.IOException {
+        ostream.defaultWriteObject();
+        ostream.writeInt(source.size());
+        for(T v : source) {
+            ostream.writeObject(v);
+        }
+    }
+
+    private void readObject(java.io.ObjectInputStream istream) throws java.io.IOException, ClassNotFoundException {
+        istream.defaultReadObject();
+        int size = istream.readInt();
+        source = new ArrayList<>(size);
+        for(int i=0; i<size; i++) {
+            source.add((T)istream.readObject());
+        }
+        original = false;
+    }
+
+    private void detach() {
+        if(original) {
+            source = new ArrayList<>(source);
+            original = false;
+        }
+    }
+
+    private class CopyIterator implements ListIterator<T> {
+
+        private ListIterator<T> itr;
+
+        private int index = -1;
+
+        public CopyIterator(ListIterator<T> itr) {
+            this.itr = itr;
+        }
+
+        @Override
+        public boolean hasNext() {
+            return itr.hasNext();
+        }
+
+        @Override
+        public T next() {
+            index = itr.nextIndex();
+            return itr.next();
+        }
+
+        @Override
+        public boolean hasPrevious() {
+            return itr.hasPrevious();
+        }
+
+        @Override
+        public T previous() {
+            index = itr.previousIndex();
+            return itr.previous();
+        }
+
+        @Override
+        public int nextIndex() {
+            return itr.nextIndex();
+        }
+
+        @Override
+        public int previousIndex() {
+            return itr.previousIndex();
+        }
+
+        @Override
+        public void set(T t) {
+            if(!original) {
+                itr.set(t);
+                return;
+            }
+            if(index == -1) {
+                throw new IllegalStateException();
+            }
+            CopyList.this.set(index, t);
+            itr = source.listIterator(index+1);
+        }
+
+        @Override
+        public void add(T t) {
+            if(!original) {
+                itr.add(t);
+                return;
+            }
+            CopyList.this.add(itr.nextIndex(), t);
+            itr = source.listIterator(itr.nextIndex()+1);
+        }
+
+        @Override
+        public void remove() {
+            if(!original) {
+                itr.remove();
+                return;
+            }
+            if(index == -1) {
+                throw new IllegalStateException();
+            }
+            CopyList.this.remove(index);
+            itr = source.listIterator(index);
+        }
+    }
+
+    private class CopyFragment extends AbstractList<T> implements Serializable {
+
+        private final CopyFragment parent;
+
+        private final int offset;
+
+        private int size;
+
+        CopyFragment(CopyFragment parent, int fromIndex, int toIndex) {
+            this.parent = parent;
+            if (fromIndex < 0) {
+                throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
+            }
+            if (toIndex > source.size()) {
+                throw new IndexOutOfBoundsException("toIndex = " + toIndex);
+            }
+            if (fromIndex > toIndex) {
+                throw new IllegalArgumentException("fromIndex(" + fromIndex +") > toIndex(" + toIndex + ")");
+            }
+            offset = fromIndex;
+            size = toIndex - fromIndex;
+        }
+
+        public T set(int index, T element) {
+            rangeCheck(index);
+            return CopyList.this.set(index+offset, element);
+        }
+
+        public T get(int index) {
+            rangeCheck(index);
+            return source.get(index+offset);
+        }
+
+        public int size() {
+            return size;
+        }
+
+        public void add(int index, T element) {
+            rangeCheckForAdd(index);
+            CopyList.this.add(index+offset, element);
+            size++;
+            if(parent != null) {
+                parent.size++;
+            }
+        }
+
+        public T remove(int index) {
+            rangeCheck(index);
+            T result = CopyList.this.remove(index+offset);
+            size--;
+            if(parent != null) {
+                parent.size--;
+            }
+            return result;
+        }
+
+        protected void removeRange(int fromIndex, int toIndex) {
+            detach();
+            source.subList(offset+fromIndex, offset+toIndex).clear();
+            size -= (toIndex-fromIndex);
+            if(parent != null) {
+                parent.size -= (toIndex-fromIndex);
+            }
+        }
+
+        public boolean addAll(Collection<? extends T> c) {
+            return addAll(size, c);
+        }
+
+        public boolean addAll(int index, Collection<? extends T> c) {
+            rangeCheckForAdd(index);
+            int n = c.size();
+            if (n==0) {
+                return false;
+            }
+
+            CopyList.this.addAll(offset+index, c);
+            size += n;
+            if(parent != null) {
+                parent.size += n;
+            }
+            return true;
+        }
+
+        public Iterator<T> iterator() {
+            return listIterator();
+        }
+
+        public ListIterator<T> listIterator(final int index) {
+            rangeCheckForAdd(index);
+            return new FragmentIterator(index);
+        }
+
+        public List<T> subList(int fromIndex, int toIndex) {
+            rangeCheck(fromIndex);
+            rangeCheckForAdd(toIndex);
+            return new CopyFragment(this, offset+fromIndex, offset+toIndex);
+        }
+
+        protected Object writeReplace() {
+            return new CopyList<>(this);
+        }
+
+        private void rangeCheck(int index) {
+            if (index < 0 || index >= size)
+                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
+        }
+
+        private void rangeCheckForAdd(int index) {
+            if (index < 0 || index > size)
+                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
+        }
+
+        private String outOfBoundsMsg(int index) {
+            return "Index: "+index+", Size: "+size;
+        }
+
+        private class FragmentIterator implements ListIterator<T> {
+            private final ListIterator<T> i;
+
+            public FragmentIterator(int index) {
+                i = CopyList.this.listIterator(index + offset);
+            }
+
+            public boolean hasNext() {
+                return nextIndex() < size;
+            }
+
+            public T next() {
+                if (hasNext()) {
+                    return i.next();
+                } else {
+                    throw new NoSuchElementException();
+                }
+            }
+
+            public boolean hasPrevious() {
+                return previousIndex() >= 0;
+            }
+
+            public T previous() {
+                if (hasPrevious()) {
+                    return i.previous();
+                } else {
+                    throw new NoSuchElementException();
+                }
+            }
+
+            public int nextIndex() {
+                return i.nextIndex() - offset;
+            }
+
+            public int previousIndex() {
+                return i.previousIndex() - offset;
+            }
+
+            public void remove() {
+                i.remove();
+                size--;
+                if(parent != null) {
+                    parent.size--;
+                }
+            }
+
+            public void set(T e) {
+                i.set(e);
+            }
+
+            public void add(T e) {
+                i.add(e);
+                size++;
+                if(parent != null) {
+                    parent.size++;
+                }
+            }
+        }
+    }
+
+}

+ 40 - 0
assira.core/src/test/java/net/ranides/assira/collection/lists/CopyListTest.java

@@ -0,0 +1,40 @@
+package net.ranides.assira.collection.lists;
+
+import net.ranides.test.ContractTesters;
+import net.ranides.test.data.TCollection;
+import net.ranides.test.data.TMaps;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.ListIterator;
+
+import static org.junit.Assert.*;
+
+public class CopyListTest {
+    private final TCollection<Integer> $var = TMaps.MAP_IS.keys();
+
+    @Test
+    public void testSuite() {
+        ContractTesters.runner()
+            .param("collection!", $var)
+            .function(new int[0], array -> new CopyList<>($var.list(array).values()))
+            .run();
+    }
+
+    @Test
+    public void testIterator() {
+        ArrayList<Integer> source = new ArrayList<>(Arrays.asList(7, 8, 9));
+        CopyList<Integer> list = new CopyList<>(source);
+
+        ListIterator<Integer> itr = list.listIterator();
+        itr.next();
+        itr.add(11);
+        itr.add(12);
+
+        assertEquals((Integer)8, itr.next());
+        assertEquals(Arrays.asList(7,8,9), source);
+        assertEquals(Arrays.asList(7,11,12,8,9), list);
+    }
+}