Long time no code.
Example use
import ListMap;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import static java.util.Arrays.asList;
public class AttributesMap
extends ListMap<FooItem, String, String> {
public AttributesMap(final FooOperations foos, final String cookie) {
super(new FooItems(foos, cookie), new FooItemConverter());
}
private static class FooItems
extends ArrayList<FooItem> {
private final FooOperations foos;
private final String cookie;
private FooItems(final FooOperations foos, final String cookie) {
super(asList(foos.getAttributes(cookie)));
this.foos = foos;
this.cookie = cookie;
}
@Override
public FooItem set(final int index, final FooItem element) {
final FooItem oldElement = super.set(index, element);
save();
return oldElement;
}
@Override
public void add(final int index, final FooItem element) {
super.add(index, element);
save();
}
@Override
public FooItem remove(final int index) {
final FooItem oldElement = super.remove(index);
save();
return oldElement;
}
private void save() {
try {
foos.setAttributes(cookie, toArray(new FooItem[size()]));
} catch (final BadDataRef e) {
throw new UserRuntimeException(e);
}
}
}
private static final class FooItemConverter
implements Converter<FooItem, String, String> {
@Nonnull
@Override
public FooItem toElement(@Nonnull final String key, final String value) {
return new FooItem(key, value);
}
@Nonnull
@Override
public Entry<String, String> toEntry(@Nonnull final FooItem element) {
return new SimpleEntry<>(element.tag, element.value);
}
}
}
ListMap
utility class
import javax.annotation.Nonnull;
import javax.annotation.concurrent.NotThreadSafe;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.List;
import java.util.ListIterator;
/**
* {@code ListMap} provides a read-write map view over a list of elements. The elements must
* provide a key-value pair structure; keys must {@link #equals(Object)} and {@link #hashCode()}.
* <p/>
* All map mutating methods also mutate the underlying list. Mutating the underlying list also
* mutates the map view.
* <p/>
* There is no synchronization; instances are not thread safe.
*
* @param <T> the type of list elements
* @param <K> the type of map keys
* @param <V> the type of map values
*
* @author <a href="mailto:binkley@alumni.rice.edu">B. K. Oxley (binkley)</a>
*/
@NotThreadSafe
public class ListMap<T, K, V>
extends AbstractMap<K, V> {
private final List<T> elements;
private final Converter<T, K, V> converter;
/**
* Creates a new {@code ListMap} for the given <var>elements</var> and <var>converter</var>.
*
* @param elements the list of elements underlying the map, never missing
* @param converter the converter between elements and entry objects, never missing
*/
public ListMap(@Nonnull final List<T> elements, @Nonnull final Converter<T, K, V> converter) {
this.elements = elements;
this.converter = converter;
}
/**
* {@inheritDoc}
* <p/>
* Updates the underlying list of elements.
*/
@Override
public V put(final K key, final V value) {
final ListIterator<T> lit = elements.listIterator();
while (lit.hasNext()) {
final T element = lit.next();
final Entry<K, V> entry = converter.toEntry(element);
if (entry.getKey().equals(key)) {
elements.set(lit.nextIndex() - 1, converter.toElement(key, value));
return entry.getValue();
}
}
lit.add(converter.toElement(key, value));
return null;
}
/**
* {@inheritDoc}
*
* @return a specialized set view of the entries over the element list
*/
@Override
@Nonnull
public ListMapSet entrySet() {
return new ListMapSet();
}
/**
* Exposes the underlying list of elements backing the map view.
*
* @return the element list, never missing
*/
@Nonnull
public List<T> elements() {
return elements;
}
/**
* Converts between elements and entry objects.
*
* @param <T> the type of list elements
* @param <K> the type of map keys
* @param <V> the type of map values
*/
public static interface Converter<T, K, V> {
/**
* Converts to an element instance from a map <var>key</var>-<var>value</var> pair.
*
* @param key the map key, never missing
* @param value the map value, optional
*
* @return the corresponding list element, never missing
*/
@Nonnull
T toElement(@Nonnull final K key, final V value);
/**
* Converts to a map entry instance from a list <var>element</var>.
*
* @param element the list element, never missing
*
* @return the corresponding map entry, never missing
*/
@Nonnull
Entry<K, V> toEntry(@Nonnull final T element);
}
/**
* Backing set for {@link ListMap} exposed as a separate type so callers may access a {@link
* #iterator() list iterator} and a list iterator {@link #iterator(int) offset by an index}.
*/
public final class ListMapSet
extends AbstractSet<Entry<K, V>> {
/**
* {@inheritDoc}
*
* @see java.util.List#listIterator()
*/
@Override
@Nonnull
public ListMapIterator iterator() {
return new ListMapIterator();
}
@Override
public int size() {
return elements.size();
}
@Override
public boolean add(final Entry<K, V> entry) {
return elements.add(converter.toElement(entry.getKey(), entry.getValue()));
}
/** @see List#listIterator(int) */
@Nonnull
public ListMapIterator iterator(final int index) {
return new ListMapIterator(index);
}
}
/**
* Entry set iterator for {@link ListMapSet} exposed as a separate type.
*
* @see ListMapSet#iterator() list iterator
* @see ListMapSet#iterator(int) offset by an index
*/
public final class ListMapIterator
implements ListIterator<Entry<K, V>> {
private final ListIterator<T> it;
public ListMapIterator() {
it = elements.listIterator();
}
public ListMapIterator(final int index) {
it = elements.listIterator(index);
}
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public Entry<K, V> next() {
return new ListMapEntry(it.next());
}
@Override
public boolean hasPrevious() {
return it.hasPrevious();
}
@Override
public Entry<K, V> previous() {
return new ListMapEntry(it.previous());
}
@Override
public int nextIndex() {
return it.nextIndex();
}
@Override
public int previousIndex() {
return it.previousIndex();
}
@Override
public void remove() {
it.remove();
}
@Override
public void set(final Entry<K, V> entry) {
it.set(converter.toElement(entry.getKey(), entry.getValue()));
}
@Override
public void add(final Entry<K, V> entry) {
it.add(converter.toElement(entry.getKey(), entry.getValue()));
}
private class ListMapEntry
implements Entry<K, V> {
private final T element;
ListMapEntry(final T element) {
this.element = element;
}
@Override
public K getKey() {
return converter.toEntry(element).getKey();
}
@Override
public V getValue() {
return converter.toEntry(element).getValue();
}
@Override
public V setValue(final V value) {
final Entry<K, V> oldEntry = converter.toEntry(element);
elements.set(nextIndex() - 1, converter.toElement(oldEntry.getKey(), value));
return oldEntry.getValue();
}
}
}
}