Skip to content

Package: EMFFormsContextTracker

EMFFormsContextTracker

nameinstructionbranchcomplexitylinemethod
EMFFormsContextTracker(EMFFormsViewContext)
M: 0 C: 21
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 6
100%
M: 0 C: 1
100%
close()
M: 0 C: 16
100%
M: 1 C: 1
50%
M: 1 C: 1
50%
M: 0 C: 6
100%
M: 0 C: 1
100%
getListener(EMFFormsViewContext)
M: 0 C: 8
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
handleChildContextAdded(EMFFormsViewContext, VElement, EMFFormsViewContext)
M: 0 C: 11
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 4
100%
M: 0 C: 1
100%
handleChildContextRemoved(EMFFormsViewContext, VElement, EMFFormsViewContext)
M: 0 C: 11
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 4
100%
M: 0 C: 1
100%
handleException(Throwable)
M: 9 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
isRoot(EMFFormsViewContext)
M: 0 C: 8
100%
M: 0 C: 2
100%
M: 0 C: 2
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
notifyContextAdded(EMFFormsViewContext, VElement, EMFFormsViewContext)
M: 0 C: 11
100%
M: 0 C: 2
100%
M: 0 C: 2
100%
M: 0 C: 3
100%
M: 0 C: 1
100%
notifyContextDisposed(EMFFormsViewContext)
M: 0 C: 9
100%
M: 0 C: 2
100%
M: 0 C: 2
100%
M: 0 C: 3
100%
M: 0 C: 1
100%
notifyContextInitialized(EMFFormsViewContext)
M: 0 C: 9
100%
M: 0 C: 2
100%
M: 0 C: 2
100%
M: 0 C: 3
100%
M: 0 C: 1
100%
notifyContextRemoved(EMFFormsViewContext, VElement, EMFFormsViewContext)
M: 0 C: 11
100%
M: 1 C: 1
50%
M: 1 C: 1
50%
M: 0 C: 3
100%
M: 0 C: 1
100%
notifyDomainModelChanged(EMFFormsViewContext)
M: 0 C: 9
100%
M: 0 C: 2
100%
M: 0 C: 2
100%
M: 0 C: 3
100%
M: 0 C: 1
100%
onChildContextAdded(TriConsumer)
M: 0 C: 5
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 2
100%
M: 0 C: 1
100%
onChildContextRemoved(TriConsumer)
M: 0 C: 5
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 2
100%
M: 0 C: 1
100%
onContextDisposed(Consumer)
M: 0 C: 5
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 2
100%
M: 0 C: 1
100%
onContextInitialized(Consumer)
M: 0 C: 5
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 2
100%
M: 0 C: 1
100%
onDomainModelChanged(Consumer)
M: 0 C: 5
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 2
100%
M: 0 C: 1
100%
open()
M: 0 C: 13
100%
M: 1 C: 1
50%
M: 1 C: 1
50%
M: 0 C: 4
100%
M: 0 C: 1
100%
safeAccept(Consumer, Object)
M: 14 C: 5
26%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 5 C: 3
38%
M: 0 C: 1
100%
safeAccept(TriConsumer, Object, Object, Object)
M: 14 C: 7
33%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 5 C: 3
38%
M: 0 C: 1
100%

Coverage

