Skip to content

Package: EMFFormsRevealServiceImpl$1

EMFFormsRevealServiceImpl$1

nameinstructionbranchcomplexitylinemethod
compute(IEclipseContext, String)
M: 0 C: 4
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
{...}
M: 0 C: 6
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
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.internal.core.services.reveal;
15:
16: import java.lang.annotation.Annotation;
17: import java.util.Map;
18: import java.util.Optional;
19:
20: import org.eclipse.e4.core.contexts.ContextFunction;
21: import org.eclipse.e4.core.contexts.ContextInjectionFactory;
22: import org.eclipse.e4.core.contexts.EclipseContextFactory;
23: import org.eclipse.e4.core.contexts.IEclipseContext;
24: import org.eclipse.e4.core.di.InjectionException;
25: import org.eclipse.emf.ecore.EClass;
26: import org.eclipse.emf.ecore.EObject;
27: import org.eclipse.emf.ecore.EStructuralFeature;
28: import org.eclipse.emf.ecp.common.spi.BidirectionalMap;
29: import org.eclipse.emf.ecp.view.spi.model.VElement;
30: import org.eclipse.emfforms.bazaar.Bazaar;
31: import org.eclipse.emfforms.bazaar.BazaarContext;
32: import org.eclipse.emfforms.spi.bazaar.BazaarUtil;
33: import org.eclipse.emfforms.spi.common.report.AbstractReport;
34: import org.eclipse.emfforms.spi.common.report.ReportService;
35: import org.eclipse.emfforms.spi.core.services.reveal.EMFFormsRevealProvider;
36: import org.eclipse.emfforms.spi.core.services.reveal.EMFFormsRevealService;
37: import org.eclipse.emfforms.spi.core.services.reveal.RevealHelper;
38: import org.eclipse.emfforms.spi.core.services.reveal.RevealStep;
39: import org.eclipse.emfforms.spi.core.services.view.EMFFormsContextTracker;
40: import org.eclipse.emfforms.spi.core.services.view.EMFFormsViewContext;
41: import org.osgi.framework.Bundle;
42: import org.osgi.framework.FrameworkUtil;
43:
44: /**
45: * Implementation of the EMF Forms reveal service.
46: *
47: * @since 1.22
48: */
49: public class EMFFormsRevealServiceImpl implements EMFFormsRevealService {
50:
51:         private final Bazaar<RevealStep> stepBazaar = BazaarUtil.createBazaar(RevealStep.FAILED);
52:
53:         /**
54:          * Mapping of detail contexts into which reveal steps delegate drill-down.
55:          * Keys are the view models of the detail contexts that are the values.
56:          */
57:         private final BidirectionalMap<VElement, EMFFormsViewContext> detailContexts = new BidirectionalMap<>();
58:
59:         private final EMFFormsViewContext viewContext;
60:         private IEclipseContext e4Context;
61:
62:         /**
63:          * Initializes me with my view context.
64:          *
65:          * @param viewContext my view context
66:          */
67:         public EMFFormsRevealServiceImpl(EMFFormsViewContext viewContext) {
68:                 super();
69:
70:                 this.viewContext = viewContext;
71:
72:                 new EMFFormsContextTracker(viewContext)
73:                         .onChildContextAdded(this::childContextAdded)
74:                         .onChildContextRemoved(this::childContextRemoved)
75:                         .onContextDisposed(this::contextDisposed)
76:                         .open();
77:         }
78:
79:         private void contextDisposed(EMFFormsViewContext ctx) {
80:                 if (ctx == viewContext && e4Context != null) {
81:                         final IEclipseContext toDispose = e4Context;
82:                         e4Context = null;
83:                         toDispose.dispose();
84:                 }
85:         }
86:
87:         @Override
88:         public boolean reveal(EObject object) {
89:                 final RevealStep step = reveal(object, null, viewContext.getViewModel());
90:                 return perform(step);
91:         }
92:
93:         @Override
94:         public boolean reveal(EObject object, EStructuralFeature feature) {
95:                 RevealStep step = reveal(object, feature, viewContext.getViewModel());
96:                 if (step.isFailed() && feature != null) {
97:                         // Try revealing the object itself, then
98:                         step = reveal(object, viewContext.getViewModel());
99:                 }
100:                 return perform(step);
101:         }
102:
103:         /**
104:          * Invoke a reveal step to reveal an object.
105:          *
106:          * @param revealStep the reveal step to perform
107:          * @return whether the step revealed the object
108:          */
109:         boolean perform(RevealStep revealStep) {
110:                 RevealStep step = revealStep;
111:
112:                 while (!step.isDone()) {
113:                         step = step.drillDown();
114:                 }
115:
116:                 final boolean result = !step.isFailed();
117:
118:                 if (result) {
119:                         step.reveal();
120:                 }
121:
122:                 return result;
123:         }
124:
125:         @Override
126:         public RevealStep reveal(EObject object, VElement scope) {
127:                 return reveal(object, null, scope);
128:         }
129:
130:         @Override
131:         public RevealStep reveal(EObject object, EStructuralFeature feature, VElement scope) {
132:                 final IEclipseContext e4Context = createLocalContext("Reveal Service Bazaar", //$NON-NLS-1$
133:                         scope, object, feature, null);
134:
135:                 try {
136:                         final BazaarContext context = BazaarContext.Builder.empty()
137:                                 .put(IEclipseContext.class, e4Context)
138:                                 .build();
139:
140:                         // We have a default vendor but a vendor that wins the bid may
141:                         // nonetheless create a null
142:                         return Optional.ofNullable(stepBazaar.createProduct(context))
143:                                 .orElse(RevealStep.fail());
144:                 } finally {
145:                         e4Context.dispose();
146:                 }
147:         }
148:
149:         private IEclipseContext getEclipseContext() {
150:                 if (e4Context == null) {
151:                         e4Context = viewContext.getService(IEclipseContext.class);
152:
153:                         if (e4Context == null) {
154:                                 final Bundle bundle = FrameworkUtil.getBundle(EMFFormsRevealServiceImpl.class);
155:                                 e4Context = EclipseContextFactory.createServiceContext(bundle.getBundleContext());
156:                         } else {
157:                                 // Create a private child context that we can dispose
158:                                 e4Context = e4Context.createChild("Reveal Service"); //$NON-NLS-1$
159:                         }
160:
161:                         e4Context.set(EMFFormsRevealService.class, this);
162:                         e4Context.set(EMFFormsRevealServiceImpl.class, this);
163:                         e4Context.set(EMFFormsViewContext.class, viewContext);
164:
165:                         // Bind our helper implementation
166:                         e4Context.set(RevealHelper.class.getName(), new ContextFunction() {
167:                                 @Override
168:                                 public Object compute(IEclipseContext context, String contextKey) {
169:                                         return ContextInjectionFactory.make(RevealHelperImpl.class, context);
170:                                 }
171:                         });
172:                 }
173:                 return e4Context;
174:         }
175:
176:         /**
177:          * Evaluate an {@code annotation} computation in a temporary Eclipse context.
178:          *
179:          * @param <T> the type of {@code computation} to perform
180:          * @param annotation the annotation of the method to invoke
181:          * @param resultType the type of {@code computation} to perform
182:          * @param element a view-model element to inject into the context
183:          * @param object a domain-model element (being revealed) to inject into the context
184:          * @param feature the structural feature (if any) to inject into the context
185:          * @param computation the computation to perform
186:          * @return the result of the {@code computation}
187:          */
188:         final <T> Optional<T> evaluate(Class<? extends Annotation> annotation,
189:                 Class<T> resultType, VElement element, EObject object, EStructuralFeature feature,
190:                 Object computation) {
191:
192:                 return evaluate(annotation, resultType, element, object, feature, null, computation);
193:         }
194:
195:         /**
196:          * Evaluate an {@code annotation} computation in a temporary Eclipse context.
197:          *
198:          * @param <T> the type of {@code computation} to perform
199:          * @param annotation the annotation of the method to invoke
200:          * @param resultType the type of {@code computation} to perform
201:          * @param element a view-model element to inject into the context
202:          * @param object a domain-model element (being revealed) to inject into the context
203:          * @param feature the structural feature (if any) to inject into the context
204:          * @param parameters additional variables to inject
205:          * @param computation the computation to perform
206:          * @return the result of the {@code computation}
207:          */
208:         final <T> Optional<T> evaluate(Class<? extends Annotation> annotation,
209:                 Class<T> resultType, VElement element, EObject object, EStructuralFeature feature,
210:                 Map<Class<?>, ?> parameters, Object computation) {
211:
212:                 final IEclipseContext localContext = createLocalContext("invocation", //$NON-NLS-1$
213:                         element, object, feature, parameters);
214:
215:                 try {
216:                         return evaluate(annotation, resultType, localContext, computation);
217:                 } finally {
218:                         localContext.dispose();
219:                 }
220:         }
221:
222:         /**
223:          * Evaluate an {@code annotation} computation in a temporary Eclipse context.
224:          *
225:          * @param <T> the type of {@code computation} to perform
226:          * @param annotation the annotation of the method to invoke
227:          * @param resultType the type of {@code computation} to perform
228:          * @param context the Eclipse context for method injection
229:          * @param computation the computation to perform
230:          * @return the result of the {@code computation}
231:          */
232:         final <T> Optional<T> evaluate(Class<? extends Annotation> annotation, Class<T> resultType,
233:                 IEclipseContext context, Object computation) {
234:
235:                 Object result;
236:                 try {
237:                         result = ContextInjectionFactory.invoke(computation, annotation, context, null);
238:                 } catch (final InjectionException e) {
239:                         report(new AbstractReport(e.getCause(), e.getMessage()));
240:                         result = null;
241:                 }
242:
243:                 return Optional.ofNullable(result).filter(resultType::isInstance).map(resultType::cast);
244:         }
245:
246:         /**
247:          * Create a local context to overlay on our primary context for method invocation.
248:          *
249:          * @param debugLabel a debug label for the context
250:          * @param viewModel the view-model element to inject
251:          * @param domainModel the domain-model element to inject
252:          * @param feature the structural feature to inject, or {@code null} if none
253:          * @param parameters additional variables to inject
254:          *
255:          * @return the local (overlay) injection context
256:          */
257:         private IEclipseContext createLocalContext(String debugLabel, VElement viewModel, EObject domainModel,
258:                 EStructuralFeature feature, Map<Class<?>, ?> parameters) {
259:
260:                 final IEclipseContext result = getEclipseContext().createChild(debugLabel);
261:
262:                 fill(result, viewModel, domainModel, feature);
263:
264:                 if (parameters != null) {
265:                         for (final Map.Entry<Class<?>, ?> entry : parameters.entrySet()) {
266:                                 final String key = entry.getKey().getName();
267:                                 if (!result.containsKey(key)) {
268:                                         result.set(key, entry.getValue());
269:                                 }
270:                         }
271:                 }
272:
273:                 return result;
274:         }
275:
276:         private void fill(IEclipseContext context, VElement viewModel, EObject domainModel, EStructuralFeature feature) {
277:                 final String eObjectClassName = EObject.class.getName();
278:                 context.set(eObjectClassName, domainModel);
279:                 if (feature != null) {
280:                         context.set(EStructuralFeature.class, feature);
281:                 }
282:
283:                 // It is more important for the view model to support specific types in the
284:                 // injection because that is more likely to be what reveal providers filter
285:                 // on (after all, the renderers employed depend on the view model, not the
286:                 // domain model that they render). We cannot do the same for the domain
287:                 // model because of the special case of the View Model Editor, in which the
288:                 // domain model is an instance of the view model packages
289:                 context.set(viewModel.eClass().getInstanceClassName(), viewModel);
290:                 for (final EClass next : viewModel.eClass().getEAllSuperTypes()) {
291:                         final String superclassName = next.getInstanceClassName();
292:
293:                         // In case EObject is an explicit superclass, don't hide the domain model
294:                         if (!eObjectClassName.equals(superclassName)) {
295:                                 context.set(superclassName, viewModel);
296:                         }
297:                 }
298:
299:                 final EMFFormsViewContext viewContext = getViewContext(viewModel);
300:                 if (viewContext != null) {
301:                         context.set(EMFFormsViewContext.class, viewContext);
302:                 }
303:         }
304:
305:         private void report(AbstractReport report) {
306:                 final ReportService service = viewContext.getService(ReportService.class);
307:                 if (service != null) {
308:                         service.report(report);
309:                 } else if (report.hasException()) {
310:                         report.getException().printStackTrace();
311:                 }
312:         }
313:
314:         /**
315:          * Get the nearest context for a view model {@code element}. This is either some active
316:          * detail context or the root context that owns me.
317:          *
318:          * @param element a view-model element
319:          * @return its most specific applicable context
320:          */
321:         protected EMFFormsViewContext getViewContext(VElement element) {
322:                 EMFFormsViewContext result = null;
323:
324:                 // Don't just get the root container, but the container that is a view that
325:                 // either is the root or is the detail view of (e.g.) a table or tree
326:                 for (EObject parent = element; result == null && parent != null; parent = parent.eContainer()) {
327:                         if (parent instanceof VElement) {
328:                                 result = detailContexts.getValue((VElement) parent);
329:                         }
330:                 }
331:                 if (result == null) {
332:                         result = viewContext;
333:                 }
334:                 return result;
335:         }
336:
337:         @Override
338:         public void addRevealProvider(EMFFormsRevealProvider provider) {
339:                 stepBazaar.addVendor(provider);
340:         }
341:
342:         @Override
343:         public void removeRevealProvider(EMFFormsRevealProvider provider) {
344:                 stepBazaar.removeVendor(provider);
345:         }
346:
347:         private void childContextAdded(EMFFormsViewContext parentContext, VElement parentElement,
348:                 EMFFormsViewContext childContext) {
349:
350:                 detailContexts.put(childContext.getViewModel(), childContext);
351:         }
352:
353:         private void childContextRemoved(EMFFormsViewContext parentContext, VElement parentElement,
354:                 EMFFormsViewContext childContext) {
355:
356:                 detailContexts.removeByValue(childContext);
357:         }
358:
359:         /**
360:          * Find the view model context that is the child context for details of the given
361:          * master selection.
362:          *
363:          * @param masterSelection an object selected in the master control
364:          * @return the detail context, or {@code null} if none
365:          */
366:         EMFFormsViewContext getDetailContext(EObject masterSelection) {
367:                 return detailContexts.values().stream()
368:                         .filter(ctx -> ctx.getDomainModel() == masterSelection)
369:                         .findAny().orElse(null);
370:         }
371:
372: }