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

com.metsci.glimpse.util.var.VarUtils Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2020, Metron, Inc.
 * 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 Metron, Inc. 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 METRON, INC. 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.metsci.glimpse.util.var;

import static com.google.common.base.Objects.equal;
import static com.google.common.collect.Sets.difference;
import static java.util.Collections.emptySet;

import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;

import com.google.common.collect.ImmutableMap;

public class VarUtils
{

    public static interface OldNewListener
    {
        void accept( VarEvent ev, V vOld, V vNew );
    }

    public static  Disposable addOldNewListener( ReadableVar var, boolean runImmediately, OldNewListener oldNewListener )
    {
        return var.addListener( runImmediately, new Consumer( )
        {
            private V valuePrev = null;
            private boolean ongoingPrev = true;

            @Override
            public void accept( VarEvent ev )
            {
                V valueOld = this.valuePrev;
                boolean ongoingOld = this.ongoingPrev;

                V valueNew = var.v( );
                boolean ongoingNew = ev.ongoing;

                // Update prev values BEFORE firing listeners, in case one of them triggers this method again
                this.valuePrev = valueNew;
                this.ongoingPrev = ongoingNew;

                if ( ( !ongoingNew && ongoingOld ) || !equal( valueNew, valueOld ) )
                {
                    oldNewListener.accept( ev, valueOld, valueNew );
                }
            }
        } );
    }

    public static  Disposable addOldNewListener( ReadableVar> mapVar, K key, boolean runImmediately, OldNewListener oldNewListener )
    {
        return mapVar.addListener( runImmediately, new Consumer( )
        {
            private V valuePrev = null;
            private boolean ongoingPrev = true;

            @Override
            public void accept( VarEvent ev )
            {
                V valueOld = this.valuePrev;
                boolean ongoingOld = this.ongoingPrev;

                V valueNew = mapVar.v( ).get( key );
                boolean ongoingNew = ev.ongoing;

                // Update prev values BEFORE firing listeners, in case one of them triggers this method again
                this.valuePrev = valueNew;
                this.ongoingPrev = ongoingNew;

                if ( ( !ongoingNew && ongoingOld ) || !equal( valueNew, valueOld ) )
                {
                    oldNewListener.accept( ev, valueOld, valueNew );
                }
            }
        } );
    }

    public static  Disposable addElementAddedListener( ReadableVar> var, boolean runImmediately, Consumer listener )
    {
        return addElementAddedListener( var, runImmediately, ( ev, value ) ->
        {
            listener.accept( value );
        } );
    }

    public static  Disposable addElementAddedListener( ReadableVar> var, boolean runImmediately, BiConsumer listener )
    {
        return var.addListener( runImmediately, new Consumer( )
        {
            private Set ongoingPrev = emptySet( );
            private Set completePrev = emptySet( );

            @Override
            public void accept( VarEvent ev )
            {
                if ( ev.ongoing )
                {
                    Set ongoingOld = this.ongoingPrev;
                    Set ongoingNew = new HashSet<>( var.v( ) );

                    // Update prev values BEFORE firing listeners, in case one of them triggers this method again
                    this.ongoingPrev = ongoingNew;

                    // difference() returns an unmodifiable view, which is what we want
                    Set ongoingAdded = difference( ongoingNew, ongoingOld );
                    for ( T value : ongoingAdded )
                    {
                        listener.accept( ev, value );
                    }
                }
                else
                {
                    Set completeOld = this.completePrev;
                    Set completeNew = new HashSet<>( var.v( ) );

                    // Update prev values BEFORE firing listeners, in case one of them triggers this method again
                    this.ongoingPrev = completeNew;
                    this.completePrev = completeNew;

                    // difference() returns an unmodifiable view, which is what we want
                    Set completeAdded = difference( completeNew, completeOld );
                    for ( T value : completeAdded )
                    {
                        listener.accept( ev, value );
                    }
                }
            }
        } );
    }

    public static  Disposable addElementRemovedListener( ReadableVar> var, Consumer listener )
    {
        return addElementRemovedListener( var, ( ev, value ) ->
        {
            listener.accept( value );
        } );
    }

    public static  Disposable addElementRemovedListener( ReadableVar> var, BiConsumer listener )
    {
        return var.addListener( false, new Consumer( )
        {
            private Set ongoingPrev = new HashSet<>( var.v( ) );
            private Set completePrev = new HashSet<>( var.v( ) );

            @Override
            public void accept( VarEvent ev )
            {
                if ( ev.ongoing )
                {
                    Set ongoingOld = this.ongoingPrev;
                    Set ongoingNew = new HashSet<>( var.v( ) );

                    // Update prev values BEFORE firing listeners, in case one of them triggers this method again
                    this.ongoingPrev = ongoingNew;

                    // difference() returns an unmodifiable view, which is what we want
                    Set ongoingRemoved = difference( ongoingOld, ongoingNew );
                    for ( T value : ongoingRemoved )
                    {
                        listener.accept( ev, value );
                    }
                }
                else
                {
                    Set completeOld = this.completePrev;

                    Set completeNew = new HashSet<>( var.v( ) );

                    // Update prev values BEFORE firing listeners, in case one of them triggers this method again
                    this.ongoingPrev = completeNew;
                    this.completePrev = completeNew;

                    // difference() returns an unmodifiable view, which is what we want
                    Set completeRemoved = difference( completeOld, completeNew );
                    for ( T value : completeRemoved )
                    {
                        listener.accept( ev, value );
                    }
                }
            }
        } );
    }

    public static interface MapVarListener
    {
        void accept( VarEvent ev, K key, V vOld, V vNew );
    }

    public static  Disposable addMapVarListener( ReadableVar> var, boolean runImmediately, MapVarListener listener )
    {
        return var.addListener( runImmediately, new Consumer( )
        {
            private ImmutableMap ongoingPrev = ImmutableMap.of( );
            private ImmutableMap completePrev = ImmutableMap.of( );

            @Override
            public void accept( VarEvent ev )
            {
                if ( ev.ongoing )
                {
                    ImmutableMap ongoingOld = this.ongoingPrev;
                    ImmutableMap ongoingNew = ImmutableMap.copyOf( var.v( ) );

                    // Update this.mapPrev BEFORE firing listeners, in case one of them triggers this method again
                    this.ongoingPrev = ongoingNew;

                    Set keys = new LinkedHashSet<>( );
                    keys.addAll( ongoingOld.keySet( ) );
                    keys.addAll( ongoingNew.keySet( ) );
                    for ( K k : keys )
                    {
                        V vOld = ongoingOld.get( k );
                        V vNew = ongoingNew.get( k );
                        if ( !equal( vNew, vOld ) )
                        {
                            listener.accept( ev, k, vOld, vNew );
                        }
                    }
                }
                else
                {
                    ImmutableMap completeOld = this.completePrev;
                    ImmutableMap completeNew = ImmutableMap.copyOf( var.v( ) );

                    // Update this.mapPrev BEFORE firing listeners, in case one of them triggers this method again
                    this.ongoingPrev = completeNew;
                    this.completePrev = completeNew;

                    Set keys = new LinkedHashSet<>( );
                    keys.addAll( completeOld.keySet( ) );
                    keys.addAll( completeNew.keySet( ) );
                    for ( K k : keys )
                    {
                        V vOld = completeOld.get( k );
                        V vNew = completeNew.get( k );
                        if ( !equal( vNew, vOld ) )
                        {
                            listener.accept( ev, k, vOld, vNew );
                        }
                    }
                }
            }
        } );
    }

    public static  void updateMapValue( Var> var, K key, Function updateFn )
    {
        updateMapValue( var, false, key, updateFn );
    }

    public static  void updateMapValue( Var> var, boolean ongoing, K key, Function updateFn )
    {
        var.update( ongoing, ( map ) ->
        {
            V vOld = map.get( key );
            V vNew = updateFn.apply( vOld );
            return mapWith( map, key, vNew );
        } );
    }

    public static  void putMapValue( Var> var, K key, V value )
    {
        putMapValue( var, false, key, value );
    }

    public static  void putMapValue( Var> var, boolean ongoing, K key, V value )
    {
        var.update( ongoing, ( map ) ->
        {
            return mapWith( map, key, value );
        } );
    }

    protected static  ImmutableMap mapWith( ImmutableMap map, K key, V value )
    {
        if ( equal( value, map.get( key ) ) )
        {
            return map;
        }
        else
        {
            Map newMap = new LinkedHashMap<>( map );
            if ( value == null )
            {
                newMap.remove( key );
            }
            else
            {
                newMap.put( key, value );
            }
            return ImmutableMap.copyOf( newMap );
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy