Skip to content

Instantly share code, notes, and snippets.

@bugabinga
Created March 3, 2016 14:43
Show Gist options
  • Save bugabinga/908ca858ffc21337856a to your computer and use it in GitHub Desktop.
Save bugabinga/908ca858ffc21337856a to your computer and use it in GitHub Desktop.
Example of an ObservableListWrapper for JavaFx that maps values from a source list to a mapped list according to a user defined transformation. It is the analog of Stream.map for ObservableList.
package net.bugabinga.javafx.util;
import static java.util.stream.Collectors.toList;
import java.util.List;
import java.util.function.Function;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.collections.ObservableListBase;
/**
* @author Oliver Jan Krylow <[email protected]>
* @date 02.03.2016
*/
public class MappingList<V, E> extends ObservableListBase<E>
{
private final List<E> mapped;
private final Function<V, E> mapper;
public MappingList( final ObservableList<V> source, final Function<V, E> mapper )
{
this.mapper = mapper;
mapped = applyMapper( source );
source.addListener( this::sourceChanged );
}
private void sourceChanged( final Change<? extends V> c )
{
beginChange();
while ( c.next() )
{
if ( c.wasPermutated() )
{
/*
* Order of the list was changed
*/
final int[] permutatedElements = new int[c.getTo() - c.getFrom()];
for ( int i = c.getFrom(); i < c.getTo(); ++i )
{
permutatedElements[ i ] = c.getPermutation( i );
mapped.set( i, mapper.apply( c.getList().get( i ) ) );
}
nextPermutation( c.getFrom(), c.getTo(), permutatedElements );
}
else if ( c.wasUpdated() )
{
/*
* Some lists get notified when their elements mutate
*/
for ( int i = c.getFrom(); i < c.getTo(); ++i )
{
mapped.set( i, mapper.apply( c.getList().get( i ) ) );
nextUpdate( i );
}
}
else
{
/*
* Handles replace,remove and add
*/
final List<E> removedElements = applyMapper( c.getRemoved() );
mapped.removeAll( removedElements );
nextRemove( c.getFrom(), removedElements );
final List<E> addedElements = applyMapper( c.getAddedSubList() );
mapped.addAll( c.getFrom(), addedElements );
nextAdd( c.getFrom(), c.getTo() );
}
}
endChange();
}
private List<E> applyMapper( final List<? extends V> list )
{
return list.stream().map( mapper )/*.distinct()*/.collect( toList() );
}
@Override
public E get( final int index )
{
return mapped.get( index );
}
@Override
public int size()
{
return mapped.size();
}
}
package net.bugabinga.javafx.util;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import javafx.beans.Observable;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
/**
* @author Oliver Jan Krylow <[email protected]>
* @date 03.03.2016
*
*/
public class MappingListTest
{
/**
* Test method for
* {@link com.isp.canvas.MappingList#MappingList(javafx.collections.ObservableList, java.util.function.Function)}
* .
*/
@Test
public void testMappingList()
{
final Double v0 = 0.0;
final Double v1 = 1.0;
final Double v2 = 1.4;
final Double v3 = 2341.5;
final Double v4 = 165.99;
final ObservableList<Double> valueList = FXCollections.observableArrayList();
final MappingList<Double, String> moneyList = new MappingList<>( valueList, v -> v + " €" );
assertTrue( "Mapped list should be empty.", moneyList.isEmpty() );
// Adding values
valueList.add( v0 );
assertThat( moneyList.size(), is( 1 ) );
assertThat( moneyList.get( 0 ), is( "0.0 €" ) );
valueList.add( v1 );
assertThat( moneyList.size(), is( 2 ) );
assertThat( moneyList.get( 1 ), is( "1.0 €" ) );
valueList.add( v2 );
assertThat( moneyList.size(), is( 3 ) );
assertThat( moneyList.get( 2 ), is( "1.4 €" ) );
valueList.add( v3 );
assertThat( moneyList.size(), is( 4 ) );
assertThat( moneyList.get( 3 ), is( "2341.5 €" ) );
valueList.add( v4 );
assertThat( moneyList.size(), is( 5 ) );
assertThat( moneyList.get( 4 ), is( "165.99 €" ) );
//Removing values
valueList.remove( 2 );
assertThat( moneyList.size(), is( 4 ) );
assertThat( moneyList.get( 0 ), is( "0.0 €" ) );
assertThat( moneyList.get( 1 ), is( "1.0 €" ) );
assertThat( moneyList.get( 2 ), is( "2341.5 €" ) );
assertThat( moneyList.get( 3 ), is( "165.99 €" ) );
valueList.remove( 0 );
assertThat( moneyList.size(), is( 3 ) );
assertThat( moneyList.get( 0 ), is( "1.0 €" ) );
assertThat( moneyList.get( 1 ), is( "2341.5 €" ) );
assertThat( moneyList.get( 2 ), is( "165.99 €" ) );
valueList.remove( 2 );
assertThat( moneyList.size(), is( 2 ) );
assertThat( moneyList.get( 0 ), is( "1.0 €" ) );
assertThat( moneyList.get( 1 ), is( "2341.5 €" ) );
//Adding values after removing
valueList.add( 5.5 );
assertThat( moneyList.size(), is( 3 ) );
assertThat( moneyList.get( 2 ), is( "5.5 €" ) );
valueList.add( 10000.44 );
assertThat( moneyList.size(), is( 4 ) );
assertThat( moneyList.get( 3 ), is( "10000.44 €" ) );
//Changing values
valueList.set( 0, 33.33 );
assertThat( moneyList.size(), is( 4 ) );
assertThat( moneyList.get( 0 ), is( "33.33 €" ) );
valueList.set( 3, 1567.111 );
assertThat( moneyList.size(), is( 4 ) );
assertThat( moneyList.get( 3 ), is( "1567.111 €" ) );
//Updating values
valueList.replaceAll( v -> v * 2 );
assertThat( moneyList.get( 0 ), is( "66.66 €" ) );
assertThat( moneyList.get( 1 ), is( "4683.0 €" ) );
assertThat( moneyList.get( 2 ), is( "11.0 €" ) );
assertThat( moneyList.get( 3 ), is( "3134.222 €" ) );
// Adding multiple values
valueList.addAll( 1.0, 2.0, 3.0, 4.0 );
assertThat( moneyList.get( 4 ), is( "1.0 €" ) );
assertThat( moneyList.get( 5 ), is( "2.0 €" ) );
assertThat( moneyList.get( 6 ), is( "3.0 €" ) );
assertThat( moneyList.get( 7 ), is( "4.0 €" ) );
//Clear the list
valueList.clear();
assertThat( moneyList.size(), is( 0 ) );
//Permute the list
valueList.addAll( 3.0, 1.0, 2.0 );
valueList.sort( Double::compare );
assertThat( moneyList.get( 0 ), is( "1.0 €" ) );
assertThat( moneyList.get( 1 ), is( "2.0 €" ) );
assertThat( moneyList.get( 2 ), is( "3.0 €" ) );
//Update list elements (element must be Observable) (list needs an extractor, otherwise elements will not be observed for changes)
final ObservableList<DoubleProperty> observablePropertyList = FXCollections.observableArrayList( p -> new Observable[]{ p } );
final DoubleProperty d1 = new SimpleDoubleProperty( 0.0 );
final DoubleProperty d2 = new SimpleDoubleProperty( 1.1 );
final DoubleProperty d3 = new SimpleDoubleProperty( 2.2 );
observablePropertyList.addAll( d1, d2, d3 );
final MappingList<DoubleProperty, String> mappedPropertyList =
new MappingList<>( observablePropertyList, doubleProperty -> doubleProperty.get() + " $" );
assertThat( mappedPropertyList.get( 0 ), is( "0.0 $" ) );
assertThat( mappedPropertyList.get( 1 ), is( "1.1 $" ) );
assertThat( mappedPropertyList.get( 2 ), is( "2.2 $" ) );
d1.set( 10.1 );
d2.set( 11.11 );
d3.set( 12.12 );
assertThat( mappedPropertyList.get( 0 ), is( "10.1 $" ) );
assertThat( mappedPropertyList.get( 1 ), is( "11.11 $" ) );
assertThat( mappedPropertyList.get( 2 ), is( "12.12 $" ) );
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment