PropertyListView.java

  1. package sprouts.impl;

  2. import org.jspecify.annotations.Nullable;
  3. import sprouts.Observable;
  4. import sprouts.Observer;
  5. import sprouts.*;

  6. import java.lang.ref.WeakReference;
  7. import java.util.*;
  8. import java.util.function.Function;
  9. import java.util.stream.Collectors;

  10. final class PropertyListView<T extends @Nullable Object> implements Viewables<T> {

  11.     public static <T, U> Viewables<U> of(U nullObject, U errorObject, Vals<T> source, Function<T, @Nullable U> mapper) {
  12.         Objects.requireNonNull(nullObject);
  13.         Objects.requireNonNull(errorObject);

  14.         final Class<U> targetType = Util.expectedClassFromItem(nullObject);
  15.         Function<T, U> nonNullMapper = Util.nonNullMapper(nullObject, errorObject, mapper);

  16.         PropertyListView<U> view = new PropertyListView<>(targetType, source.allowsNull(), source);
  17.         Function<Val<T>, Var<U>> sourcePropToViewProp = prop -> {
  18.             return (Var<U>) prop.view(nullObject, errorObject, nonNullMapper);
  19.         };
  20.         for (int i = 0; i < source.size(); i++) {
  21.             Var<U> viewable = sourcePropToViewProp.apply(source.at(i));
  22.             view._variables.add(i, viewable);
  23.         }

  24.         WeakReference<Vals<T>> weakSource = new WeakReference<>(source);
  25.         Viewables.cast(source).onChange(Action.ofWeak(view, (innerView, delegate) -> {
  26.             Vals<T> innerSource = weakSource.get();
  27.             if (innerSource == null) {
  28.                 return;
  29.             }
  30.             switch (delegate.change()) {
  31.                 case NONE:
  32.                     break;
  33.                 case REMOVE:
  34.                     onRemove(delegate, innerView);
  35.                     break;
  36.                 case ADD:
  37.                     onAdd(delegate, innerSource, innerView, targetType, sourcePropToViewProp);
  38.                     break;
  39.                 case SET:
  40.                     onSet(delegate, innerSource, innerView, sourcePropToViewProp);
  41.                     break;
  42.                 case CLEAR:
  43.                     innerView.clear();
  44.                     break;
  45.                 case SORT:
  46.                     innerView.sort();
  47.                     break;
  48.                 case REVERSE:
  49.                     innerView.reversed();
  50.                     break;
  51.                 case DISTINCT:
  52.                     innerView.makeDistinct();
  53.                     break;
  54.                 default:
  55.                     onUpdateAll(innerSource, innerView, sourcePropToViewProp);
  56.             }
  57.         }));
  58.         return Viewables.cast(view);
  59.     }

  60.     private static <T, U> void onRemove(ValsDelegate<T> delegate, PropertyListView<U> view) {
  61.         assert delegate.change() == SequenceChange.REMOVE;
  62.         int index = delegate.index().orElse(-1);
  63.         if (delegate.oldValues().isEmpty() || index < 0)
  64.             throw new UnsupportedOperationException(); // todo: implement

  65.         view.removeAt(index, delegate.oldValues().size());
  66.     }

  67.     private static <T, U> void onAdd(
  68.             ValsDelegate<T>          delegate,
  69.             Vals<T>                  source,
  70.             PropertyListView<U>      view,
  71.             Class<U>                 targetType,
  72.             Function<Val<T>, Var<U>> sourcePropToViewProp
  73.     ) {
  74.         assert delegate.change() == SequenceChange.ADD;
  75.         int index = delegate.index().orElse(-1);

  76.         if (delegate.newValues().isEmpty() || index < 0)
  77.             throw new UnsupportedOperationException(); // todo: implement

  78.         Vars<U> newViews = Vars.of(targetType);

  79.         for (int i = 0; i < delegate.newValues().size(); i++) {
  80.             Val<T> t = source.at(index + i);
  81.             newViews.add(sourcePropToViewProp.apply(t));
  82.         }

  83.         view.addAllAt(index, newViews);
  84.     }

  85.     private static <T, U> void onSet(
  86.             ValsDelegate<T>          delegate,
  87.             Vals<T>                  source,
  88.             PropertyListView<U>      view,
  89.             Function<Val<T>, Var<U>> sourcePropToViewProp
  90.     ) {
  91.         assert delegate.change() == SequenceChange.SET;
  92.         int index = delegate.index().orElse(-1);

  93.         if (delegate.newValues().isEmpty() || index < 0)
  94.             throw new UnsupportedOperationException(); // todo: implement

  95.         Vars<U> newViews = Vars.of(view.type());

  96.         for (int i = 0; i < delegate.newValues().size(); i++) {
  97.             Val<T> t = source.at(index + i);
  98.             newViews.add(sourcePropToViewProp.apply(t));
  99.         }

  100.         view.setAllAt(index, newViews);
  101.     }

  102.     private static <T, U> void onUpdateAll(
  103.             Vals<T>                  source,
  104.             PropertyListView<U>      view,
  105.             Function<Val<T>, Var<U>> sourcePropToViewProp
  106.     ) {
  107.         view.clear();
  108.         for (int i = 0; i < source.size(); i++) {
  109.             Val<T> t = source.at(i);
  110.             view.add(sourcePropToViewProp.apply(t));
  111.         }
  112.     }


  113.     private final List<Var<T>> _variables = new ArrayList<>();
  114.     private final boolean      _allowsNull;
  115.     private final Class<T>     _type;

  116.     private final PropertyListChangeListeners<T> _changeListeners = new PropertyListChangeListeners<>();
  117.     private final ParentListRef<Vals<?>> _parentRef;


  118.     @SafeVarargs
  119.     private PropertyListView(Class<T> type, boolean allowsNull, Vals<?> source, Var<T>... vals) {
  120.         _type        = type;
  121.         _allowsNull  = allowsNull;
  122.         _parentRef   = ParentListRef.of(source);
  123.         _variables.addAll(Arrays.asList(vals));
  124.         _checkNullSafety();
  125.     }

  126.     /** {@inheritDoc} */
  127.     @Override public final Var<T> at( int index ) {
  128.         return _variables.get(index);
  129.     }

  130.     /** {@inheritDoc} */
  131.     @Override public final Class<T> type() {
  132.         return _type;
  133.     }

  134.     /** {@inheritDoc} */
  135.     @Override public final int size() {
  136.         return _variables.size();
  137.     }

  138.     private void removeAt( int index, int size ) {
  139.         removeRange(index, index + size);
  140.     }

  141.     private void add( Var<T> var ) {
  142.         Objects.requireNonNull(var);
  143.         addAt( size(), var );
  144.     }

  145.     private void addAt( int index, Var<T> value ) {
  146.         if ( this.allowsNull() != value.allowsNull() )
  147.             throw new IllegalArgumentException("The null safety of the given property does not match this list.");
  148.         _checkNullSafetyOf(value);
  149.         _variables.add(index, value);
  150.         _triggerAction( SequenceChange.ADD, index, value, null );
  151.     }

  152.     private void removeRange(int from, int to) {
  153.         if ( from < 0 || to > _variables.size() || from > to )
  154.             throw new IndexOutOfBoundsException("From: " + from + ", To: " + to + ", Size: " + _variables.size());

  155.         if (from == to)
  156.             return;

  157.         Vars<T> removal = (Vars<T>) (_allowsNull ? Vars.ofNullable(_type) : Vars.of(_type));

  158.         List<Var<T>> subList = _variables.subList( from, to );
  159.         for ( Var<T> var : subList ) removal.add(var);
  160.         subList.clear();

  161.         _triggerAction( SequenceChange.REMOVE, from, null, removal );
  162.     }

  163.     private void addAllAt( int index, Vars<T> vars ) {
  164.         if ( !_checkCanAdd(vars) )
  165.             return;

  166.         if ( index < 0 || index > size() )
  167.             throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size());

  168.         for ( int i = 0; i < vars.size(); i++ ) {
  169.             Var<T> toBeAdded = vars.at(i);
  170.             _checkNullSafetyOf(toBeAdded);
  171.             _variables.add(index + i, toBeAdded);
  172.         }

  173.         _triggerAction( SequenceChange.ADD, index, vars, null );
  174.     }

  175.     private void setAllAt(int index, Vars<T> vars) {
  176.         if ( !_checkCanAdd(vars) )
  177.             return;

  178.         if ( index < 0 || index > size() )
  179.             throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size());

  180.         int end = index + vars.size();

  181.         if ( end > size() )
  182.             throw new IndexOutOfBoundsException("Index: " + end + ", Size: " + size());

  183.         Vars<T> oldVars = (Vars<T>) (_allowsNull ? Vars.ofNullable(_type) : Vars.of(_type));
  184.         Vars<T> newVars = (Vars<T>) (_allowsNull ? Vars.ofNullable(_type) : Vars.of(_type));

  185.         for ( int i = 0; i < vars.size(); i++ ) {
  186.             Var<T> toBeAdded = vars.at(i);
  187.             _checkNullSafetyOf(toBeAdded);
  188.             Var<T> old = _variables.set(index + i, toBeAdded);
  189.             newVars.add(toBeAdded);
  190.             oldVars.add(old);
  191.         }

  192.         _triggerAction( SequenceChange.SET, index, newVars, oldVars );
  193.     }

  194.     private boolean _checkCanAdd( Vals<T> properties ) {
  195.         if ( properties.allowsNull() != this.allowsNull() )
  196.             throw new IllegalArgumentException(
  197.                     "The null safety of the given property list does not match this list."
  198.                 );

  199.         if ( properties.isEmpty() )
  200.             return false;

  201.         return true;
  202.     }

  203.     private void clear() {
  204.         Vars<T> vars = (Vars<T>) (_allowsNull ? Vars.ofNullable(_type, _variables) : Vars.of(_type, _variables));

  205.         _variables.clear();
  206.         _triggerAction( SequenceChange.CLEAR, 0, null, vars);
  207.     }

  208.     public void sort( Comparator<T> comparator ) {
  209.         _variables.sort( ( a, b ) -> comparator.compare( a.orElseNull(), b.orElseNull() ) );
  210.         _triggerAction( SequenceChange.SORT );
  211.     }

  212.     private void sort() {
  213.         // First we have to check if the type is comparable:
  214.         if (Comparable.class.isAssignableFrom(type())) {
  215.             @SuppressWarnings("unchecked")
  216.             Comparator<T> comparator = (Comparator<T>) Comparator.naturalOrder();
  217.             sort(comparator);
  218.         } else {
  219.             throw new UnsupportedOperationException("Cannot sort a list of non-comparable types.");
  220.         }
  221.     }

  222.     public final void makeDistinct() {
  223.         Set<T> checked = new HashSet<>();
  224.         List<Var<T>> retained = new ArrayList<>();
  225.         for ( Var<T> property : _variables ) {
  226.             T item = property.orElseNull();
  227.             if ( !checked.contains(item) ) {
  228.                 checked.add(item);
  229.                 retained.add(property);
  230.             }
  231.         }
  232.         _variables.clear();
  233.         _variables.addAll(retained);
  234.         _triggerAction( SequenceChange.DISTINCT );
  235.     }

  236.     public void reversed() {
  237.         int size = size();
  238.         for ( int i = 0; i < size / 2; i++ ) {
  239.             Var<T> tmp = at(i);
  240.             _variables.set( i, at(size - i - 1) );
  241.             _variables.set( size - i - 1, tmp );
  242.         }
  243.         _triggerAction( SequenceChange.REVERSE );
  244.     }

  245.     /** {@inheritDoc} */
  246.     @Override
  247.     public Vals<T> onChange( Action<ValsDelegate<T>> action ) {
  248.         _changeListeners.onChange(action);
  249.         return this;
  250.     }

  251.     /** {@inheritDoc} */
  252.     @Override
  253.     public Vals<T> fireChange() {
  254.         _triggerAction( SequenceChange.NONE );
  255.         return this;
  256.     }

  257.     @Override
  258.     public boolean allowsNull() {
  259.         return _allowsNull;
  260.     }

  261.     @Override
  262.     public boolean isMutable() {
  263.         return true;
  264.     }

  265.     @Override
  266.     public boolean isView() {
  267.         return true;
  268.     }

  269.     private void _triggerAction(
  270.             SequenceChange type, int index, @Nullable Var<T> newVal, @Nullable Var<T> oldVal
  271.     ) {
  272.         _changeListeners.fireChange(type, index, newVal, oldVal, this);
  273.     }

  274.     private void _triggerAction(SequenceChange type) {
  275.         _changeListeners.fireChange(type, this);
  276.     }

  277.     private void _triggerAction(
  278.             SequenceChange type, int index, @Nullable Vals<T> newVals, @Nullable Vals<T> oldVals
  279.     ) {
  280.         _changeListeners.fireChange(type, index, newVals, oldVals, this);
  281.     }

  282.     /** {@inheritDoc} */
  283.     @Override
  284.     public java.util.Iterator<T> iterator() {
  285.         return new java.util.Iterator<T>() {
  286.             private int index = 0;
  287.             @Override public boolean hasNext() { return index < size(); }
  288.             @Override public @Nullable T next() { return at(index++).orElseNull(); }
  289.         };
  290.     }

  291.     /** {@inheritDoc} */
  292.     @Override
  293.     public final String toString() {
  294.         String entries = _variables.stream()
  295.                                     .map( o -> o.itemAsString() + ( o.hasID() ? "(" + o.id() + ")" : "" ) )
  296.                                     .collect(Collectors.joining(", "));

  297.         return "Views<" + _type.getSimpleName() + ">[" + entries + "]";
  298.     }

  299.     /** {@inheritDoc} */
  300.     @Override
  301.     public final boolean equals( Object obj ) {
  302.         return this == obj;
  303.     }

  304.     /** {@inheritDoc} */
  305.     @Override
  306.     public final int hashCode() {
  307.         return System.identityHashCode(this);
  308.     }

  309.     private void _checkNullSafety() {
  310.         if ( !_allowsNull )
  311.             for ( Var<T> val : _variables )
  312.                 _checkNullSafetyOf(val);
  313.         else {
  314.             for ( Var<T> val : _variables )
  315.                 if ( !val.allowsNull() )
  316.                     throw new IllegalArgumentException("The null safety of the given property does not match this list.");
  317.         }
  318.     }

  319.     private void _checkNullSafetyOf( Val<T> value ) {
  320.         Objects.requireNonNull(value);
  321.         if ( !_allowsNull && value.allowsNull() )
  322.             throw new IllegalArgumentException("Null values are not allowed in this property list.");
  323.     }

  324.     @Override
  325.     public Observable subscribe( Observer observer ) {
  326.         _changeListeners.onChange(observer);
  327.         return this;
  328.     }

  329.     @Override
  330.     public Observable unsubscribe( Subscriber subscriber ) {
  331.         _changeListeners.unsubscribe(subscriber);
  332.         return this;
  333.     }

  334.     @Override
  335.     public void unsubscribeAll() {
  336.         _changeListeners.unsubscribeAll();
  337.     }
  338. }