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