Created
July 13, 2014 20:33
-
-
Save mikehearn/a2e4a048a996fd900656 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Maps elements of type F to E with change listeners working as expected. | |
*/ | |
public class MappedList<E, F> extends TransformationList<E, F> { | |
private final Function<F, E> mapper; | |
private final ArrayList<E> mapped; | |
/** | |
* Creates a new MappedList list wrapped around the source list. | |
* Each element will have the given function applied to it, such that the list is cast through the mapper. | |
*/ | |
public MappedList(ObservableList<? extends F> source, Function<F, E> mapper) { | |
super(source); | |
this.mapper = mapper; | |
this.mapped = new ArrayList<>(source.size()); | |
mapAll(); | |
} | |
private void mapAll() { | |
mapped.clear(); | |
for (F val : getSource()) | |
mapped.add(mapper.apply(val)); | |
} | |
@Override | |
protected void sourceChanged(ListChangeListener.Change<? extends F> c) { | |
// Is all this stuff right for every case? Probably it doesn't matter for this app. | |
beginChange(); | |
while (c.next()) { | |
if (c.wasPermutated()) { | |
int[] perm = new int[c.getTo() - c.getFrom()]; | |
for (int i = c.getFrom(); i < c.getTo(); i++) | |
perm[i - c.getFrom()] = c.getPermutation(i); | |
nextPermutation(c.getFrom(), c.getTo(), perm); | |
} else if (c.wasUpdated()) { | |
for (int i = c.getFrom(); i < c.getTo(); i++) { | |
remapIndex(i); | |
nextUpdate(i); | |
} | |
} else { | |
if (c.wasRemoved()) { | |
// Removed should come first to properly handle replacements, then add. | |
List<E> removed = mapped.subList(c.getFrom(), c.getFrom() + c.getRemovedSize()); | |
ArrayList<E> duped = new ArrayList<>(removed); | |
removed.clear(); | |
nextRemove(c.getFrom(), duped); | |
} | |
if (c.wasAdded()) { | |
for (int i = c.getFrom(); i < c.getTo(); i++) { | |
mapped.addAll(c.getFrom(), c.getAddedSubList().stream().map(mapper).collect(Collectors.toList())); | |
remapIndex(i); | |
} | |
nextAdd(c.getFrom(), c.getTo()); | |
} | |
} | |
} | |
endChange(); | |
} | |
private void remapIndex(int i) { | |
if (i >= mapped.size()) { | |
for (int j = mapped.size(); j <= i; j++) { | |
mapped.add(mapper.apply(getSource().get(j))); | |
} | |
} | |
mapped.set(i, mapper.apply(getSource().get(i))); | |
} | |
@Override | |
public int getSourceIndex(int index) { | |
return index; | |
} | |
@Override | |
public E get(int index) { | |
return mapped.get(index); | |
} | |
@Override | |
public int size() { | |
return mapped.size(); | |
} | |
} | |
public class MappedListTest { | |
private ObservableList<String> inputs; | |
private ObservableList<String> outputs; | |
private Queue<ListChangeListener.Change<? extends String>> changes; | |
@Before | |
public void setup() { | |
inputs = FXCollections.observableArrayList(); | |
outputs = new MappedList<>(inputs, str -> "Hello " + str); | |
changes = new LinkedList<>(); | |
outputs.addListener(changes::add); | |
} | |
@Test | |
public void add() throws Exception { | |
assertEquals(0, outputs.size()); | |
inputs.add("Mike"); | |
ListChangeListener.Change<? extends String> change = getChange(); | |
assertTrue(change.wasAdded()); | |
assertEquals("Hello Mike", change.getAddedSubList().get(0)); | |
assertEquals(1, outputs.size()); | |
assertEquals("Hello Mike", outputs.get(0)); | |
inputs.remove(0); | |
assertEquals(0, outputs.size()); | |
} | |
private ListChangeListener.Change<? extends String> getChange() { | |
ListChangeListener.Change<? extends String> change = changes.poll(); | |
change.next(); | |
return change; | |
} | |
@Test | |
public void remove() { | |
inputs.add("Mike"); | |
inputs.add("Dave"); | |
inputs.add("Katniss"); | |
getChange(); getChange(); getChange(); | |
assertEquals("Hello Mike", outputs.get(0)); | |
assertEquals("Hello Dave", outputs.get(1)); | |
assertEquals("Hello Katniss", outputs.get(2)); | |
inputs.remove(0); | |
ListChangeListener.Change<? extends String> change = getChange(); | |
assertTrue(change.wasRemoved()); | |
assertEquals(2, outputs.size()); | |
assertEquals(1, change.getRemovedSize()); | |
assertEquals("Hello Mike", change.getRemoved().get(0)); | |
assertEquals("Hello Dave", outputs.get(0)); | |
inputs.remove(1); | |
assertEquals(1, outputs.size()); | |
assertEquals("Hello Dave", outputs.get(0)); | |
} | |
@Test | |
public void replace() throws Exception { | |
inputs.add("Mike"); | |
inputs.add("Dave"); | |
getChange(); getChange(); | |
inputs.set(0, "Bob"); | |
assertEquals("Hello Bob", outputs.get(0)); | |
ListChangeListener.Change<? extends String> change = getChange(); | |
assertTrue(change.wasReplaced()); | |
assertEquals("Hello Mike", change.getRemoved().get(0)); | |
assertEquals("Hello Bob", change.getAddedSubList().get(0)); | |
} | |
// Could also test permutation here if I could figure out how to actually apply one! | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment