1 /******************************************************************************* 2 * Copyright (c) 2008, 2017 Matthew Hall and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * Matthew Hall - initial API and implementation (bug 221704) 13 * Matthew Hall - bug 223114, 226289, 247875, 246782, 249526, 268022, 14 * 251424 15 * Stefan Xenos <sxenos@gmail.com> - Bug 335792 16 * Stefan Xenos <sxenos@gmail.com> - Bug 474065 17 ******************************************************************************/ 18 19 package org.eclipse.core.internal.databinding.observable.masterdetail; 20 21 import java.util.Collections; 22 import java.util.HashMap; 23 import java.util.Map; 24 25 import org.eclipse.core.databinding.observable.Diffs; 26 import org.eclipse.core.databinding.observable.IObserving; 27 import org.eclipse.core.databinding.observable.ObservableTracker; 28 import org.eclipse.core.databinding.observable.map.IMapChangeListener; 29 import org.eclipse.core.databinding.observable.map.IObservableMap; 30 import org.eclipse.core.databinding.observable.map.ObservableMap; 31 import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory; 32 import org.eclipse.core.databinding.observable.value.IObservableValue; 33 import org.eclipse.core.databinding.observable.value.IValueChangeListener; 34 import org.eclipse.core.runtime.Assert; 35 36 /** 37 * @param <M> 38 * type of the master observable 39 * @param <K> 40 * type of the keys to the inner observable map 41 * @param <V> 42 * type of the values in the inner observable map 43 * @since 1.1 44 * 45 */ 46 public class DetailObservableMap<M, K, V> extends ObservableMap<K, V>implements IObserving { 47 private boolean updating = false; 48 49 private IObservableValue<M> master; 50 private IObservableFactory<? super M, IObservableMap<K, V>> detailFactory; 51 52 private IObservableMap<K, V> detailMap; 53 54 private Object detailKeyType; 55 private Object detailValueType; 56 57 private IValueChangeListener<M> masterChangeListener = event -> { 58 if (isDisposed()) 59 return; 60 ObservableTracker.setIgnore(true); 61 try { 62 Map<K, V> oldMap = new HashMap<>(wrappedMap); 63 updateDetailMap(); 64 fireMapChange(Diffs.computeMapDiff(oldMap, wrappedMap)); 65 } finally { 66 ObservableTracker.setIgnore(false); 67 } 68 }; 69 70 private IMapChangeListener<K, V> detailChangeListener = event -> { 71 if (!updating) { 72 fireMapChange(Diffs.unmodifiableDiff(event.diff)); 73 } 74 }; 75 76 /** 77 * Constructs a new DetailObservableMap 78 * 79 * @param detailFactory 80 * observable factory that creates IObservableMap instances given 81 * the current value of master observable value 82 * @param master 83 * @param keyType 84 * @param valueType 85 * 86 */ DetailObservableMap( IObservableFactory<? super M, IObservableMap<K, V>> detailFactory, IObservableValue<M> master, Object keyType, Object valueType)87 public DetailObservableMap( 88 IObservableFactory<? super M, IObservableMap<K, V>> detailFactory, 89 IObservableValue<M> master, Object keyType, Object valueType) { 90 super(master.getRealm(), Collections.<K, V> emptyMap()); 91 Assert.isTrue(!master.isDisposed(), "Master observable is disposed"); //$NON-NLS-1$ 92 93 this.master = master; 94 this.detailFactory = detailFactory; 95 this.detailKeyType = keyType; 96 this.detailValueType = valueType; 97 98 master.addDisposeListener(staleEvent -> dispose()); 99 100 ObservableTracker.setIgnore(true); 101 try { 102 updateDetailMap(); 103 } finally { 104 ObservableTracker.setIgnore(false); 105 } 106 master.addValueChangeListener(masterChangeListener); 107 } 108 updateDetailMap()109 private void updateDetailMap() { 110 final M masterValue = master.getValue(); 111 if (detailMap != null) { 112 detailMap.removeMapChangeListener(detailChangeListener); 113 detailMap.dispose(); 114 } 115 116 if (masterValue == null) { 117 detailMap = null; 118 wrappedMap = Collections.emptyMap(); 119 } else { 120 ObservableTracker.setIgnore(true); 121 try { 122 detailMap = detailFactory.createObservable(masterValue); 123 } finally { 124 ObservableTracker.setIgnore(false); 125 } 126 DetailObservableHelper.warnIfDifferentRealms(getRealm(), 127 detailMap.getRealm()); 128 wrappedMap = detailMap; 129 130 if (detailKeyType != null) { 131 Object innerKeyType = detailMap.getKeyType(); 132 133 Assert.isTrue(detailKeyType.equals(innerKeyType), 134 "Cannot change key type in a nested observable map"); //$NON-NLS-1$ 135 } 136 137 if (detailValueType != null) { 138 Object innerValueType = detailMap.getValueType(); 139 140 Assert.isTrue(detailValueType.equals(innerValueType), 141 "Cannot change value type in a nested observable map"); //$NON-NLS-1$ 142 } 143 144 detailMap.addMapChangeListener(detailChangeListener); 145 } 146 } 147 148 @Override getKeyType()149 public Object getKeyType() { 150 return detailKeyType; 151 } 152 153 @Override getValueType()154 public Object getValueType() { 155 return detailValueType; 156 } 157 158 @Override put(final K key, final V value)159 public V put(final K key, final V value) { 160 ObservableTracker.setIgnore(true); 161 try { 162 return detailMap.put(key, value); 163 } finally { 164 ObservableTracker.setIgnore(false); 165 } 166 } 167 168 @Override putAll(final Map<? extends K, ? extends V> map)169 public void putAll(final Map<? extends K, ? extends V> map) { 170 ObservableTracker.setIgnore(true); 171 try { 172 detailMap.putAll(map); 173 } finally { 174 ObservableTracker.setIgnore(false); 175 } 176 } 177 178 @Override remove(final Object key)179 public V remove(final Object key) { 180 ObservableTracker.setIgnore(true); 181 try { 182 return detailMap.remove(key); 183 } finally { 184 ObservableTracker.setIgnore(false); 185 } 186 } 187 188 @Override clear()189 public void clear() { 190 ObservableTracker.setIgnore(true); 191 try { 192 detailMap.clear(); 193 } finally { 194 ObservableTracker.setIgnore(false); 195 } 196 } 197 198 @Override dispose()199 public synchronized void dispose() { 200 if (master != null) { 201 master.removeValueChangeListener(masterChangeListener); 202 master = null; 203 masterChangeListener = null; 204 } 205 detailFactory = null; 206 if (detailMap != null) { 207 detailMap.removeMapChangeListener(detailChangeListener); 208 detailMap.dispose(); 209 detailMap = null; 210 } 211 detailChangeListener = null; 212 super.dispose(); 213 } 214 215 @Override getObserved()216 public Object getObserved() { 217 if (detailMap instanceof IObserving) { 218 return ((IObserving) detailMap).getObserved(); 219 } 220 return null; 221 } 222 } 223