Skip to content

Instantly share code, notes, and snippets.

@mikehearn
Created July 13, 2014 20:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mikehearn/a2e4a048a996fd900656 to your computer and use it in GitHub Desktop.
Save mikehearn/a2e4a048a996fd900656 to your computer and use it in GitHub Desktop.
/**
* 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