All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.libj.util.ObservableCollection Maven / Gradle / Ivy

Go to download

Supplementary utilities for classes that belong to java.util, or are considered essential as to justify existence in java.util.

There is a newer version: 0.9.1
Show newest version
/* Copyright (c) 2017 LibJ
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * You should have received a copy of The MIT License (MIT) along with this
 * program. If not, see .
 */

package org.libj.util;

import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Iterator;
import java.util.Spliterator;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;

/**
 * A {@link DelegateCollection} that provides callback methods to observe the
 * addition and removal of elements, either due to direct method invocation on
 * the collection instance itself, or via {@link #iterator()},
 * {@link #spliterator()}, {@link #forEach(Consumer)}, and any other entrypoint
 * that facilitates modification of the elements in this list.
 *
 * @param  The type of elements in this collection.
 * @see #beforeAdd(Object)
 * @see #afterAdd(Object,RuntimeException)
 * @see #beforeRemove(Object)
 * @see #afterRemove(Object,RuntimeException)
 */
public abstract class ObservableCollection extends DelegateCollection {
  public ObservableCollection(final Collection collection) {
    super(collection);
  }

  /**
   * Callback method that is invoked immediately after a value is retrieved via
   * {@link Iterator#next()}.
   *
   * @param value The value desired to be returned by the
   *          {@link Iterator#next()} operation.
   * @param e A {@link RuntimeException} that occurred during the add
   *          operation, or {@code null} if no exception occurred.
   * @return The value to be returned by the {@link Iterator#next()} operation.
   */
  protected E afterGet(final E value, final RuntimeException e) {
    return value;
  }

  /**
   * Callback method that is invoked immediately before an element is added to
   * the enclosed {@link Collection}.
   *
   * @param element The element to be added to the enclosed {@link Collection}.
   * @return If this method returns {@code true}, the subsequent add
   *         operation will be performed; if this method returns {@code false},
   *         the subsequent add operation will not be performed.
   */
  protected boolean beforeAdd(final E element) {
    return true;
  }

  /**
   * Callback method that is invoked immediately after an element is added to
   * the enclosed {@link Collection}.
   *
   * @param element The element added to the enclosed {@link Collection}.
   * @param e A {@link RuntimeException} that occurred during the add
   *          operation, or {@code null} if no exception occurred.
   */
  protected void afterAdd(final E element, final RuntimeException e) {
  }

  /**
   * Callback method that is invoked immediately before an element is removed
   * from the enclosed {@link Collection}.
   *
   * @param element The element to be removed from the enclosed
   *          {@link Collection}.
   * @return If this method returns {@code true}, the subsequent remove
   *         operation will be performed; if this method returns {@code false},
   *         the subsequent remove operation will not be performed.
   */
  protected boolean beforeRemove(final Object element) {
    return true;
  }

  /**
   * Callback method that is invoked immediately after an element is removed
   * from the enclosed {@link Collection}.
   *
   * @param element The element removed from the enclosed {@link Collection}, or
   *          attempted to be removed from the {@link Collection} in case of a
   *          {@link RuntimeException}.
   * @param e A {@link RuntimeException} that occurred during the add operation,
   *          or {@code null} if no exception occurred.
   */
  protected void afterRemove(final Object element, final RuntimeException e) {
  }

  /**
   * A {@link DelegateIterator} that delegates callback methods to the parent
   * {@link ObservableCollection} instance for the retrieval and removal of
   * elements.
   */
  protected class ObservableIterator extends DelegateIterator {
    private E current;

    /**
     * Creates a new {@link ObservableIterator} for the specified
     * {@link Iterator}.
     *
     * @param iterator The {@link Iterator}.
     * @throws NullPointerException If the specified {@link Iterator} is null.
     */
    protected ObservableIterator(final Iterator iterator) {
      super(iterator);
    }

    @Override
    public E next() {
      RuntimeException exception = null;
      E value = null;
      try {
        value = super.next();
      }
      catch (final RuntimeException re) {
        exception = re;
      }

      value = ObservableCollection.this.afterGet(value, exception);
      if (exception != null)
        throw exception;

      return current = value;
    }

    @Override
    public void remove() {
      final E remove = current;
      if (!ObservableCollection.this.beforeRemove(remove))
        return;

      RuntimeException exception = null;
      try {
        super.remove();
      }
      catch (final RuntimeException re) {
        exception = re;
      }

      ObservableCollection.this.afterRemove(remove, exception);
      if (exception != null)
        throw exception;
    }
  }

  /**
   * Returns an iterator over the elements in this collection. Calling
   * {@link Iterator#remove()} will delegate a callback to
   * {@link #beforeRemove(Object)} and
   * {@link #afterRemove(Object,RuntimeException)} on this instance. All
   * elements for which {@link #beforeRemove(Object)} returns false will not be
   * removed from this collection.
   *
   * @return An {@link Iterator} over the elements in this collection.
   * @see Collection#iterator()
   */
  @Override
  public Iterator iterator() {
    return new ObservableIterator(super.iterator());
  }

  /**
   * {@inheritDoc}
   * 

* The callback method {@link #afterGet(Object,RuntimeException)} is called * immediately after each element of the enclosed collection is retrieved to * be tested for equality. */ @Override public boolean contains(final Object o) { final Iterator iterator = iterator(); if (o == null) { while (iterator.hasNext()) if (iterator.next() == null) return true; } else { while (iterator.hasNext()) if (o.equals(iterator.next())) return true; } return false; } /** * {@inheritDoc} *

* The callback method {@link #afterGet(Object,RuntimeException)} is called * immediately after each element of the enclosed collection is retrieved to * be tested for equality. */ @Override @SuppressWarnings("unlikely-arg-type") public boolean containsAll(final Collection c) { if (c.size() == 0) return true; for (final Object o : c) if (!contains(o)) return false; return true; } /** * {@inheritDoc} *

* The callback methods {@link #beforeAdd(Object)} and * {@link ObservableCollection#afterAdd(Object, RuntimeException)} are called * immediately before and after the enclosed collection is modified. If * {@link #beforeAdd(Object)} returns false, the element will not be added. */ @Override public boolean add(final E e) { final int size = size(); if (!beforeAdd(e)) return size != size(); RuntimeException exception = null; try { super.add(e); } catch (final RuntimeException re) { exception = re; } afterAdd(e, exception); if (exception != null) throw exception; return size != size(); } /** * {@inheritDoc} *

* The callback methods {@link #beforeAdd(Object)} and * {@link #afterAdd(Object,RuntimeException)} are called immediately before * and after the enclosed collection is modified for the addition of each * element in the argument Collection. All elements for which * {@link #beforeAdd(Object)} returns false will not be added to this * collection. */ @Override public boolean addAll(final Collection c) { boolean changed = false; for (final E e : c) changed |= add(e); return changed; } /** * {@inheritDoc} *

* The callback methods {@link #beforeRemove(Object)} and * {@link #afterRemove(Object,RuntimeException)} are called immediately before * and after the enclosed collection is modified. If * {@link #beforeRemove(Object)} returns false, the element will not be * removed. */ @Override @SuppressWarnings("unlikely-arg-type") public boolean remove(final Object o) { final int size = size(); if (!beforeRemove(o)) return size != size(); RuntimeException exception = null; try { super.remove(o); } catch (final RuntimeException re) { exception = re; } afterRemove(o, exception); if (exception != null) throw exception; return size != size(); } /** * {@inheritDoc} *

* The callback methods {@link #beforeRemove(Object)} and * {@link #afterRemove(Object,RuntimeException)} are called immediately * before and after the enclosed collection is modified for the removal of * each element in the argument Collection. All elements for which * {@link #beforeRemove(Object)} returns false will not be removed from this * collection. */ @Override @SuppressWarnings("unlikely-arg-type") public boolean removeAll(final Collection c) { boolean changed = false; for (final Object e : c) changed |= remove(e); return changed; } /** * {@inheritDoc} *

* The callback methods {@link #beforeRemove(Object)} and * {@link #afterRemove(Object,RuntimeException)} are called immediately * before and after the enclosed collection is modified for the removal of * each element. If {@link #beforeRemove(Object)} returns false, the element * will not be removed. */ @Override public boolean removeIf(final Predicate filter) { return superRemoveIf(filter); } /** * {@inheritDoc} *

* The callback methods {@link #beforeRemove(Object)} and * {@link #afterRemove(Object,RuntimeException)} are called immediately * before and after the enclosed collection is modified for the removal of * each element not in the argument Collection. */ @Override @SuppressWarnings("unlikely-arg-type") public boolean retainAll(final Collection c) { boolean changed = false; for (final Iterator i = iterator(); i.hasNext();) { if (!c.contains(i.next())) { i.remove(); changed = true; } } return changed; } /** * {@inheritDoc} *

* The callback methods {@link #beforeRemove(Object)} and * {@link #afterRemove(Object,RuntimeException)} are called immediately * before and after the enclosed collection is modified for the removal of * each element. */ @Override public void clear() { for (final Iterator i = iterator(); i.hasNext();) { i.next(); i.remove(); } } /** * {@inheritDoc} *

* The callback method {@link #afterGet(Object,RuntimeException)} is called * immediately after each element of the enclosed collection is retrieved. */ @Override public Object[] toArray() { if (size() == 0) return ArrayUtil.EMPTY_ARRAY; final Object[] a = new Object[size()]; final Iterator iterator = iterator(); for (int i = 0; iterator.hasNext(); ++i) a[i] = iterator.next(); return a; } /** * {@inheritDoc} *

* The callback method {@link #afterGet(Object,RuntimeException)} is called * immediately after each element of the enclosed collection is retrieved. */ @Override @SuppressWarnings("unchecked") public T[] toArray(T[] a) { if (a.length < size()) a = (T[])Array.newInstance(a.getClass().getComponentType(), size()); int i = 0; for (final Iterator iterator = iterator(); iterator.hasNext(); ++i) a[i] = (T)iterator.next(); if (++i < a.length) a[i] = null; return a; } // /** // * {@inheritDoc} // *

// * The callback method {@link #afterGet(Object,RuntimeException)} is called // * immediately after each element of the enclosed collection is retrieved. // */ // @Override // public T[] toArray(final IntFunction generator) { // return superToArray(generator); // } /** * {@inheritDoc} *

* The callback method {@link #afterGet(Object,RuntimeException)} is called * immediately after each element of the enclosed collection is retrieved. */ @Override public void forEach(final Consumer action) { superForEach(action); } /** * {@inheritDoc} *

* The callback method {@link #afterGet(Object,RuntimeException)} is called * immediately after each element of the enclosed collection is retrieved. */ @Override public Spliterator spliterator() { return superSpliterator(); } /** * {@inheritDoc} *

* The callback method {@link #afterGet(Object,RuntimeException)} is called * immediately after each element of the enclosed collection is retrieved. */ @Override public Stream stream() { return superStream(); } /** * {@inheritDoc} *

* The callback method {@link #afterGet(Object,RuntimeException)} is called * immediately after each element of the enclosed collection is retrieved. */ @Override public Stream parallelStream() { return superParallelStream(); } private void touchElements() { for (final Iterator i = iterator(); i.hasNext(); i.next()); } /** * {@inheritDoc} *

* The callback method {@link #afterGet(Object,RuntimeException)} is called * immediately after each element of the enclosed collection is retrieved. */ @Override public boolean equals(final Object obj) { if (obj == this) return true; if (!(obj instanceof Collection) || size() != ((Collection)obj).size()) return false; touchElements(); return super.equals(obj); } /** * {@inheritDoc} *

* The callback method {@link #afterGet(Object,RuntimeException)} is called * immediately after each element of the enclosed collection is retrieved. */ @Override public int hashCode() { if (target == null) return 31; touchElements(); return 31 + target.hashCode(); } /** * {@inheritDoc} *

* The callback method {@link #afterGet(Object,RuntimeException)} is called * immediately after each element of the enclosed collection is retrieved. */ @Override public String toString() { if (target == null) return "null"; touchElements(); return String.valueOf(target); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy