/* * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * * * * * * * * * * * * * * * * * * * * */ package com.sun.javafx.binding; import javafx.beans.InvalidationListener; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableMapValue; import javafx.collections.MapChangeListener; import javafx.collections.ObservableMap; import java.util.Arrays; import java.util.Map; /** */ public abstract class MapExpressionHelper extends ExpressionHelperBase { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Static methods public static MapExpressionHelper addListener(MapExpressionHelper helper, ObservableMapValue observable, InvalidationListener listener) { if ((observable == null) || (listener == null)) { throw new NullPointerException(); } observable.getValue(); // validate observable return (helper == null)? new SingleInvalidation(observable, listener) : helper.addListener(listener); } public static MapExpressionHelper removeListener(MapExpressionHelper helper, InvalidationListener listener) { if (listener == null) { throw new NullPointerException(); } return (helper == null)? null : helper.removeListener(listener); } public static MapExpressionHelper addListener(MapExpressionHelper helper, ObservableMapValue observable, ChangeListener> listener) { if ((observable == null) || (listener == null)) { throw new NullPointerException(); } return (helper == null)? new SingleChange(observable, listener) : helper.addListener(listener); } public static MapExpressionHelper removeListener(MapExpressionHelper helper, ChangeListener> listener) { if (listener == null) { throw new NullPointerException(); } return (helper == null)? null : helper.removeListener(listener); } public static MapExpressionHelper addListener(MapExpressionHelper helper, ObservableMapValue observable, MapChangeListener listener) { if ((observable == null) || (listener == null)) { throw new NullPointerException(); } return (helper == null)? new SingleMapChange(observable, listener) : helper.addListener(listener); } public static MapExpressionHelper removeListener(MapExpressionHelper helper, MapChangeListener listener) { if (listener == null) { throw new NullPointerException(); } return (helper == null)? null : helper.removeListener(listener); } public static void fireValueChangedEvent(MapExpressionHelper helper) { if (helper != null) { helper.fireValueChangedEvent(); } } public static void fireValueChangedEvent(MapExpressionHelper helper, MapChangeListener.Change change) { if (helper != null) { helper.fireValueChangedEvent(change); } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Common implementations protected final ObservableMapValue observable; protected MapExpressionHelper(ObservableMapValue observable) { this.observable = observable; } protected abstract MapExpressionHelper addListener(InvalidationListener listener); protected abstract MapExpressionHelper removeListener(InvalidationListener listener); protected abstract MapExpressionHelper addListener(ChangeListener> listener); protected abstract MapExpressionHelper removeListener(ChangeListener> listener); protected abstract MapExpressionHelper addListener(MapChangeListener listener); protected abstract MapExpressionHelper removeListener(MapChangeListener listener); protected abstract void fireValueChangedEvent(); protected abstract void fireValueChangedEvent(MapChangeListener.Change change); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Implementations private static class SingleInvalidation extends MapExpressionHelper { private final InvalidationListener listener; private SingleInvalidation(ObservableMapValue observable, InvalidationListener listener) { super(observable); this.listener = listener; } @Override protected MapExpressionHelper addListener(InvalidationListener listener) { return new Generic(observable, this.listener, listener); } @Override protected MapExpressionHelper removeListener(InvalidationListener listener) { return (listener.equals(this.listener))? null : this; } @Override protected MapExpressionHelper addListener(ChangeListener> listener) { return new Generic(observable, this.listener, listener); } @Override protected MapExpressionHelper removeListener(ChangeListener> listener) { return this; } @Override protected MapExpressionHelper addListener(MapChangeListener listener) { return new Generic(observable, this.listener, listener); } @Override protected MapExpressionHelper removeListener(MapChangeListener listener) { return this; } @Override protected void fireValueChangedEvent() { listener.invalidated(observable); } @Override protected void fireValueChangedEvent(MapChangeListener.Change change) { listener.invalidated(observable); } } private static class SingleChange extends MapExpressionHelper { private final ChangeListener> listener; private ObservableMap currentValue; private SingleChange(ObservableMapValue observable, ChangeListener> listener) { super(observable); this.listener = listener; this.currentValue = observable.getValue(); } @Override protected MapExpressionHelper addListener(InvalidationListener listener) { return new Generic(observable, listener, this.listener); } @Override protected MapExpressionHelper removeListener(InvalidationListener listener) { return this; } @Override protected MapExpressionHelper addListener(ChangeListener> listener) { return new Generic(observable, this.listener, listener); } @Override protected MapExpressionHelper removeListener(ChangeListener> listener) { return (listener.equals(this.listener))? null : this; } @Override protected MapExpressionHelper addListener(MapChangeListener listener) { return new Generic(observable, this.listener, listener); } @Override protected MapExpressionHelper removeListener(MapChangeListener listener) { return this; } @Override protected void fireValueChangedEvent() { final ObservableMap oldValue = currentValue; currentValue = observable.getValue(); if (currentValue != oldValue) { listener.changed(observable, oldValue, currentValue); } } @Override protected void fireValueChangedEvent(MapChangeListener.Change change) { listener.changed(observable, currentValue, currentValue); } } private static class SingleMapChange extends MapExpressionHelper { private final MapChangeListener listener; private ObservableMap currentValue; private SingleMapChange(ObservableMapValue observable, MapChangeListener listener) { super(observable); this.listener = listener; this.currentValue = observable.getValue(); } @Override protected MapExpressionHelper addListener(InvalidationListener listener) { return new Generic(observable, listener, this.listener); } @Override protected MapExpressionHelper removeListener(InvalidationListener listener) { return this; } @Override protected MapExpressionHelper addListener(ChangeListener> listener) { return new Generic(observable, listener, this.listener); } @Override protected MapExpressionHelper removeListener(ChangeListener> listener) { return this; } @Override protected MapExpressionHelper addListener(MapChangeListener listener) { return new Generic(observable, this.listener, listener); } @Override protected MapExpressionHelper removeListener(MapChangeListener listener) { return (listener.equals(this.listener))? null : this; } @Override protected void fireValueChangedEvent() { final ObservableMap oldValue = currentValue; currentValue = observable.getValue(); if (currentValue != oldValue) { final SimpleChange change = new SimpleChange(observable); if (currentValue == null) { for (final Map.Entry element : oldValue.entrySet()) { listener.onChanged(change.setRemoved(element.getKey(), element.getValue())); } } else if (oldValue == null) { for (final Map.Entry element : currentValue.entrySet()) { listener.onChanged(change.setAdded(element.getKey(), element.getValue())); } } else { for (final Map.Entry element : oldValue.entrySet()) { final K key = element.getKey(); final V oldEntry = element.getValue(); if (currentValue.containsKey(key)) { final V newEntry = currentValue.get(key); if (oldEntry == null ? newEntry != null : !newEntry.equals(oldEntry)) { listener.onChanged(change.setPut(key, oldEntry, newEntry)); } } else { listener.onChanged(change.setRemoved(key, oldEntry)); } } for (final Map.Entry element : currentValue.entrySet()) { final K key = element.getKey(); if (!oldValue.containsKey(key)) { listener.onChanged(change.setAdded(key, element.getValue())); } } } } } @Override protected void fireValueChangedEvent(final MapChangeListener.Change change) { listener.onChanged(new SimpleChange(observable, change)); } } private static class Generic extends MapExpressionHelper { private InvalidationListener[] invalidationListeners; private ChangeListener>[] changeListeners; private MapChangeListener[] mapChangeListeners; private int invalidationSize; private int changeSize; private int mapChangeSize; private boolean locked; private ObservableMap currentValue; private Generic(ObservableMapValue observable, InvalidationListener listener0, InvalidationListener listener1) { super(observable); this.invalidationListeners = new InvalidationListener[] {listener0, listener1}; this.invalidationSize = 2; } private Generic(ObservableMapValue observable, ChangeListener> listener0, ChangeListener> listener1) { super(observable); this.changeListeners = new ChangeListener[] {listener0, listener1}; this.changeSize = 2; this.currentValue = observable.getValue(); } private Generic(ObservableMapValue observable, MapChangeListener listener0, MapChangeListener listener1) { super(observable); this.mapChangeListeners = new MapChangeListener[] {listener0, listener1}; this.mapChangeSize = 2; this.currentValue = observable.getValue(); } private Generic(ObservableMapValue observable, InvalidationListener invalidationListener, ChangeListener> changeListener) { super(observable); this.invalidationListeners = new InvalidationListener[] {invalidationListener}; this.invalidationSize = 1; this.changeListeners = new ChangeListener[] {changeListener}; this.changeSize = 1; this.currentValue = observable.getValue(); } private Generic(ObservableMapValue observable, InvalidationListener invalidationListener, MapChangeListener listChangeListener) { super(observable); this.invalidationListeners = new InvalidationListener[] {invalidationListener}; this.invalidationSize = 1; this.mapChangeListeners = new MapChangeListener[] {listChangeListener}; this.mapChangeSize = 1; this.currentValue = observable.getValue(); } private Generic(ObservableMapValue observable, ChangeListener> changeListener, MapChangeListener listChangeListener) { super(observable); this.changeListeners = new ChangeListener[] {changeListener}; this.changeSize = 1; this.mapChangeListeners = new MapChangeListener[] {listChangeListener}; this.mapChangeSize = 1; this.currentValue = observable.getValue(); } @Override protected MapExpressionHelper addListener(InvalidationListener listener) { if (invalidationListeners == null) { invalidationListeners = new InvalidationListener[] {listener}; invalidationSize = 1; } else { final int oldCapacity = invalidationListeners.length; if (locked) { final int newCapacity = (invalidationSize < oldCapacity)? oldCapacity : (oldCapacity * 3)/2 + 1; invalidationListeners = Arrays.copyOf(invalidationListeners, newCapacity); } else if (invalidationSize == oldCapacity) { invalidationSize = trim(invalidationSize, invalidationListeners); if (invalidationSize == oldCapacity) { final int newCapacity = (oldCapacity * 3)/2 + 1; invalidationListeners = Arrays.copyOf(invalidationListeners, newCapacity); } } invalidationListeners[invalidationSize++] = listener; } return this; } @Override protected MapExpressionHelper removeListener(InvalidationListener listener) { if (invalidationListeners != null) { for (int index = 0; index < invalidationSize; index++) { if (listener.equals(invalidationListeners[index])) { if (invalidationSize == 1) { if ((changeSize == 1) && (mapChangeSize == 0)) { return new SingleChange(observable, changeListeners[0]); } else if ((changeSize == 0) && (mapChangeSize == 1)) { return new SingleMapChange(observable, mapChangeListeners[0]); } invalidationListeners = null; invalidationSize = 0; } else { final int numMoved = invalidationSize - index - 1; final InvalidationListener[] oldListeners = invalidationListeners; if (locked) { invalidationListeners = new InvalidationListener[invalidationListeners.length]; System.arraycopy(oldListeners, 0, invalidationListeners, 0, index+1); } if (numMoved > 0) { System.arraycopy(oldListeners, index+1, invalidationListeners, index, numMoved); } invalidationSize--; if (!locked) { invalidationListeners[--invalidationSize] = null; // Let gc do its work } } break; } } } return this; } @Override protected MapExpressionHelper addListener(ChangeListener> listener) { if (changeListeners == null) { changeListeners = new ChangeListener[] {listener}; changeSize = 1; } else { final int oldCapacity = changeListeners.length; if (locked) { final int newCapacity = (changeSize < oldCapacity)? oldCapacity : (oldCapacity * 3)/2 + 1; changeListeners = Arrays.copyOf(changeListeners, newCapacity); } else if (changeSize == oldCapacity) { changeSize = trim(changeSize, changeListeners); if (changeSize == oldCapacity) { final int newCapacity = (oldCapacity * 3)/2 + 1; changeListeners = Arrays.copyOf(changeListeners, newCapacity); } } changeListeners[changeSize++] = listener; } if (changeSize == 1) { currentValue = observable.getValue(); } return this; } @Override protected MapExpressionHelper removeListener(ChangeListener> listener) { if (changeListeners != null) { for (int index = 0; index < changeSize; index++) { if (listener.equals(changeListeners[index])) { if (changeSize == 1) { if ((invalidationSize == 1) && (mapChangeSize == 0)) { return new SingleInvalidation(observable, invalidationListeners[0]); } else if ((invalidationSize == 0) && (mapChangeSize == 1)) { return new SingleMapChange(observable, mapChangeListeners[0]); } changeListeners = null; changeSize = 0; } else { final int numMoved = changeSize - index - 1; final ChangeListener>[] oldListeners = changeListeners; if (locked) { changeListeners = new ChangeListener[changeListeners.length]; System.arraycopy(oldListeners, 0, changeListeners, 0, index+1); } if (numMoved > 0) { System.arraycopy(oldListeners, index+1, changeListeners, index, numMoved); } changeSize--; if (!locked) { changeListeners[--changeSize] = null; // Let gc do its work } } break; } } } return this; } @Override protected MapExpressionHelper addListener(MapChangeListener listener) { if (mapChangeListeners == null) { mapChangeListeners = new MapChangeListener[] {listener}; mapChangeSize = 1; } else { final int oldCapacity = mapChangeListeners.length; if (locked) { final int newCapacity = (mapChangeSize < oldCapacity)? oldCapacity : (oldCapacity * 3)/2 + 1; mapChangeListeners = Arrays.copyOf(mapChangeListeners, newCapacity); } else if (mapChangeSize == oldCapacity) { mapChangeSize = trim(mapChangeSize, mapChangeListeners); if (mapChangeSize == oldCapacity) { final int newCapacity = (oldCapacity * 3)/2 + 1; mapChangeListeners = Arrays.copyOf(mapChangeListeners, newCapacity); } } mapChangeListeners[mapChangeSize++] = listener; } if (mapChangeSize == 1) { currentValue = observable.getValue(); } return this; } @Override protected MapExpressionHelper removeListener(MapChangeListener listener) { if (mapChangeListeners != null) { for (int index = 0; index < mapChangeSize; index++) { if (listener.equals(mapChangeListeners[index])) { if (mapChangeSize == 1) { if ((invalidationSize == 1) && (changeSize == 0)) { return new SingleInvalidation(observable, invalidationListeners[0]); } else if ((invalidationSize == 0) && (changeSize == 1)) { return new SingleChange(observable, changeListeners[0]); } mapChangeListeners = null; mapChangeSize = 0; } else { final int numMoved = mapChangeSize - index - 1; final MapChangeListener[] oldListeners = mapChangeListeners; if (locked) { mapChangeListeners = new MapChangeListener[mapChangeListeners.length]; System.arraycopy(oldListeners, 0, mapChangeListeners, 0, index+1); } if (numMoved > 0) { System.arraycopy(oldListeners, index+1, mapChangeListeners, index, numMoved); } mapChangeSize--; if (!locked) { mapChangeListeners[--mapChangeSize] = null; // Let gc do its work } } break; } } } return this; } @Override protected void fireValueChangedEvent() { if ((changeSize == 0) && (mapChangeSize == 0)) { notifyListeners(currentValue, null); } else { final ObservableMap oldValue = currentValue; currentValue = observable.getValue(); notifyListeners(oldValue, null); } } @Override protected void fireValueChangedEvent(final MapChangeListener.Change change) { final SimpleChange mappedChange = (mapChangeSize == 0)? null : new SimpleChange(observable, change); notifyListeners(currentValue, mappedChange); } private void notifyListeners(ObservableMap oldValue, SimpleChange change) { final InvalidationListener[] curInvalidationList = invalidationListeners; final int curInvalidationSize = invalidationSize; final ChangeListener>[] curChangeList = changeListeners; final int curChangeSize = changeSize; final MapChangeListener[] curListChangeList = mapChangeListeners; final int curListChangeSize = mapChangeSize; try { locked = true; for (int i = 0; i < curInvalidationSize; i++) { curInvalidationList[i].invalidated(observable); } if ((currentValue != oldValue) || (change != null)) { for (int i = 0; i < curChangeSize; i++) { curChangeList[i].changed(observable, oldValue, currentValue); } if (curListChangeSize > 0) { if (change != null) { for (int i = 0; i < curListChangeSize; i++) { curListChangeList[i].onChanged(change); } } else { change = new SimpleChange(observable); if (currentValue == null) { for (final Map.Entry element : oldValue.entrySet()) { change.setRemoved(element.getKey(), element.getValue()); for (int i = 0; i < curListChangeSize; i++) { curListChangeList[i].onChanged(change); } } } else if (oldValue == null) { for (final Map.Entry element : currentValue.entrySet()) { change.setAdded(element.getKey(), element.getValue()); for (int i = 0; i < curListChangeSize; i++) { curListChangeList[i].onChanged(change); } } } else { for (final Map.Entry element : oldValue.entrySet()) { final K key = element.getKey(); final V oldEntry = element.getValue(); if (currentValue.containsKey(key)) { final V newEntry = currentValue.get(key); if (oldEntry == null ? newEntry != null : !newEntry.equals(oldEntry)) { change.setPut(key, oldEntry, newEntry); for (int i = 0; i < curListChangeSize; i++) { curListChangeList[i].onChanged(change); } } } else { change.setRemoved(key, oldEntry); for (int i = 0; i < curListChangeSize; i++) { curListChangeList[i].onChanged(change); } } } for (final Map.Entry element : currentValue.entrySet()) { final K key = element.getKey(); if (!oldValue.containsKey(key)) { change.setAdded(key, element.getValue()); for (int i = 0; i < curListChangeSize; i++) { curListChangeList[i].onChanged(change); } } } } } } } } finally { locked = false; } } } public static class SimpleChange extends MapChangeListener.Change { private K key; private V old; private V added; private boolean removeOp; private boolean addOp; public SimpleChange(ObservableMap set) { super(set); } public SimpleChange(ObservableMap set, MapChangeListener.Change source) { super(set); key = source.getKey(); old = source.getValueRemoved(); added = source.getValueAdded(); addOp = source.wasAdded(); removeOp = source.wasRemoved(); } public SimpleChange setRemoved(K key, V old) { this.key = key; this.old = old; this.added = null; addOp = false; removeOp = true; return this; } public SimpleChange setAdded(K key, V added) { this.key = key; this.old = null; this.added = added; addOp = true; removeOp = false; return this; } public SimpleChange setPut(K key, V old, V added) { this.key = key; this.old = old; this.added = added; addOp = true; removeOp = true; return this; } @Override public boolean wasAdded() { return addOp; } @Override public boolean wasRemoved() { return removeOp; } @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 (addOp) { if (removeOp) { 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(); } } }