/* * Copyright (c) 2011, 2013, 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.WeakListener; import javafx.collections.*; import java.lang.ref.WeakReference; import java.util.Map; import java.util.Set; /** */ public class BidirectionalContentBinding { private static void checkParameters(Object property1, Object property2) { if ((property1 == null) || (property2 == null)) { throw new NullPointerException("Both parameters must be specified."); } if (property1 == property2) { throw new IllegalArgumentException("Cannot bind object to itself"); } } public static Object bind(ObservableList list1, ObservableList list2) { checkParameters(list1, list2); final ListContentBinding binding = new ListContentBinding(list1, list2); list1.setAll(list2); list1.addListener(binding); list2.addListener(binding); return binding; } public static Object bind(ObservableSet set1, ObservableSet set2) { checkParameters(set1, set2); final SetContentBinding binding = new SetContentBinding(set1, set2); set1.clear(); set1.addAll(set2); set1.addListener(binding); set2.addListener(binding); return binding; } public static Object bind(ObservableMap map1, ObservableMap map2) { checkParameters(map1, map2); final MapContentBinding binding = new MapContentBinding(map1, map2); map1.clear(); map1.putAll(map2); map1.addListener(binding); map2.addListener(binding); return binding; } public static void unbind(Object obj1, Object obj2) { checkParameters(obj1, obj2); if ((obj1 instanceof ObservableList) && (obj2 instanceof ObservableList)) { final ObservableList list1 = (ObservableList)obj1; final ObservableList list2 = (ObservableList)obj2; final ListContentBinding binding = new ListContentBinding(list1, list2); list1.removeListener(binding); list2.removeListener(binding); } else if ((obj1 instanceof ObservableSet) && (obj2 instanceof ObservableSet)) { final ObservableSet set1 = (ObservableSet)obj1; final ObservableSet set2 = (ObservableSet)obj2; final SetContentBinding binding = new SetContentBinding(set1, set2); set1.removeListener(binding); set2.removeListener(binding); } else if ((obj1 instanceof ObservableMap) && (obj2 instanceof ObservableMap)) { final ObservableMap map1 = (ObservableMap)obj1; final ObservableMap map2 = (ObservableMap)obj2; final MapContentBinding binding = new MapContentBinding(map1, map2); map1.removeListener(binding); map2.removeListener(binding); } } private static class ListContentBinding implements ListChangeListener, WeakListener { private final WeakReference> propertyRef1; private final WeakReference> propertyRef2; private boolean updating = false; public ListContentBinding(ObservableList list1, ObservableList list2) { propertyRef1 = new WeakReference>(list1); propertyRef2 = new WeakReference>(list2); } @Override public void onChanged(Change change) { if (!updating) { final ObservableList list1 = propertyRef1.get(); final ObservableList list2 = propertyRef2.get(); if ((list1 == null) || (list2 == null)) { if (list1 != null) { list1.removeListener(this); } if (list2 != null) { list2.removeListener(this); } } else { try { updating = true; final ObservableList dest = (list1 == change.getList())? list2 : list1; while (change.next()) { if (change.wasPermutated()) { dest.remove(change.getFrom(), change.getTo()); dest.addAll(change.getFrom(), change.getList().subList(change.getFrom(), change.getTo())); } else { if (change.wasRemoved()) { dest.remove(change.getFrom(), change.getFrom() + change.getRemovedSize()); } if (change.wasAdded()) { dest.addAll(change.getFrom(), change.getAddedSubList()); } } } } finally { updating = false; } } } } @Override public boolean wasGarbageCollected() { return (propertyRef1.get() == null) || (propertyRef2.get() == null); } @Override public int hashCode() { final ObservableList list1 = propertyRef1.get(); final ObservableList list2 = propertyRef2.get(); final int hc1 = (list1 == null)? 0 : list1.hashCode(); final int hc2 = (list2 == null)? 0 : list2.hashCode(); return hc1 * hc2; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } final Object propertyA1 = propertyRef1.get(); final Object propertyA2 = propertyRef2.get(); if ((propertyA1 == null) || (propertyA2 == null)) { return false; } if (obj instanceof ListContentBinding) { final ListContentBinding otherBinding = (ListContentBinding) obj; final Object propertyB1 = otherBinding.propertyRef1.get(); final Object propertyB2 = otherBinding.propertyRef2.get(); if ((propertyB1 == null) || (propertyB2 == null)) { return false; } if ((propertyA1 == propertyB1) && (propertyA2 == propertyB2)) { return true; } if ((propertyA1 == propertyB2) && (propertyA2 == propertyB1)) { return true; } } return false; } } private static class SetContentBinding implements SetChangeListener, WeakListener { private final WeakReference> propertyRef1; private final WeakReference> propertyRef2; private boolean updating = false; public SetContentBinding(ObservableSet list1, ObservableSet list2) { propertyRef1 = new WeakReference>(list1); propertyRef2 = new WeakReference>(list2); } @Override public void onChanged(Change change) { if (!updating) { final ObservableSet set1 = propertyRef1.get(); final ObservableSet set2 = propertyRef2.get(); if ((set1 == null) || (set2 == null)) { if (set1 != null) { set1.removeListener(this); } if (set2 != null) { set2.removeListener(this); } } else { try { updating = true; final Set dest = (set1 == change.getSet())? set2 : set1; if (change.wasRemoved()) { dest.remove(change.getElementRemoved()); } else { dest.add(change.getElementAdded()); } } finally { updating = false; } } } } @Override public boolean wasGarbageCollected() { return (propertyRef1.get() == null) || (propertyRef2.get() == null); } @Override public int hashCode() { final ObservableSet set1 = propertyRef1.get(); final ObservableSet set2 = propertyRef2.get(); final int hc1 = (set1 == null)? 0 : set1.hashCode(); final int hc2 = (set2 == null)? 0 : set2.hashCode(); return hc1 * hc2; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } final Object propertyA1 = propertyRef1.get(); final Object propertyA2 = propertyRef2.get(); if ((propertyA1 == null) || (propertyA2 == null)) { return false; } if (obj instanceof SetContentBinding) { final SetContentBinding otherBinding = (SetContentBinding) obj; final Object propertyB1 = otherBinding.propertyRef1.get(); final Object propertyB2 = otherBinding.propertyRef2.get(); if ((propertyB1 == null) || (propertyB2 == null)) { return false; } if ((propertyA1 == propertyB1) && (propertyA2 == propertyB2)) { return true; } if ((propertyA1 == propertyB2) && (propertyA2 == propertyB1)) { return true; } } return false; } } private static class MapContentBinding implements MapChangeListener, WeakListener { private final WeakReference> propertyRef1; private final WeakReference> propertyRef2; private boolean updating = false; public MapContentBinding(ObservableMap list1, ObservableMap list2) { propertyRef1 = new WeakReference>(list1); propertyRef2 = new WeakReference>(list2); } @Override public void onChanged(Change change) { if (!updating) { final ObservableMap map1 = propertyRef1.get(); final ObservableMap map2 = propertyRef2.get(); if ((map1 == null) || (map2 == null)) { if (map1 != null) { map1.removeListener(this); } if (map2 != null) { map2.removeListener(this); } } else { try { updating = true; final Map dest = (map1 == change.getMap())? map2 : map1; if (change.wasRemoved()) { dest.remove(change.getKey()); } else { dest.put(change.getKey(), change.getValueAdded()); } } finally { updating = false; } } } } @Override public boolean wasGarbageCollected() { return (propertyRef1.get() == null) || (propertyRef2.get() == null); } @Override public int hashCode() { final ObservableMap map1 = propertyRef1.get(); final ObservableMap map2 = propertyRef2.get(); final int hc1 = (map1 == null)? 0 : map1.hashCode(); final int hc2 = (map2 == null)? 0 : map2.hashCode(); return hc1 * hc2; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } final Object propertyA1 = propertyRef1.get(); final Object propertyA2 = propertyRef2.get(); if ((propertyA1 == null) || (propertyA2 == null)) { return false; } if (obj instanceof MapContentBinding) { final MapContentBinding otherBinding = (MapContentBinding) obj; final Object propertyB1 = otherBinding.propertyRef1.get(); final Object propertyB2 = otherBinding.propertyRef2.get(); if ((propertyB1 == null) || (propertyB2 == null)) { return false; } if ((propertyA1 == propertyB1) && (propertyA2 == propertyB2)) { return true; } if ((propertyA1 == propertyB2) && (propertyA2 == propertyB1)) { return true; } } return false; } } }