1: /*******************************************************************************
2: * Copyright (c) 2019 Christian W. Damus and others.
3: *
4: * All rights reserved. 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: * Christian W. Damus - initial API and implementation
13: ******************************************************************************/
14: package org.eclipse.emfforms.spi.core.services.view;
15:
16: import java.lang.ref.Reference;
17: import java.lang.ref.WeakReference;
18: import java.util.Map;
19: import java.util.Optional;
20: import java.util.WeakHashMap;
21: import java.util.function.Consumer;
22: import java.util.function.Function;
23:
24: import org.eclipse.emf.ecp.view.spi.model.VElement;
25: import org.eclipse.emfforms.common.TriConsumer;
26: import org.eclipse.emfforms.spi.common.report.AbstractReport;
27: import org.eclipse.emfforms.spi.common.report.ReportService;
28:
29: /**
30: * A tracker of the comings and goings of contexts in a hierarchy of {@link EMFFormsViewContext}s.
31: * This supplements the {@link EMFFormsContextListener} and {@link RootDomainModelChangeListener} APIs
32: * with specificity in the call-backs of which context in a hierarchy the call-back pertains to.
33: *
34: * @since 1.22
35: */
36: public class EMFFormsContextTracker {
37:
38:         private final Map<EMFFormsViewContext, ContextListener> contextListeners = new WeakHashMap<>();
39:         private final Function<EMFFormsViewContext, ContextListener> listenerFactory = ContextListener::new;
40:
41:         private final EMFFormsViewContext root;
42:         private final ReportService reportService;
43:
44:         private boolean active;
45:
46:         private Consumer<? super EMFFormsViewContext> initializedHandler;
47:         private Consumer<? super EMFFormsViewContext> disposedHandler;
48:         private TriConsumer<? super EMFFormsViewContext, ? super VElement, ? super EMFFormsViewContext> addedHandler;
49:         private TriConsumer<? super EMFFormsViewContext, ? super VElement, ? super EMFFormsViewContext> removedHandler;
50:         private Consumer<? super EMFFormsViewContext> domainModelChangedHandler;
51:
52:         /**
53:          * Initializes me with the root context to track.
54:          *
55:          * @param context the root context
56:          */
57:         public EMFFormsContextTracker(EMFFormsViewContext context) {
58:                 super();
59:
60:                 root = context;
61:                 reportService = context.getService(ReportService.class);
62:         }
63:
64:         /**
65:          * Query whether a {@code context} is the root of this tracker.
66:          *
67:          * @param context a context
68:          * @return whether it is the root of the context tree being tracked
69:          */
70:         public boolean isRoot(EMFFormsViewContext context) {
71:•                return context == root;
72:         }
73:
74:         /**
75:          * Add a call-back to handle the initialization of a new context, including the
76:          * {@linkplain #isRoot(EMFFormsViewContext) root context}.
77:          *
78:          * @param handler the call-back. It is invoked with the context that was initialized
79:          * @return myself, for convenience of call chaining
80:          */
81:         public EMFFormsContextTracker onContextInitialized(Consumer<? super EMFFormsViewContext> handler) {
82:                 initializedHandler = handler;
83:                 return this;
84:         }
85:
86:         /**
87:          * Add a call-back to handle the disposal of a context, including the {@linkplain #isRoot(EMFFormsViewContext) root
88:          * context}.
89:          *
90:          * @param handler the call-back. It is invoked with the context that was disposed
91:          * @return myself, for convenience of call chaining
92:          */
93:         public EMFFormsContextTracker onContextDisposed(Consumer<? super EMFFormsViewContext> handler) {
94:                 disposedHandler = handler;
95:                 return this;
96:         }
97:
98:         /**
99:          * Add a call-back to handle the replacement of a context's domain model, including the
100:          * {@linkplain #isRoot(EMFFormsViewContext) root context}.
101:          *
102:          * @param handler the call-back. It is invoked with the context that had its domain-model
103:          * replaced
104:          * @return myself, for convenience of call chaining
105:          */
106:         public EMFFormsContextTracker onDomainModelChanged(Consumer<? super EMFFormsViewContext> handler) {
107:                 domainModelChangedHandler = handler;
108:                 return this;
109:         }
110:
111:         /**
112:          * Add a call-back to handle the addition of a new child context to a parent context.
113:          *
114:          * @param handler the call-back. It is invoked with the parent context, the parent element,
115:          * and the child context that was added
116:          * @return myself, for convenience of call chaining
117:          */
118:         public EMFFormsContextTracker onChildContextAdded(
119:                 TriConsumer<? super EMFFormsViewContext, ? super VElement, ? super EMFFormsViewContext> handler) {
120:                 addedHandler = handler;
121:                 return this;
122:         }
123:
124:         /**
125:          * Add a call-back to handle the removal of a child context from a parent context.
126:          *
127:          * @param handler the call-back. It is invoked with the parent context, the parent element with
128:          * which the child context was associated, and the child context that was removed
129:          * @return myself, for convenience of call chaining
130:          */
131:         public EMFFormsContextTracker onChildContextRemoved(
132:                 TriConsumer<? super EMFFormsViewContext, ? super VElement, ? super EMFFormsViewContext> handler) {
133:                 removedHandler = handler;
134:                 return this;
135:         }
136:
137:         /**
138:          * Start tracking my {@linkplain #isRoot(EMFFormsViewContext) root context}.
139:          */
140:         public void open() {
141:•                if (!active) {
142:                         active = true;
143:                         handleChildContextAdded(null, null, root);
144:                 }
145:         }
146:
147:         /**
148:          * Stop tracking contexts.
149:          */
150:         public void close() {
151:•                if (active) {
152:                         active = false;
153:
154:                         try {
155:                                 contextListeners.values().forEach(ContextListener::dispose);
156:                         } finally {
157:                                 contextListeners.clear();
158:                         }
159:                 }
160:         }
161:
162:         private void handleChildContextAdded(EMFFormsViewContext parentContext, VElement parentElement,
163:                 EMFFormsViewContext childContext) {
164:
165:                 final ContextListener listener = getListener(childContext);
166:                 listener.addedToParent(true);
167:                 listener.setParentElement(parentElement);
168:         }
169:
170:         private ContextListener getListener(EMFFormsViewContext context) {
171:                 return contextListeners.computeIfAbsent(context, listenerFactory);
172:         }
173:
174:         private void handleChildContextRemoved(EMFFormsViewContext parentContext, VElement parentElement,
175:                 EMFFormsViewContext childContext) {
176:                 final ContextListener listener = getListener(childContext);
177:                 listener.addedToParent(false);
178:                 listener.setParentElement(null);
179:         }
180:
181:         private void notifyContextInitialized(EMFFormsViewContext context) {
182:•                if (initializedHandler != null) {
183:                         safeAccept(initializedHandler, context);
184:                 }
185:         }
186:
187:         private void notifyContextDisposed(EMFFormsViewContext context) {
188:•                if (disposedHandler != null) {
189:                         safeAccept(disposedHandler, context);
190:                 }
191:         }
192:
193:         private void notifyDomainModelChanged(EMFFormsViewContext context) {
194:•                if (domainModelChangedHandler != null) {
195:                         safeAccept(domainModelChangedHandler, context);
196:                 }
197:         }
198:
199:         private void notifyContextAdded(EMFFormsViewContext parent, VElement parentElement, EMFFormsViewContext child) {
200:•                if (addedHandler != null) {
201:                         safeAccept(addedHandler, parent, parentElement, child);
202:                 }
203:         }
204:
205:         private void notifyContextRemoved(EMFFormsViewContext parent, VElement parentElement, EMFFormsViewContext child) {
206:•                if (removedHandler != null) {
207:                         safeAccept(removedHandler, parent, parentElement, child);
208:                 }
209:         }
210:
211:         private <T> void safeAccept(Consumer<? super T> consumer, T t) {
212:                 try {
213:                         consumer.accept(t);
214:                         // CHECKSTYLE.OFF: IllegalCatch
215:                 } catch (final Exception e) {
216:                         handleException(e);
217:                         // CHECKSTYLE.ON: IllegalCatch
218:                 } catch (final LinkageError e) {
219:                         handleException(e);
220:                 } catch (final AssertionError e) {
221:                         handleException(e);
222:                 }
223:         }
224:
225:         private <T, U, V> void safeAccept(TriConsumer<? super T, ? super U, ? super V> consumer, T t, U u, V v) {
226:                 try {
227:                         consumer.accept(t, u, v);
228:                         // CHECKSTYLE.OFF: IllegalCatch
229:                 } catch (final Exception e) {
230:                         handleException(e);
231:                         // CHECKSTYLE.ON: IllegalCatch
232:                 } catch (final LinkageError e) {
233:                         handleException(e);
234:                 } catch (final AssertionError e) {
235:                         handleException(e);
236:                 }
237:         }
238:
239:         private void handleException(Throwable t) {
240:                 reportService.report(new AbstractReport(t, "Unhandled exception in EMFFormsContextTracker call-back")); //$NON-NLS-1$
241:         }
242:
243:         //
244:         // Nested types
245:         //
246:
247:         /**
248:          * Encapsulation of a context with a listener to its lifecycle events.
249:          */
250:         private final class ContextListener implements EMFFormsContextListener {
251:                 private final RootDomainModelChangeListener domainModelChangeListener = this::domainModelChanged;
252:                 private final Reference<EMFFormsViewContext> context;
253:                 private Reference<VElement> parentElement;
254:                 private boolean addedToParent;
255:
256:                 ContextListener(EMFFormsViewContext context) {
257:                         super();
258:
259:                         this.context = new WeakReference<>(context);
260:                         context.registerEMFFormsContextListener(this);
261:                         context.registerRootDomainModelChangeListener(domainModelChangeListener);
262:                 }
263:
264:                 // Note that if we're invoking this method, it's because there are events to handle
265:                 // involving the context, so the reference cannot have been cleared
266:                 private Optional<EMFFormsViewContext> getContext() {
267:                         return Optional.ofNullable(context.get());
268:                 }
269:
270:                 void dispose() {
271:                         getContext().ifPresent(ctx -> {
272:                                 ctx.unregisterRootDomainModelChangeListener(domainModelChangeListener);
273:                                 ctx.unregisterEMFFormsContextListener(this);
274:                         });
275:                 }
276:
277:                 @Override
278:                 public void contextInitialised() {
279:                         getContext().ifPresent(EMFFormsContextTracker.this::notifyContextInitialized);
280:                 }
281:
282:                 @Override
283:                 public void contextDispose() {
284:                         getContext().ifPresent(EMFFormsContextTracker.this::notifyContextDisposed);
285:                 }
286:
287:                 @Override
288:                 public void childContextAdded(VElement parentElement, EMFFormsViewContext childContext) {
289:                         // Check if we already processed the add
290:                         if (getListener(childContext).isAddedToParent()) {
291:                                 return;
292:                         }
293:
294:                         getContext().ifPresent(ctx -> {
295:                                 notifyContextAdded(ctx, parentElement, childContext);
296:                                 handleChildContextAdded(ctx, parentElement, childContext);
297:                         });
298:                 }
299:
300:                 @Override
301:                 public void childContextDisposed(EMFFormsViewContext childContext) {
302:                         // Check if we already processed the disposal
303:                         if (!getListener(childContext).isAddedToParent()) {
304:                                 return;
305:                         }
306:
307:                         getContext().ifPresent(ctx -> {
308:                                 final VElement parentElement = getListener(childContext).getParentElement();
309:                                 handleChildContextRemoved(ctx, parentElement, childContext);
310:                                 notifyContextRemoved(ctx, parentElement, childContext);
311:                         });
312:                 }
313:
314:                 void domainModelChanged() {
315:                         getContext().ifPresent(EMFFormsContextTracker.this::notifyDomainModelChanged);
316:                 }
317:
318:                 void addedToParent(boolean added) {
319:                         addedToParent = added;
320:                 }
321:
322:                 /**
323:                  * Is my context currently added to (attached to) its parent? Note that the root
324:                  * context is considered as implicitly added to its null parent.
325:                  *
326:                  * @return whether my context is currently known by its parent as a child of it
327:                  */
328:                 boolean isAddedToParent() {
329:                         return addedToParent;
330:                 }
331:
332:                 VElement getParentElement() {
333:                         return parentElement != null ? parentElement.get() : null;
334:                 }
335:
336:                 void setParentElement(VElement parentElement) {
337:                         this.parentElement = parentElement != null ? new WeakReference<>(parentElement) : null;
338:                 }
339:
340:         }
341:
342: }