public class BoxFamily {

    private static final InstanceStore<BoxFamily> INSTANCE_STORE = new InstanceStore<BoxFamily>() {
        @Override
        public BoxFamily getNew(Object... args) {
            return new BoxFamily((Class<?>) args[0], (String) args[1]);
        }
    };

    private final String name;
    private final Class<?> clazz;

    private ParticipantList<ChangeMiddleware> changeMiddlewares = new ParticipantList<ChangeMiddleware>();
    private ParticipantList<ChangeObserver> changeObservers = new ParticipantList<ChangeObserver>();
    private ParticipantList<GetMiddleware> getMiddlewares = new ParticipantList<GetMiddleware>();
    private ParticipantList<GetObserver> getObservers = new ParticipantList<GetObserver>();

    private boolean showsValueStrings = true;

    /**
     * Obtain an instance of {@code BoxFamily} identified by the given class and name.
     * Repeated calls with the same arguments return the same instance.
     *
     * @param clazz the class in which the boxes belonging to this family are declared.
     *              This can be obtained later by {@link BoxFamily#getDeclaringClass()}
     * @param name  the name of the boxes belonging to this family.
     *              This can be obtained later by {@link BoxFamily#getName()}
     * @return the unique instance with this class and name.
     */
    public static BoxFamily getInstance(Class<?> clazz, String name) {
        return INSTANCE_STORE.get(clazz, name);
    }

    private BoxFamily(Class<?> clazz, String name) {
        Objects.requireNonNull(clazz, "Class must not be null");
        Objects.requireNonNull(name, "Name must not be null");
        this.clazz = clazz;
        this.name = name;
    }

    /**
     * Return the name of the boxes belonging to this family.
     */
    public String getName() {
        return name;
    }

    /**
     * Return the class in which the boxes belonging to this family are declared.
     */
    public Class<?> getDeclaringClass() {
        return clazz;
    }

    /**
     * Add {@link ChangeMiddleware}s which will be applied by boxes belonging to this family
     * when {@link PowerBox#set(Object)} is called.
     * Arguments equal to instances that have already been added will be ignored.
     *
     * @return this object for chaining
     */
    public BoxFamily addChangeMiddleware(ChangeMiddleware... middlewares) {
        return add(changeMiddlewares, (Object[]) middlewares);
    }

    /**
     * Add {@link GetMiddleware}s which will be applied by boxes belonging to this family
     * when {@link PowerBox#get()} is called.
     * Arguments equal to instances that have already been added will be ignored.
     *
     * @return this object for chaining
     */
    public BoxFamily addGetMiddleware(GetMiddleware... middlewares) {
        return add(getMiddlewares, (Object[]) middlewares);
    }

    /**
     * Add {@link ChangeObserver}s which will be applied by boxes belonging to this family
     * when the value of the box changes.
     * Arguments equal to instances that have already been added will be ignored.
     *
     * @return this object for chaining
     */
    public BoxFamily addChangeObserver(ChangeObserver... observers) {
        return add(changeObservers, (Object[]) observers);
    }

    /**
     * Add {@link GetObserver}s which will be applied by boxes belonging to this family
     * when {@link PowerBox#get()} is called.
     * Arguments equal to instances that have already been added will be ignored.
     *
     * @return this object for chaining
     */
    public BoxFamily addGetObserver(GetObserver... observers) {
        return add(getObservers, (Object[]) observers);
    }

    private BoxFamily add(List list, Object... participants) {
        Collections.addAll(list, participants);
        return this;
    }

    /**
     * Get the {@link ChangeMiddleware}s of this family.
     */
    public ParticipantList<ChangeMiddleware> getChangeMiddlewares() {
        return changeMiddlewares;
    }

    /**
     * Get the {@link ChangeObserver}s of this family.
     */
    public ParticipantList<ChangeObserver> getChangeObservers() {
        return changeObservers;
    }

    /**
     * Get the {@link GetMiddleware}s of this family.
     */
    public ParticipantList<GetMiddleware> getGetMiddlewares() {
        return getMiddlewares;
    }

    /**
     * Get the {@link GetObserver}s of this family.
     */
    public ParticipantList<GetObserver> getGetObservers() {
        return getObservers;
    }

    /**
     * A simple human readable description of the boxes belonging to this family, equal to:
     * <p>
     * {@code getDeclaringClass().getSimpleName() + "." + getName()}
     */
    public String description() {
        return getDeclaringClass().getSimpleName() + "." + getName();
    }

    @Override
    public String toString() {
        return "BoxFamily " + description();
    }

    /**
     * Return whether boxes in this family should show the string representations of their values in
     * {@code toString()} and in exception messages. By default this returns {@code true}.
     * If {@link BoxFamily#hideValueStrings()} has been called it returns {@code false}.
     */
    public boolean showsValueStrings() {
        return showsValueStrings;
    }

    /**
     * Prevent boxes in this family from showing the string representations of their values in
     * {@code toString()} and in exception messages. This is to avoid leaking sensitive data in logs
     * or computing expensive string representations. It is still possible to explicitly
     * obtain the string representation if desired.
     *
     * @see BoxFamily#showsValueStrings()
     * @see AbstractPowerBox#toString()
     * @see WrapperBox#revealedToString()
     */
    public BoxFamily hideValueStrings() {
        showsValueStrings = false;
        return this;
    }

    /**
     * Convenience method equivalent to {@code addChangeObserver(ThrowOnNull.INSTANCE)}.
     *
     * @return this object for chaining
     */
    public BoxFamily notNull() {
        return addChangeObserver(ThrowOnNull.I);
    }

    /**
     * A thread-safe, append-only list of observers/middleware. All modifications other than {@code add()}
     * are forbidden.
     *
     * @see ParticipantList#disable()
     */
    public static class ParticipantList<E> extends ForwardingList<E> {

        private ParticipantList() {
        }

        private volatile List<E> inner = new CopyOnWriteArrayList<E>();

        @Override
        public boolean add(E e) {
            if (inner == Collections.emptyList()) {
                throw new UnsupportedOperationException("This kind of observer/middleware is disabled for this BoxFamily.");
            }
            return ((CopyOnWriteArrayList<E>) inner).addIfAbsent(e);
        }

        /**
         * Clear this list and prevent any further modifications to it. For example:
         * <p>
         * {@code box.getFamily().getChangeMiddlewares().disable();}
         * <p>
         * means that {@code ChangeMiddleware} can't be used on boxes in this family.
         */
        public void disable() {
            inner = Collections.emptyList();
        }

        @Override
        protected List<E> delegate() {
            return inner;
        }

        @Override
        public E set(int index, E element) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void add(int index, E element) {
            throw new UnsupportedOperationException();
        }

        @Override
        public E remove(int index) {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean addAll(int index, Collection<? extends E> c) {
            throw new UnsupportedOperationException();
        }

        @Override
        public ListIterator<E> listIterator() {
            return listIterator(0);
        }

        @Override
        public ListIterator<E> listIterator(final int index) {
            final ListIterator<E> delegateIterator = delegate().listIterator(index);
            return new ForwardingListIterator<E>() {

                @Override
                protected ListIterator<E> delegate() {
                    return delegateIterator;
                }

                @Override
                public void remove() {
                    throw new UnsupportedOperationException();
                }

                @Override
                public void set(E e) {
                    throw new UnsupportedOperationException();
                }

                @Override
                public void add(E e) {
                    throw new UnsupportedOperationException();
                }

            };
        }

        @Override
        public List<E> subList(int fromIndex, int toIndex) {
            return Collections.unmodifiableList(delegate().subList(fromIndex, toIndex));
        }

        @Override
        public Iterator<E> iterator() {
            final Iterator<E> delegateIterator = delegate().iterator();
            return new ForwardingIterator<E>() {

                @Override
                protected Iterator<E> delegate() {
                    return delegateIterator;
                }

                @Override
                public void remove() {
                    throw new UnsupportedOperationException();
                }
            };
        }

        @Override
        public boolean remove(Object o) {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean addAll(Collection<? extends E> coll) {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean removeAll(Collection<?> coll) {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean retainAll(Collection<?> coll) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void clear() {
            throw new UnsupportedOperationException();
        }

    }

} public interface PowerBox<T> extends Box<T> {

    /**
     * Return the result of applying the {@link GetMiddleware}s added to the box's family, in the order they were
     * added, to the value stored by this box, then similarly notify the {@link GetObserver}s.
     */
    @Override
    T get();

    /**
     * Change the value stored by this box to the result of applying the {@link ChangeMiddleware}s
     * added to the box's family, in the order they were added, to the given parameter, then similarly notify
     * the {@link ChangeObserver}s.
     */
    @Override
    PowerBox<T> set(T value);

    /**
     * Add {@link ChangeMiddleware}s to this box's family.
     * Arguments equal to instances that have already been added will be ignored.
     *
     * @return this object for chaining
     */
    PowerBox<T> addChangeMiddleware(ChangeMiddleware... middlewares);

    /**
     * Add {@link GetMiddleware}s to this box's family.
     * Arguments equal to instances that have already been added will be ignored.
     *
     * @return this object for chaining
     */
    PowerBox<T> addGetMiddleware(GetMiddleware... middlewares);

    /**
     * Add {@link ChangeObserver}s to this box's family.
     * Arguments equal to instances that have already been added will be ignored.
     *
     * @return this object for chaining
     */
    PowerBox<T> addChangeObserver(ChangeObserver... observers);

    /**
     * Add {@link GetObserver}s to this box's family.
     * Arguments equal to instances that have already been added will be ignored.
     *
     * @return this object for chaining
     */
    PowerBox<T> addGetObserver(GetObserver... observers);

    /**
     * Return the family that this box belongs to.
     */
    BoxFamily getFamily();
} public abstract class WrapperBox<T> extends CommonBox<T> {

    protected static final TargetedChangeObserver<?, WrapperBox> TARGETED_CHANGE_OBSERVER = new TargetedChangeObserver<Object, WrapperBox>() {
        @Override
        public void onChange(PowerBox box, Object originalValue, Object finalValue, Object requestedValue, WrapperBox target) {
            target.change();
        }
    };

    public WrapperBox(BoxFamily family) {
        super(family);
        construct();
    }

    public WrapperBox(Class<?> clazz, String name) {
        super(clazz, name);
        construct();
    }

    private void construct() {
        getFamily().getGetMiddlewares().disable();
        getFamily().getGetObservers().disable();
    }

    /**
     * Notify all {@link ChangeObserver}s. The parameters to
     * {@link ChangeObserver#onChange(PowerBox, Object, Object, Object)} will all be this object. Call this method
     * whenever the internal state of this value might have changed in such a way that is externally visible.
     */
    protected void change() {
        T thisValue = get();
        notifyChangeObservers(thisValue, thisValue, thisValue);
    }

    /**
     * Call {@link WrapperBox#change()} and return the given value. This is just easier and shorter than manually
     * saving the return value, calling {@code change()}, and returning the saved value.
     *
     * @param returnValue the value to return
     * @return the parameter {@code returnValue}
     */
    protected <R> R change(R returnValue) {
        change();
        return returnValue;
    }

    /**
     * Similar to {@link WrapperBox#change(Object)}, but only calls {@link WrapperBox#change()} if the parameter is
     * {@code true}. As well as being slightly nicer than a full if statement, this is especially convenient when
     * the return value is an indication of whether there was a change, e.g. see {@link CollectionBox#add(Object)}.
     *
     * @param condition the return value, and also whether the box's inner value has changed.
     * @return the parameter {@code condition}.
     */
    protected boolean changeIf(boolean condition) {
        if (condition) {
            change();
        }
        return condition;
    }

    @Override
    protected T rawGet() {
        return get();
    }

    /**
     * Return this object.
     */
    @Override
    public abstract T get();

    @Override
    protected T toStringSource() {
        return value;
    }

    /**
     * Return the string representation of the underlying value (or {@code "null"} if the value is {@code null}),
     * even if string values should be hidden for this box.
     *
     * @see BoxFamily#hideValueStrings()
     * @see WrapperBox#toString()
     */
    public String revealedToString() {
        return String.valueOf(value);
    }

    /**
     * Return whether the underlying value is equal to the argument, handling nulls appropriately.
     * To avoid breaking symmetry in the equals contract, it is essential that implementations of the interface that
     * this box contains allow equality between different implementations of the interface. For example, a
     * {@code LinkedList} can be equal to an {@code ArrayList} because they check {@code obj instanceof List}, not e.g.
     * {@code obj instanceof LinkedList}. Without this {@code ListBox} would not be possible.
     */
    @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
    @Override
    public boolean equals(Object obj) {
        //noinspection EqualsBetweenInconvertibleTypes
        return Objects.equals(value, obj);
    }

    /**
     * Return the hash code of the underlying value, handling nulls appropriately.
     */
    @Override
    public int hashCode() {
        return Objects.hashCode(value);
    }

    /**
     * Return whether the underlying value is null.
     */
    public boolean isNull() {
        return value == null;
    }

} public class ListBox<E> extends CollectionBox<List<E>, E> implements List<E> {

    private static final BoxFamily SUB_LIST_FAMILY = BoxFamily.getInstance(ListBox.class, "subList");

    /**
     * Construct a {@code ListBox} belonging to the given family.
     */
    public ListBox(BoxFamily family) {
        super(family);
    }

    /**
     * Construct a {@code ListBox} belonging to a family identified by the given class a name.
     */
    public ListBox(Class<?> clazz, String name) {
        super(clazz, name);
    }

    @Override
    public List<E> get() {
        return this;
    }

    /**
     * A convenience method that sets the value to an empty {@code ArrayList}.
     *
     * @return this object for chaining
     */
    public ListBox<E> init() {
        return set(new ArrayList<E>());
    }

    @Override
    public boolean addAll(int index, Collection<? extends E> c) {
        return changeIf(value.addAll(index, c));
    }

    @Override
    public E get(int index) {
        return value.get(index);
    }

    @Override
    public E set(int index, E element) {
        return change(value.set(index, element));
    }

    @Override
    public void add(int index, E element) {
        value.add(index, element);
        change();
    }

    @Override
    public E remove(int index) {
        return change(value.remove(index));
    }

    @Override
    public int indexOf(Object o) {
        return value.indexOf(o);
    }

    @Override
    public int lastIndexOf(Object o) {
        return value.lastIndexOf(o);
    }

    @Override
    public ListIterator<E> listIterator() {
        return listIterator(0);
    }

    @Override
    public ListIterator<E> listIterator(int index) {
        final ListIterator<E> delegate = value.listIterator(index);
        return new ForwardingListIterator<E>() {

            @Override
            protected ListIterator<E> delegate() {
                return delegate;
            }

            @Override
            public void remove() {
                super.remove();
                change();
            }

            @Override
            public void set(E e) {
                super.set(e);
                change();
            }

            @Override
            public void add(E e) {
                super.add(e);
                change();
            }
        };
    }

    @Override
    public List<E> subList(int fromIndex, int toIndex) {
        ListBox<E> subList = new ListBox<E>(SUB_LIST_FAMILY)
                .set(value.subList(fromIndex, toIndex));
        //noinspection unchecked
        TARGETED_CHANGE_OBSERVER.register((PowerBox) subList, this);
        return subList;
    }

    // Specifying the return type for chaining

    @Override
    public ListBox<E> set(List<E> value) {
        super.set(value);
        return this;
    }

    @Override
    public ListBox<E> addChangeMiddleware(ChangeMiddleware... middlewares) {
        super.addChangeMiddleware(middlewares);
        return this;
    }

    @Override
    public ListBox<E> addChangeObserver(ChangeObserver... observers) {
        super.addChangeObserver(observers);
        return this;
    }

} public class MapBox<K, V> extends WrapperBox<Map<K, V>> implements Map<K, V> {

    private static final BoxFamily KEY_SET_FAMILY = BoxFamily.getInstance(MapBox.class, "keySet");
    private static final BoxFamily ENTRY_SET_FAMILY = BoxFamily.getInstance(MapBox.class, "entrySet");

    /**
     * Construct a {@code MapBox} belonging to the given family.
     */
    public MapBox(BoxFamily family) {
        super(family);
    }

    /**
     * Construct a {@code MapBox} belonging to a family identified by the given class a name.
     */
    public MapBox(Class<?> clazz, String name) {
        super(clazz, name);
    }

    @Override
    public Map<K, V> get() {
        return this;
    }

    /**
     * A convenience method that sets the value to an empty {@code HashMap}.
     *
     * @return this object for chaining
     */
    public MapBox<K, V> init() {
        set(new HashMap<K, V>());
        return this;
    }

    @Override
    public int size() {
        return value.size();
    }

    @Override
    public boolean isEmpty() {
        return value.isEmpty();
    }

    @Override
    public boolean containsKey(Object key) {
        return value.containsKey(key);
    }

    @Override
    public boolean containsValue(Object value) {
        return this.value.containsValue(value);
    }

    @Override
    public V get(Object key) {
        return value.get(key);
    }

    @Override
    public V put(K key, V value) {
        return change(this.value.put(key, value));
    }

    @Override
    public V remove(Object key) {
        return change(value.remove(key));
    }

    @Override
    public void putAll(Map<? extends K, ? extends V> m) {
        value.putAll(m);
        change();
    }

    @Override
    public void clear() {
        value.clear();
        change();
    }

    @Override
    public Set<K> keySet() {
        return watchedSet(this.value.keySet(), KEY_SET_FAMILY);
    }

    private <T> Set<T> watchedSet(Set<T> innerSet, BoxFamily family) {
        SetBox<T> setBox = new SetBox<T>(family).set(innerSet);
        //noinspection unchecked
        TARGETED_CHANGE_OBSERVER.register((PowerBox) setBox, this);
        return setBox;
    }

    @Override
    public Collection<V> values() {
        return value.values();
    }

    @Override
    public Set<Entry<K, V>> entrySet() {
        final Set<Entry<K, V>> delegateSet = value.entrySet();
        Set<Entry<K, V>> innerSet = new ForwardingSet<Entry<K, V>>() {
            @Override
            protected Set<Entry<K, V>> delegate() {
                return delegateSet;
            }

            @Override
            public Iterator<Entry<K, V>> iterator() {
                final Iterator<Entry<K, V>> delegateIterator = super.iterator();
                return new ForwardingIterator<Entry<K, V>>() {
                    @Override
                    protected Iterator<Entry<K, V>> delegate() {
                        return delegateIterator;
                    }

                    @Override
                    public Entry<K, V> next() {
                        final Entry<K, V> delegateEntry = super.next();
                        return new ForwardingMapEntry<K, V>() {
                            @Override
                            protected Entry<K, V> delegate() {
                                return delegateEntry;
                            }

                            @Override
                            public V setValue(V value) {
                                return change(super.setValue(value));
                            }
                        };
                    }
                };
            }
        };
        return watchedSet(innerSet, ENTRY_SET_FAMILY);
    }

    // Specifying the return type for chaining

    @Override
    public MapBox<K, V> set(Map<K, V> value) {
        super.set(value);
        return this;
    }

    @Override
    public MapBox<K, V> addChangeMiddleware(ChangeMiddleware... middlewares) {
        super.addChangeMiddleware(middlewares);
        return this;
    }

    @Override
    public MapBox<K, V> addChangeObserver(ChangeObserver... observers) {
        super.addChangeObserver(observers);
        return this;
    }

} public class SetBox<E> extends CollectionBox<Set<E>, E> implements Set<E> {

    /**
     * Construct a {@code SetBox} belonging to the given family.
     */
    public SetBox(BoxFamily family) {
        super(family);
    }

    /**
     * Construct a {@code SetBox} belonging to a family identified by the given class a name.
     */
    public SetBox(Class<?> clazz, String name) {
        super(clazz, name);
    }

    @Override
    public Set<E> get() {
        return this;
    }

    /**
     * A convenience method that sets the value to an empty {@code HashSet}.
     *
     * @return this object for chaining
     */
    public SetBox<E> init() {
        set(new HashSet<E>());
        return this;
    }

    // Specifying the return type for chaining

    @Override
    public SetBox<E> set(Set<E> value) {
        super.set(value);
        return this;
    }

    @Override
    public SetBox<E> addChangeMiddleware(ChangeMiddleware... middlewares) {
        super.addChangeMiddleware(middlewares);
        return this;
    }

    @Override
    public SetBox<E> addChangeObserver(ChangeObserver... observers) {
        super.addChangeObserver(observers);
        return this;
    }

} public class BoxParticipantError extends Error {

    public Box<ParticipationDetails> details = box();

    public BoxParticipantError(String message, Error cause) {
        super(message, cause);
    }

    public BoxParticipantError withDetails(ParticipationDetails details) {
        this.details.set(details);
        return this;
    }
} public interface ChangeMiddleware<T> {

    /**
     * Return the given {@code currentValue} or some transformation of it.
     * Called during {@link PowerBox#set(Object)} for zero or more of these objects before applying the list of
     * {@link ChangeObserver}s.
     *
     * @param box            the {@code PowerBox} whose value is being {@code set}.
     * @param originalValue  the value the box contained before it was set.
     * @param currentValue   for the first middleware in the sequence, this is the same as {@code requestedValue}.
     *                       For subsequent middleware, this is the return value of the previous middleware.
     * @param requestedValue the parameter of {@link PowerBox#set(Object)}.
     * @return the final value to be stored in the box and the parameter {@code finalValue} in the
     * {@link ChangeObserver#onChange(PowerBox, Object, Object, Object)} method for any {@code ChangeObserver}s this
     * box has.
     */
    T onChange(PowerBox<T> box, T originalValue, T currentValue, T requestedValue);
} public class DefaultValue extends SymmetricMiddleware {

    private static final InstanceStore<DefaultValue> INSTANCE_STORE = new InstanceStore<DefaultValue>() {
        @Override
        public DefaultValue getNew(Object... args) {
            return new DefaultValue(args[0]);
        }
    };

    private final Object value;

    private DefaultValue(Object defaultValue) {
        this.value = defaultValue;
    }

    public static DefaultValue getInstance(Object value) {
        return INSTANCE_STORE.get(value);
    }

    @Override
    public Object apply(PowerBox box, Object firstValue, Object currentValue) {
        if (currentValue == null) {
            return value;
        }
        return currentValue;
    }
} public interface ChangeObserver<T> {

    /**
     * Take some action based on the values involved in the change.
     * Called when the value of a {@code PowerBox} changes for zero or more of these objects.
     *
     * @param box            the {@code PowerBox} whose value changed.
     * @param originalValue  the value the box contained before the change.
     * @param finalValue     the new value the box will have. In the case of {@link PowerBox#set(Object)},
     *                       this is the final result of applying all {@link ChangeMiddleware}.
     * @param requestedValue the parameter of {@link PowerBox#set(Object)}, or just the same as {@code originalValue}
     *                       in the case of a mutation to a {@code WrapperBox}.
     */
    void onChange(PowerBox<T> box, T originalValue, T finalValue, T requestedValue);
}
import alex.mojaki.boxes.BoxFamily;
import alex.mojaki.boxes.PowerBox;
import alex.mojaki.boxes.WrapperBox;
import alex.mojaki.boxes.collections.ListBox;
import alex.mojaki.boxes.collections.MapBox;
import alex.mojaki.boxes.collections.SetBox;
import alex.mojaki.boxes.exceptions.BoxParticipantError;
import alex.mojaki.boxes.middleware.change.ChangeMiddleware;
import alex.mojaki.boxes.middleware.symmetric.DefaultValue;
import alex.mojaki.boxes.observers.change.ChangeObserver;
import org.junit.Test;
import java.util.Arrays;
import java.util.Collections;
import static alex.mojaki.boxes.utils.Utils.describeChange;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertSame;
import static org.junit.Assert.*;
package alex.mojaki.boxes.test;




public class WrapperBoxTest {

    @Test
    public void testWrapperCommon() {
        for (WrapperBox box : new WrapperBox[]{
                new ListBox<Integer>(BoxFamily.getInstance(WrapperBoxTest.class, "list")),
                new SetBox<Integer>(BoxFamily.getInstance(WrapperBoxTest.class, "set")),
                new MapBox<Integer, Integer>(BoxFamily.getInstance(WrapperBoxTest.class, "map"))
        }) {
            // Box does not reveal self
            assertSame(box, box.get());

            // addChangeMiddleware behaves
            ChangeMiddleware middleware = DefaultValue.getInstance(3);