// // Copyright © 2014, David Tesler (https://github.com/protobufel) // 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 the <organization> 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 <COPYRIGHT HOLDER> 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 com.github.protobufel.util; import java.io.ObjectInputStream.GetField; import java.io.ObjectOutputStream; import java.io.ObjectOutputStream.PutField; import java.io.ObjectStreamField; import java.io.Serializable; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.RandomAccess; import java.util.concurrent.atomic.AtomicBoolean; final class MutableControlCollections { private MutableControlCollections() {} public static final <E> List<E> mutableControlList(final List<E> delegate, final Immutable immutable) { if (delegate instanceof RandomAccess) { return new MutableControlRandomAccessList<E>(delegate, immutable); } else { return new MutableControlList<E>(delegate, immutable); } } public interface Immutable { boolean isImmutable(); } public interface IImmutableState extends Immutable { boolean makeImmutable(); } public static class ImmutableStates { public static final class BooleanImmutableState implements IImmutableState, Serializable { private static final long serialVersionUID = 553811341266855092L; private final AtomicBoolean isImmutable; public BooleanImmutableState(final boolean isImmutable) { this.isImmutable = new AtomicBoolean(isImmutable); } @Override public boolean isImmutable() { return isImmutable.get(); } @Override public boolean makeImmutable() { return isImmutable.compareAndSet(false, true); } } public static final class ReferenceImmutableState implements IImmutableState, Serializable { private static final long serialVersionUID = 553811341266855092L; /** * @serialField isImmutable Boolean the immutable status of this object */ private static final ObjectStreamField[] serialPersistentFields = {new ObjectStreamField( "isImmutable", Boolean.class)}; // private final AtomicBoolean isImmutable; private WeakReference<IImmutableState> reference; public ReferenceImmutableState(final IImmutableState reference) { this.reference = new WeakReference<IImmutableState>(reference); // this.isImmutable = new AtomicBoolean(reference.isImmutable()); } @Override public boolean isImmutable() { if (reference == null) { return true; } IImmutableState immutableState = reference.get(); if (immutableState == null) { immutableState = null; return true; } return immutableState.isImmutable(); } @Override public boolean makeImmutable() { throw new UnsupportedOperationException(); } private void writeObject(final ObjectOutputStream out) throws java.io.IOException { final PutField fields = out.putFields(); fields.put("isImmutable", isImmutable()); out.writeFields(); } /** * Restores the object to the always immutable state! */ private void readObject(final java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException { final GetField fields = in.readFields(); // ignore it, as we always deserialize this object to immutable state fields.get("isImmutable", true); // make sure the object is immutable! reference = null; } } } private static class MutableControlList<E> implements List<E>, Immutable, java.io.Serializable { private static final long serialVersionUID = -2643387676083235458L; private static final ObjectStreamField[] serialPersistentFields = { new ObjectStreamField("delegate", List.class), new ObjectStreamField("isImmutable", Boolean.class)}; final List<E> delegate; private final Immutable immutable; public MutableControlList(final List<E> delegate, final Immutable immutable) { this.delegate = delegate; this.immutable = immutable; } /** * Return the mutability status of this list. Sublasses should override this method, as it * defaults to mutable. */ @Override public boolean isImmutable() { return immutable == null || immutable.isImmutable(); } protected Immutable getImmutable() { return immutable; } private void verifyIsMutable() { if (isImmutable()) { throw new UnsupportedOperationException(); } } @Override public int size() { return delegate.size(); } @Override public boolean isEmpty() { return delegate.isEmpty(); } @Override public boolean contains(final Object o) { return delegate.contains(o); } @Override public Object[] toArray() { return delegate.toArray(); } @Override public <T> T[] toArray(final T[] a) { return delegate.toArray(a); } @Override public boolean add(final E e) { verifyIsMutable(); return delegate.add(e); } @Override public boolean remove(final Object o) { verifyIsMutable(); return delegate.remove(o); } @Override public boolean containsAll(final Collection<?> c) { return delegate.containsAll(c); } @Override public boolean addAll(final Collection<? extends E> c) { verifyIsMutable(); return delegate.addAll(c); } @Override public boolean addAll(final int index, final Collection<? extends E> c) { verifyIsMutable(); return delegate.addAll(index, c); } @Override public boolean removeAll(final Collection<?> c) { verifyIsMutable(); return delegate.removeAll(c); } @Override public boolean retainAll(final Collection<?> c) { verifyIsMutable(); return delegate.retainAll(c); } @Override public void clear() { verifyIsMutable(); delegate.clear(); } @Override public boolean equals(final Object o) { // TODO(protobufel): add canEqual()! // TODO(protobufel): should I add boolean isImmutable()? return this == o || delegate.equals(o); } @Override public int hashCode() { // TODO(protobufel): should this be a special case when isImmutable field/value irrelevant? // TODO(protobufel): should I add boolean isImmutable()? return delegate.hashCode(); } @Override public E get(final int index) { return delegate.get(index); } @Override public E set(final int index, final E element) { verifyIsMutable(); return delegate.set(index, element); } @Override public void add(final int index, final E element) { verifyIsMutable(); delegate.add(index, element); } @Override public E remove(final int index) { verifyIsMutable(); return delegate.remove(index); } @Override public int indexOf(final Object o) { return delegate.indexOf(o); } @Override public int lastIndexOf(final Object o) { return delegate.lastIndexOf(o); } @Override public Iterator<E> iterator() { return new Itr(delegate.iterator()); } @Override public ListIterator<E> listIterator() { return new ListItr(delegate.listIterator()); } @Override public ListIterator<E> listIterator(final int index) { return new ListItr(delegate.listIterator(index)); } @Override public List<E> subList(final int fromIndex, final int toIndex) { return new MutableControlList<E>(delegate.subList(fromIndex, toIndex), immutable); } private class Itr implements Iterator<E> { private final Iterator<E> it; public Itr(final Iterator<E> it) { this.it = it; } @Override public boolean hasNext() { return it.hasNext(); } @Override public E next() { return it.next(); } @Override public void remove() { verifyIsMutable(); it.remove(); } } private class ListItr implements ListIterator<E> { private final ListIterator<E> it; public ListItr(final ListIterator<E> it) { this.it = it; } @Override public boolean hasNext() { return it.hasNext(); } @Override public E next() { return it.next(); } @Override public boolean hasPrevious() { return it.hasPrevious(); } @Override public E previous() { return it.previous(); } @Override public int nextIndex() { return it.nextIndex(); } @Override public int previousIndex() { return it.previousIndex(); } @Override public void remove() { verifyIsMutable(); it.remove(); } @Override public void set(final E e) { verifyIsMutable(); it.set(e); } @Override public void add(final E e) { verifyIsMutable(); it.add(e); } } private void writeObject(final ObjectOutputStream out) throws java.io.IOException { final PutField fields = out.putFields(); fields.put("delegate", delegate); // isImmutable in this class is purely informational and ignored by readObject method! fields.put("isImmutable", isImmutable()); out.writeFields(); } /** * Reconstitute an always immutable list! */ private void readObject(final java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException { // isImmutable simply ignored, so the object is immutable! in.defaultReadObject(); } } static class MutableControlRandomAccessList<E> extends MutableControlList<E> implements Serializable, RandomAccess { private static final long serialVersionUID = -37339958348657863L; public MutableControlRandomAccessList(final List<E> delegate, final Immutable immutable) { super(delegate, immutable); } @Override public List<E> subList(final int fromIndex, final int toIndex) { return new MutableControlRandomAccessList<E>(delegate.subList(fromIndex, toIndex), getImmutable()); } } // What to do with this? // FIXME static class ArrayListEx<E> extends ArrayList<E> { private static final long serialVersionUID = 6103893868125258638L; public ArrayListEx() { super(); } public ArrayListEx(final Collection<? extends E> c) { super(c); } public ArrayListEx(final int initialCapacity) { super(initialCapacity); } /** * Switches mutability of this list. Sublasses should override this method, as it defaults to * noop. */ protected boolean setImmutable(final boolean isImmutable) { return false; } /** * Return the mutability status of this list. Sublasses should override this method, as it * defaults to mutable. */ public boolean isImmutable() { return false; } public boolean makeImmutable() { if (isImmutable()) { return false; } return setImmutable(true); } private void verifyIsMutable() { if (isImmutable()) { throw new UnsupportedOperationException(); } } @Override public E set(final int index, final E element) { verifyIsMutable(); return super.set(index, element); } @Override public boolean add(final E e) { verifyIsMutable(); return super.add(e); } @Override public void add(final int index, final E element) { verifyIsMutable(); super.add(index, element); } @Override public E remove(final int index) { verifyIsMutable(); return super.remove(index); } @Override public boolean remove(final Object o) { verifyIsMutable(); return super.remove(o); } @Override public void clear() { verifyIsMutable(); super.clear(); } @Override public boolean addAll(final Collection<? extends E> c) { verifyIsMutable(); return super.addAll(c); } @Override public boolean addAll(final int index, final Collection<? extends E> c) { verifyIsMutable(); return super.addAll(index, c); } @Override public boolean removeAll(final Collection<?> c) { verifyIsMutable(); return super.removeAll(c); } @Override public boolean retainAll(final Collection<?> c) { verifyIsMutable(); return super.retainAll(c); } @Override public List<E> subList(final int fromIndex, final int toIndex) { // verifyIsMutable(); return super.subList(fromIndex, toIndex); } @Override protected void removeRange(final int fromIndex, final int toIndex) { verifyIsMutable(); super.removeRange(fromIndex, toIndex); } } }