/* * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * * * * * * * * * * * * * * * * * * * * */ package com.sun.javafx.collections; import javafx.beans.InvalidationListener; import javafx.collections.MapChangeListener; import javafx.collections.ObservableMap; import java.util.Collection; import java.util.Iterator; import java.util.Map; import java.util.Set; /** * A Map wrapper class that implements observability. * */ public class ObservableMapWrapper implements ObservableMap{ private ObservableEntrySet entrySet; private ObservableKeySet keySet; private ObservableValues values; private MapListenerHelper listenerHelper; private final Map backingMap; public ObservableMapWrapper(Map map) { this.backingMap = map; } private class SimpleChange extends MapChangeListener.Change { private final K key; private final V old; private final V added; private final boolean wasAdded; private final boolean wasRemoved; public SimpleChange(K key, V old, V added, boolean wasAdded, boolean wasRemoved) { super(ObservableMapWrapper.this); assert(wasAdded || wasRemoved); this.key = key; this.old = old; this.added = added; this.wasAdded = wasAdded; this.wasRemoved = wasRemoved; } @Override public boolean wasAdded() { return wasAdded; } @Override public boolean wasRemoved() { return wasRemoved; } @Override public K getKey() { return key; } @Override public V getValueAdded() { return added; } @Override public V getValueRemoved() { return old; } @Override public String toString() { StringBuilder builder = new StringBuilder(); if (wasAdded) { if (wasRemoved) { builder.append("replaced ").append(old).append("by ").append(added); } else { builder.append("added ").append(added); } } else { builder.append("removed ").append(old); } builder.append(" at key ").append(key); return builder.toString(); } } protected void callObservers(MapChangeListener.Change change) { MapListenerHelper.fireValueChangedEvent(listenerHelper, change); } @Override public void addListener(InvalidationListener listener) { listenerHelper = MapListenerHelper.addListener(listenerHelper, listener); } @Override public void removeListener(InvalidationListener listener) { listenerHelper = MapListenerHelper.removeListener(listenerHelper, listener); } @Override public void addListener(MapChangeListener observer) { listenerHelper = MapListenerHelper.addListener(listenerHelper, observer); } @Override public void removeListener(MapChangeListener observer) { listenerHelper = MapListenerHelper.removeListener(listenerHelper, observer); } @Override public int size() { return backingMap.size(); } @Override public boolean isEmpty() { return backingMap.isEmpty(); } @Override public boolean containsKey(Object key) { return backingMap.containsKey(key); } @Override public boolean containsValue(Object value) { return backingMap.containsValue(value); } @Override public V get(Object key) { return backingMap.get(key); } @Override public V put(K key, V value) { V ret; if (backingMap.containsKey(key)) { ret = backingMap.put(key, value); if (ret == null && value != null || ret != null && !ret.equals(value)) { callObservers(new SimpleChange(key, ret, value, true, true)); } } else { ret = backingMap.put(key, value); callObservers(new SimpleChange(key, ret, value, true, false)); } return ret; } @Override @SuppressWarnings("unchecked") public V remove(Object key) { if (!backingMap.containsKey(key)) { return null; } V ret = backingMap.remove(key); callObservers(new SimpleChange((K)key, ret, null, false, true)); return ret; } @Override public void putAll(Map m) { for (Map.Entry e : m.entrySet()) { put(e.getKey(), e.getValue()); } } @Override public void clear() { for (Iterator> i = backingMap.entrySet().iterator(); i.hasNext(); ) { Entry e = i.next(); K key = e.getKey(); V val = e.getValue(); i.remove(); callObservers(new SimpleChange(key, val, null, false, true)); } } @Override public Set keySet() { if (keySet == null) { keySet = new ObservableKeySet(); } return keySet; } @Override public Collection values() { if (values == null) { values = new ObservableValues(); } return values; } @Override public Set> entrySet() { if (entrySet == null) { entrySet = new ObservableEntrySet(); } return entrySet; } @Override public String toString() { return backingMap.toString(); } @Override public boolean equals(Object obj) { return backingMap.equals(obj); } @Override public int hashCode() { return backingMap.hashCode(); } private class ObservableKeySet implements Set{ @Override public int size() { return backingMap.size(); } @Override public boolean isEmpty() { return backingMap.isEmpty(); } @Override public boolean contains(Object o) { return backingMap.keySet().contains(o); } @Override public Iterator iterator() { return new Iterator() { private Iterator> entryIt = backingMap.entrySet().iterator(); private K lastKey; private V lastValue; @Override public boolean hasNext() { return entryIt.hasNext(); } @Override public K next() { Entry last = entryIt.next(); lastKey = last.getKey(); lastValue = last.getValue(); return last.getKey(); } @Override public void remove() { entryIt.remove(); callObservers(new SimpleChange(lastKey, lastValue, null, false, true)); } }; } @Override public Object[] toArray() { return backingMap.keySet().toArray(); } @Override public T[] toArray(T[] a) { return backingMap.keySet().toArray(a); } @Override public boolean add(K e) { throw new UnsupportedOperationException("Not supported."); } @Override public boolean remove(Object o) { return ObservableMapWrapper.this.remove(o) != null; } @Override public boolean containsAll(Collection c) { return backingMap.keySet().containsAll(c); } @Override public boolean addAll(Collection c) { throw new UnsupportedOperationException("Not supported."); } @Override public boolean retainAll(Collection c) { return removeRetain(c, false); } private boolean removeRetain(Collection c, boolean remove) { boolean removed = false; for (Iterator> i = backingMap.entrySet().iterator(); i.hasNext();) { Entry e = i.next(); if (remove == c.contains(e.getKey())) { removed = true; K key = e.getKey(); V value = e.getValue(); i.remove(); callObservers(new SimpleChange(key, value, null, false, true)); } } return removed; } @Override public boolean removeAll(Collection c) { return removeRetain(c, true); } @Override public void clear() { ObservableMapWrapper.this.clear(); } @Override public String toString() { return backingMap.keySet().toString(); } @Override public boolean equals(Object obj) { return backingMap.keySet().equals(obj); } @Override public int hashCode() { return backingMap.keySet().hashCode(); } } private class ObservableValues implements Collection { @Override public int size() { return backingMap.size(); } @Override public boolean isEmpty() { return backingMap.isEmpty(); } @Override public boolean contains(Object o) { return backingMap.values().contains(o); } @Override public Iterator iterator() { return new Iterator() { private Iterator> entryIt = backingMap.entrySet().iterator(); private K lastKey; private V lastValue; @Override public boolean hasNext() { return entryIt.hasNext(); } @Override public V next() { Entry last = entryIt.next(); lastKey = last.getKey(); lastValue = last.getValue(); return lastValue; } @Override public void remove() { entryIt.remove(); callObservers(new SimpleChange(lastKey, lastValue, null, false, true)); } }; } @Override public Object[] toArray() { return backingMap.values().toArray(); } @Override public T[] toArray(T[] a) { return backingMap.values().toArray(a); } @Override public boolean add(V e) { throw new UnsupportedOperationException("Not supported."); } @Override public boolean remove(Object o) { for(Iterator i = iterator(); i.hasNext();) { if (i.next().equals(o)) { i.remove(); return true; } } return false; } @Override public boolean containsAll(Collection c) { return backingMap.values().containsAll(c); } @Override public boolean addAll(Collection c) { throw new UnsupportedOperationException("Not supported."); } @Override public boolean removeAll(Collection c) { return removeRetain(c, true); } private boolean removeRetain(Collection c, boolean remove) { boolean removed = false; for (Iterator> i = backingMap.entrySet().iterator(); i.hasNext();) { Entry e = i.next(); if (remove == c.contains(e.getValue())) { removed = true; K key = e.getKey(); V value = e.getValue(); i.remove(); callObservers(new SimpleChange(key, value, null, false, true)); } } return removed; } @Override public boolean retainAll(Collection c) { return removeRetain(c, false); } @Override public void clear() { ObservableMapWrapper.this.clear(); } @Override public String toString() { return backingMap.values().toString(); } @Override public boolean equals(Object obj) { return backingMap.values().equals(obj); } @Override public int hashCode() { return backingMap.values().hashCode(); } } private class ObservableEntry implements Entry { private final Entry backingEntry; public ObservableEntry(Entry backingEntry) { this.backingEntry = backingEntry; } @Override public K getKey() { return backingEntry.getKey(); } @Override public V getValue() { return backingEntry.getValue(); } @Override public V setValue(V value) { V oldValue = backingEntry.setValue(value); callObservers(new SimpleChange(getKey(), oldValue, value, true, true)); return oldValue; } @Override public final boolean equals(Object o) { if (!(o instanceof Map.Entry)) { return false; } Map.Entry e = (Map.Entry) o; Object k1 = getKey(); Object k2 = e.getKey(); if (k1 == k2 || (k1 != null && k1.equals(k2))) { Object v1 = getValue(); Object v2 = e.getValue(); if (v1 == v2 || (v1 != null && v1.equals(v2))) { return true; } } return false; } @Override public final int hashCode() { return (getKey() == null ? 0 : getKey().hashCode()) ^ (getValue() == null ? 0 : getValue().hashCode()); } @Override public final String toString() { return getKey() + "=" + getValue(); } } private class ObservableEntrySet implements Set>{ @Override public int size() { return backingMap.size(); } @Override public boolean isEmpty() { return backingMap.isEmpty(); } @Override public boolean contains(Object o) { return backingMap.entrySet().contains(o); } @Override public Iterator> iterator() { return new Iterator>() { private Iterator> backingIt = backingMap.entrySet().iterator(); private K lastKey; private V lastValue; @Override public boolean hasNext() { return backingIt.hasNext(); } @Override public Entry next() { Entry last = backingIt.next(); lastKey = last.getKey(); lastValue = last.getValue(); return new ObservableEntry(last); } @Override public void remove() { backingIt.remove(); callObservers(new SimpleChange(lastKey, lastValue, null, false, true)); } }; } @Override @SuppressWarnings("unchecked") public Object[] toArray() { Object[] array = backingMap.entrySet().toArray(); for (int i = 0; i < array.length; ++i) { array[i] = new ObservableEntry((Entry)array[i]); } return array; } @Override @SuppressWarnings("unchecked") public T[] toArray(T[] a) { T[] array = backingMap.entrySet().toArray(a); for (int i = 0; i < array.length; ++i) { array[i] = (T) new ObservableEntry((Entry)array[i]); } return array; } @Override public boolean add(Entry e) { throw new UnsupportedOperationException("Not supported."); } @Override @SuppressWarnings("unchecked") public boolean remove(Object o) { boolean ret = backingMap.entrySet().remove(o); if (ret) { Entry entry = (Entry) o; callObservers(new SimpleChange(entry.getKey(), entry.getValue(), null, false, true)); } return ret; } @Override public boolean containsAll(Collection c) { return backingMap.entrySet().containsAll(c); } @Override public boolean addAll(Collection> c) { throw new UnsupportedOperationException("Not supported."); } @Override public boolean retainAll(Collection c) { return removeRetain(c, false); } private boolean removeRetain(Collection c, boolean remove) { boolean removed = false; for (Iterator> i = backingMap.entrySet().iterator(); i.hasNext();) { Entry e = i.next(); if (remove == c.contains(e)) { removed = true; K key = e.getKey(); V value = e.getValue(); i.remove(); callObservers(new SimpleChange(key, value, null, false, true)); } } return removed; } @Override public boolean removeAll(Collection c) { return removeRetain(c, true); } @Override public void clear() { ObservableMapWrapper.this.clear(); } @Override public String toString() { return backingMap.entrySet().toString(); } @Override public boolean equals(Object obj) { return backingMap.entrySet().equals(obj); } @Override public int hashCode() { return backingMap.entrySet().hashCode(); } } }