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 class CommonBox<T> extends DefaultPowerBox<T> {

    private BoxFamily family;

    public CommonBox(BoxFamily family) {
        this.family = family;
    }

    public CommonBox(Class<?> clazz, String name) {
        this(BoxFamily.getInstance(clazz, name));
    }

    @Override
    public BoxFamily getFamily() {
        return family;
    }

} 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 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 class BoxParticipantException extends RuntimeException {

    public Box<ParticipationDetails> details = box();

    public BoxParticipantException(String message, Exception cause) {
        super(message, cause);
    }

    public BoxParticipantException withDetails(ParticipationDetails details) {
        this.details.set(details);
        return this;
    }
} public class ParticipationDetails {

    private final PowerBox box;
    private final int participantIndex;
    private final List participantList;
    private final Class<?> participantType;

    public ParticipationDetails(PowerBox box, int participantIndex, List participantList, Class<?> participantType) {
        this.box = box;
        this.participantIndex = participantIndex;
        this.participantList = participantList;
        this.participantType = participantType;
    }

    public PowerBox getBox() {
        return box;
    }

    /**
     * Return the index of the observer/middleware in the list that was being applied when the error
     * occurred.
     */
    public int getParticipantIndex() {
        return participantIndex;
    }

    /**
     * Return the list of observers/middleware that was being applied when the error
     * occurred.
     */
    public List getParticipantList() {
        return participantList;
    }

    /**
     * Return the interface of the observer/middleware that was being applied, i.e. one of the {@code Class} objects
     * of {@code ChangeMiddleware}, {@code ChangeObserver}, {@code GetMiddleware}, or {@code GetObserver}.
     */
    public Class<?> getParticipantType() {
        return participantType;
    }

    /**
     * Get the observer/middleware object that threw the original exception/error.
     */
    public Object getParticipant() {
        return participantList.get(participantIndex);
    }
} 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 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);
} public class ChangePrinter implements ChangeObserver {

    public static final ChangePrinter I = new ChangePrinter();

    private ChangePrinter() {
    }

    @Override
    public void onChange(PowerBox box, Object originalValue, Object finalValue, Object requestedValue) {
        System.out.println(Utils.describeChange(box, originalValue, finalValue, requestedValue));
    }

} public class RequireBounds<T> implements ChangeObserver<Comparable<T>> {

    private static final InstanceStore<RequireBounds> INSTANCE_STORE = new InstanceStore<RequireBounds>() {
        @Override
        public RequireBounds getNew(Object... args) {
            //noinspection unchecked
            return new RequireBounds((Comparable) args[0], (Boolean) args[1], (Comparable) args[2], (Boolean) args[3]);
        }
    };

    private final Comparable<T> min;
    private final boolean minInclusive;
    private final Comparable<T> max;
    private final boolean maxInclusive;

    private RequireBounds(Comparable<T> min, boolean minInclusive, Comparable<T> max, boolean maxInclusive) {
        if (min == null && max == null) {
            throw new IllegalArgumentException("Both min and max are null");
        }
        if (min != null && max != null) {
            if (lessThan(max, min)) {
                throw new IllegalArgumentException("The maximum (" + max + ") " +
                        "was set to less than the minimum (" + min + ").");
            } else if (min.equals(max) && !(minInclusive && maxInclusive)) {
                throw new IllegalArgumentException("The minimum and maximum are both " + min +
                        " and they are not both inclusive, which is impossible to satisfy.");
            }
        }
        this.min = min;
        this.minInclusive = minInclusive;
        this.max = max;
        this.maxInclusive = maxInclusive;
    }

    public static <T> RequireBounds maximum(Comparable<T> max, boolean inclusive) {
        return between(null, false, max, inclusive);
    }

    public static <T> RequireBounds minimum(Comparable<T> min, boolean inclusive) {
        return between(min, inclusive, null, false);
    }

    public static <T> RequireBounds between(Comparable<T> min, boolean minInclusive, Comparable<T> max, boolean maxInclusive) {
        return INSTANCE_STORE.get(min, minInclusive, max, maxInclusive);
    }

    @Override
    public void onChange(PowerBox<Comparable<T>> box, Comparable<T> originalValue, Comparable<T> finalValue, Comparable<T> requestedValue) {
        if (finalValue == null) {
            return;
        }
        if (min != null) {
            if (lessThan(finalValue, min)) {
                final BoxFamily family = box.getFamily();
                throw new IllegalArgumentException(
                        family.showsValueStrings() ?
                                ("Tried setting " + family.description() +
                                        " to " + finalValue + " which is less than " + min)
                                : "");
            }
            if (!minInclusive && finalValue.equals(min)) {
                final BoxFamily family = box.getFamily();
                throw new IllegalArgumentException(
                        family.showsValueStrings() ?
                                ("Tried setting " + family.description() +
                                        " to " + finalValue + " which is the exclusive minimum")
                                : "");
            }
        }
        if (max != null) {
            if (lessThan(max, finalValue)) {
                final BoxFamily family = box.getFamily();
                throw new IllegalArgumentException(
                        family.showsValueStrings() ?
                                ("Tried setting " + family.description() +
                                        " to " + finalValue + " which is more than " + max)
                                : "");
            }
            if (!maxInclusive && finalValue.equals(max)) {
                final BoxFamily family = box.getFamily();
                throw new IllegalArgumentException(
                        family.showsValueStrings() ?
                                ("Tried setting " + family.description() +
                                        " to " + finalValue + " which is the exclusive maximum")
                                : "");
            }
        }
    }
} public class ThrowOnNull implements ChangeObserver {

    public static final ThrowOnNull I = new ThrowOnNull();
    public static final ThrowOnNull INSTANCE = I;

    private ThrowOnNull() {
    }

    @Override
    public void onChange(PowerBox box, Object originalValue, Object finalValue, Object requestedValue) {
        if (finalValue == null) {
            throw new IllegalArgumentException("Cannot set " + box.getFamily().description() + " to null.");
        }
    }

}
import alex.mojaki.boxes.BoxFamily;
import alex.mojaki.boxes.CommonBox;
import alex.mojaki.boxes.PowerBox;
import alex.mojaki.boxes.exceptions.BoxParticipantError;
import alex.mojaki.boxes.exceptions.BoxParticipantException;
import alex.mojaki.boxes.exceptions.ParticipationDetails;
import alex.mojaki.boxes.middleware.change.ChangeMiddleware;
import alex.mojaki.boxes.observers.change.ChangeObserver;
import alex.mojaki.boxes.observers.change.ChangePrinter;
import alex.mojaki.boxes.observers.change.RequireBounds;
import alex.mojaki.boxes.observers.change.ThrowOnNull;
import org.junit.Test;
import static alex.mojaki.boxes.Boxes.box;
import static junit.framework.TestCase.assertSame;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
package alex.mojaki.boxes.test;



public class ErrorTest {

    @Test
    public void testParticipantException() {
        String name = "exceptionBox";
        ChangeObserver errorObserver = RequireBounds.between(0, true, 10, true);
        Class<? extends Throwable> type = BoxParticipantException.class;
        Class<? extends Throwable> causeClass = IllegalArgumentException.class;
        testThrowable(name, errorObserver, type, causeClass);
    }

    @Test
    public void testParticipantError() {
        String name = "errorBox";
        ChangeObserver errorObserver = new ChangeObserver() {
            @Override
            public void onChange(PowerBox box, Object originalValue, Object finalValue, Object requestedValue) {
                throw new StackOverflowError();
            }
        };
        Class<? extends Throwable> type = BoxParticipantError.class;
        Class<? extends Throwable> causeClass = StackOverflowError.class;
        testThrowable(name, errorObserver, type, causeClass);
    }

    private void testThrowable(String name, ChangeObserver errorObserver, Class<? extends Throwable> type, Class<? extends Throwable> causeClass) {
        PowerBox<Integer> box = box(ErrorTest.class, name);
        box.addChangeObserver(
                ThrowOnNull.INSTANCE,
                errorObserver,
                ChangePrinter.I)
                .addChangeMiddleware(new ChangeMiddleware<Integer>() {