Skip to content

Package: ValidationServiceImpl$1

ValidationServiceImpl$1

nameinstructionbranchcomplexitylinemethod
afterValidate(EObject, Diagnostic)
M: 1 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
onValidate(EObject, Diagnostic)
M: 0 C: 7
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 2
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) 2011-2019 EclipseSource Muenchen GmbH 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: * Eugen - initial API and implementation
13: * Christian W. Damus - bugs 533522, 543160, 545686, 527686, 548761, 552127, 552715
14: ******************************************************************************/
15: package org.eclipse.emf.ecp.view.internal.validation;
16:
17: import static org.eclipse.emf.ecp.view.spi.validation.ValidationServiceConstants.PROPAGATION_LIMIT_KEY;
18: import static org.eclipse.emf.ecp.view.spi.validation.ValidationServiceConstants.PROPAGATION_UNLIMITED_VALUE;
19:
20: import java.text.MessageFormat;
21: import java.util.ArrayList;
22: import java.util.Collection;
23: import java.util.Collections;
24: import java.util.Comparator;
25: import java.util.HashMap;
26: import java.util.Iterator;
27: import java.util.LinkedHashMap;
28: import java.util.LinkedHashSet;
29: import java.util.List;
30: import java.util.Map;
31: import java.util.NavigableMap;
32: import java.util.Queue;
33: import java.util.Set;
34: import java.util.TreeMap;
35: import java.util.concurrent.ConcurrentHashMap;
36: import java.util.concurrent.atomic.AtomicBoolean;
37: import java.util.function.Function;
38: import java.util.stream.Collectors;
39:
40: import org.eclipse.core.runtime.IStatus;
41: import org.eclipse.emf.common.notify.AdapterFactory;
42: import org.eclipse.emf.common.notify.Notification;
43: import org.eclipse.emf.common.notify.Notifier;
44: import org.eclipse.emf.common.util.BasicDiagnostic;
45: import org.eclipse.emf.common.util.BasicEList;
46: import org.eclipse.emf.common.util.Diagnostic;
47: import org.eclipse.emf.common.util.EList;
48: import org.eclipse.emf.common.util.TreeIterator;
49: import org.eclipse.emf.ecore.EObject;
50: import org.eclipse.emf.ecore.EReference;
51: import org.eclipse.emf.ecore.EStructuralFeature;
52: import org.eclipse.emf.ecore.EStructuralFeature.Setting;
53: import org.eclipse.emf.ecore.EValidator.SubstitutionLabelProvider;
54: import org.eclipse.emf.ecore.InternalEObject;
55: import org.eclipse.emf.ecore.util.EcoreUtil;
56: import org.eclipse.emf.ecp.common.spi.UniqueSetting;
57: import org.eclipse.emf.ecp.view.spi.context.ViewModelContext;
58: import org.eclipse.emf.ecp.view.spi.model.ModelChangeAddRemoveListener;
59: import org.eclipse.emf.ecp.view.spi.model.ModelChangeNotification;
60: import org.eclipse.emf.ecp.view.spi.model.VControl;
61: import org.eclipse.emf.ecp.view.spi.model.VDiagnostic;
62: import org.eclipse.emf.ecp.view.spi.model.VDomainModelReference;
63: import org.eclipse.emf.ecp.view.spi.model.VDomainModelReferenceSegment;
64: import org.eclipse.emf.ecp.view.spi.model.VElement;
65: import org.eclipse.emf.ecp.view.spi.model.VViewFactory;
66: import org.eclipse.emf.ecp.view.spi.model.VViewPackage;
67: import org.eclipse.emf.ecp.view.spi.validation.IncrementalValidationService;
68: import org.eclipse.emf.ecp.view.spi.validation.ValidationProvider;
69: import org.eclipse.emf.ecp.view.spi.validation.ValidationService;
70: import org.eclipse.emf.ecp.view.spi.validation.ValidationUpdateListener;
71: import org.eclipse.emf.ecp.view.spi.validation.ViewValidationListener;
72: import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
73: import org.eclipse.emf.edit.provider.ReflectiveItemProviderAdapterFactory;
74: import org.eclipse.emfforms.common.internal.validation.DiagnosticHelper;
75: import org.eclipse.emfforms.common.spi.validation.ValidationResultListener;
76: import org.eclipse.emfforms.spi.common.report.AbstractReport;
77: import org.eclipse.emfforms.spi.common.report.ReportService;
78: import org.eclipse.emfforms.spi.common.validation.DiagnosticFrequencyMap;
79: import org.eclipse.emfforms.spi.core.services.controlmapper.EMFFormsSettingToControlMapper;
80: import org.eclipse.emfforms.spi.core.services.controlmapper.SubControlMapper;
81: import org.eclipse.emfforms.spi.core.services.databinding.DatabindingFailedException;
82: import org.eclipse.emfforms.spi.core.services.databinding.DatabindingFailedReport;
83: import org.eclipse.emfforms.spi.core.services.mappingprovider.EMFFormsMappingProviderManager;
84: import org.eclipse.emfforms.spi.core.services.view.EMFFormsContextTracker;
85: import org.eclipse.emfforms.spi.core.services.view.EMFFormsViewContext;
86: import org.eclipse.emfforms.spi.localization.EMFFormsLocalizationService;
87: import org.eclipse.osgi.util.NLS;
88:
89: /**
90: * Validation service that, once instantiated, synchronizes the validation result of a model element with its
91: * Renderable.
92: *
93: * @author Eugen Neufeld
94: *
95: */
96: public class ValidationServiceImpl implements ValidationService, IncrementalValidationService {
97:
98:         /**
99:          * The model change listener for the view model.
100:          *
101:          */
102:         private class ViewModelChangeListener implements ModelChangeAddRemoveListener {
103:                 private final EMFFormsViewContext context;
104:
105:                 private boolean initialized;
106:
107:                 ViewModelChangeListener(EMFFormsViewContext context) {
108:                         super();
109:
110:                         this.context = context;
111:                 }
112:
113:                 /**
114:                  * Start listening for changes in the view model.
115:                  */
116:                 void start() {
117:                         initialized = true;
118:                 }
119:
120:                 @Override
121:                 public void notifyChange(ModelChangeNotification notification) {
122:                         if (VViewPackage.eINSTANCE.getElement_Enabled() == notification.getRawNotification()
123:                                 .getFeature()
124:                                 || VViewPackage.eINSTANCE.getElement_Visible() == notification.getRawNotification()
125:                                         .getFeature()) {
126:                                 if (VViewPackage.eINSTANCE.getControl().isInstance(notification.getNotifier())) {
127:                                         final VControl control = (VControl) notification.getNotifier();
128:                                         final VDomainModelReference domainModelReference = control.getDomainModelReference();
129:                                         if (domainModelReference == null) {
130:                                                 return;
131:                                         }
132:                                         try {
133:                                                 handleControlNotification(notification, control, domainModelReference);
134:                                         } catch (final DatabindingFailedException ex) {
135:                                                 reportService.report(new DatabindingFailedReport(ex));
136:                                                 return;
137:                                         }
138:
139:                                 }
140:                         }
141:                         if (!VElement.class.isInstance(notification.getNotifier())) {
142:                                 return;
143:                         }
144:                         switch (notification.getRawNotification().getEventType()) {
145:                         case Notification.REMOVE:
146:                         case Notification.REMOVE_MANY:
147:                                 final Map<VElement, VDiagnostic> map = Collections.emptyMap();
148:                                 reevaluateToTop((VElement) notification.getNotifier(), map);
149:                                 break;
150:                         default:
151:                                 break;
152:                         }
153:                 }
154:
155:                 private void handleControlNotification(ModelChangeNotification notification, VControl control,
156:                         VDomainModelReference domainModelReference) throws DatabindingFailedException {
157:                         if (VViewPackage.eINSTANCE.getElement_Enabled() == notification.getRawNotification().getFeature()) {
158:                                 control.setDiagnostic(null);
159:                         }
160:
161:                         final Set<UniqueSetting> settingsForControl = controlMapper.getSettingsForControl(control);
162:                         final Set<EObject> eObjectsToValidate = new LinkedHashSet<EObject>();
163:                         for (final UniqueSetting setting : settingsForControl) {
164:                                 eObjectsToValidate.add(setting.getEObject());
165:                         }
166:                         validate(eObjectsToValidate);
167:
168:                 }
169:
170:                 @Override
171:                 public void notifyAdd(Notifier notifier) {
172:                         if (!initialized) {
173:                                 // Not interested in discover of the view model because we already validated everything
174:                                 return;
175:                         }
176:
177:                         if (VDomainModelReference.class.isInstance(notifier)
178:                                 && !VDomainModelReference.class.isInstance(EObject.class.cast(notifier).eContainer())
179:                                 && !VDomainModelReferenceSegment.class.isInstance(EObject.class.cast(notifier).eContainer())) {
180:
181:                                 final VDomainModelReference domainModelReference = VDomainModelReference.class.cast(notifier);
182:                                 /*
183:                                  * We only need to validate something if the new DMR belongs to a VControl. Only in this case there can
184:                                  * be a rendered UI control which needs to show validation results for the DMR's settings.
185:                                  */
186:                                 if (VControl.class.isInstance(domainModelReference.eContainer())) {
187:                                         final Set<EObject> eObjectsToValidate = new LinkedHashSet<>();
188:                                         final Set<UniqueSetting> settings = mappingProviderManager.getAllSettingsFor(domainModelReference,
189:                                                 context.getDomainModel());
190:                                         for (final UniqueSetting setting : settings) {
191:                                                 final EObject object = setting.getEObject();
192:                                                 if (object != null) {
193:                                                         eObjectsToValidate.add(object);
194:                                                 }
195:                                         }
196:                                         validate(eObjectsToValidate);
197:                                 }
198:                         }
199:                 }
200:
201:                 @Override
202:                 public void notifyRemove(Notifier notifier) {
203:                         // do nothing
204:                 }
205:
206:         }
207:
208:         /**
209:          * The {@link ValidationDomainModelChangeListener} for the domain model.
210:          *
211:          */
212:         private class ValidationDomainModelChangeListener implements ModelChangeAddRemoveListener {
213:                 private final ViewModelContext context;
214:
215:                 ValidationDomainModelChangeListener(ViewModelContext context) {
216:                         super();
217:
218:                         this.context = context;
219:                 }
220:
221:                 @Override
222:                 public void notifyChange(ModelChangeNotification notification) {
223:                         if (ValidationNotification.class.isInstance(notification.getRawNotification())) {
224:                                 validate(notification.getNotifier());
225:                                 return;
226:                         }
227:
228:                         if (isIgnore(notification)) {
229:                                 return;
230:                         }
231:
232:                         final Notification rawNotification = notification.getRawNotification();
233:                         final int eventType = rawNotification.getEventType();
234:
235:                         // Special cases
236:                         if (eventType == Notification.ADD || eventType == Notification.ADD_MANY) {
237:                                 handleAdd(notification);
238:                                 return;
239:                         } else if (eventType == Notification.REMOVE || eventType == Notification.REMOVE_MANY) {
240:                                 handleRemove(notification);
241:                                 return;
242:                         }
243:
244:                         // Default case
245:                         validate(notification.getNotifier());
246:                         if (EReference.class.isInstance(notification.getStructuralFeature())) {
247:                                 if (notification.getRawNotification().getNewValue() != null) {
248:                                         final Object newValue = notification.getRawNotification().getNewValue();
249:                                         /*
250:                                          * unset on a list has a boolean as a new value. therefore we need to check if new value is an
251:                                          * EObject
252:                                          */
253:                                         if (EObject.class.isInstance(newValue)) {
254:                                                 validate((EObject) newValue);
255:                                         }
256:                                 }
257:                         }
258:                 }
259:
260:                 /**
261:                  * Indicates whether the given {@link ModelChangeNotification} should be ignored.
262:                  *
263:                  * @param notification the {@link ModelChangeNotification} to check.
264:                  * @return {@code true} if the given notification should be ignored, {@code false} otherwise.
265:                  */
266:                 private boolean isIgnore(ModelChangeNotification notification) {
267:                         if (notification.getRawNotification().isTouch()) {
268:                                 return true;
269:                         }
270:                         final int eventType = notification.getRawNotification().getEventType();
271:                         if (eventType == Notification.REMOVING_ADAPTER) {
272:                                 return true;
273:                         }
274:                         if (eventType == Notification.SET) {
275:                                 if (EReference.class.isInstance(notification.getStructuralFeature())) {
276:                                         final EReference eReference = EReference.class.cast(notification.getStructuralFeature());
277:                                         if (eReference.isContainer() && notification.getRawNotification().getNewValue() == null) {
278:                                                 return true;
279:                                         }
280:                                 }
281:                         }
282:                         return false;
283:                 }
284:
285:                 /**
286:                  * Handles the case when the given {@link ModelChangeNotification} originates from an ADD.
287:                  *
288:                  * @param notification
289:                  * the {@link ModelChangeNotification} to handle.
290:                  */
291:                 @SuppressWarnings("unchecked")
292:                 private void handleAdd(ModelChangeNotification notification) {
293:                         final Set<EObject> toValidate = new LinkedHashSet<EObject>();
294:                         toValidate.add(notification.getNotifier());
295:
296:                         // in case of not containment references
297:                         if (EReference.class.isInstance(notification.getStructuralFeature())) {
298:                                 if (Notification.ADD == notification.getRawNotification().getEventType()) {
299:                                         toValidate.addAll(getAllEObjects((EObject) notification.getRawNotification().getNewValue()));
300:                                 } else {
301:                                         toValidate.addAll((Collection<EObject>) notification.getRawNotification().getNewValue());
302:                                 }
303:
304:                         }
305:                         validate(toValidate);
306:                 }
307:
308:                 /**
309:                  * Handles the case when the given {@link ModelChangeNotification} originates from a REMOVE.
310:                  *
311:                  * @param notification
312:                  * the {@link ModelChangeNotification} to handle.
313:                  */
314:                 private void handleRemove(ModelChangeNotification notification) {
315:                         final Notification rawNotification = notification.getRawNotification();
316:                         if (rawNotification.getEventType() == Notification.REMOVE
317:                                 && EReference.class.isInstance(rawNotification.getFeature())) {
318:                                 cleanControlDiagnostics(EObject.class.cast(notification.getNotifier()),
319:                                         EReference.class.cast(rawNotification.getFeature()),
320:                                         EObject.class.cast(rawNotification.getOldValue()));
321:                         }
322:                         // TODO JF since we now have an indexed dmr, this should clean diagnostics, too, doesn't it?
323:                         validate(notification.getNotifier());
324:                 }
325:
326:                 @Override
327:                 public void notifyAdd(Notifier notifier) {
328:                         if (notifier == context.getDomainModel()) {
329:                                 validate(getAllEObjectsToValidate(context));
330:                         }
331:                 }
332:
333:                 @Override
334:                 public void notifyRemove(Notifier notifier) {
335:                 }
336:
337:         }
338:
339:         private org.eclipse.emfforms.common.spi.validation.ValidationService validationService;
340:         private ValidationDomainModelChangeListener domainChangeListener;
341:         private ViewModelContext rootContext;
342:         private EMFFormsContextTracker contextTracker;
343:         private final Map<EMFFormsViewContext, ViewModelChangeListener> viewModelChangeListeners = new HashMap<>();
344:         private final Queue<EObject> validationQueue = new ConcurrentLinkedSetQueue<>();
345:         private final Set<EObject> validated = Collections.newSetFromMap(new ConcurrentHashMap<>());
346:         private final AtomicBoolean validationRunning = new AtomicBoolean(false);
347:
348:         // In a typical application, these lists will usually have zero or one element. In
349:         // any case, uniqueness is ensured by the validation algorithm and so needs not be
350:         // enforced by the collection, so just use simple lists
351:         private final Map<UniqueSetting, List<Diagnostic>> currentUpdates = new ConcurrentHashMap<UniqueSetting, List<Diagnostic>>();
352:         private final Function<Object, List<Diagnostic>> diagnosticListFactory = __ -> new ArrayList<>(3);
353:
354:         private ComposedAdapterFactory adapterFactory;
355:         private ReportService reportService;
356:         private EMFFormsLocalizationService l10n;
357:         private ThresholdDiagnostic.Factory placeholderFactory;
358:
359:         // Maximal number of problems to propagate up the view hierarchy, or negative for no limit
360:         private int propagationThreshold;
361:
362:         private final Set<ValidationUpdateListener> validationUpdateListeners = new LinkedHashSet<>();
363:         private ValidationProviderHelper providerHelper;
364:
365:         @Override
366:         public void instantiate(ViewModelContext context) {
367:                 rootContext = context;
368:                 reportService = context.getService(ReportService.class);
369:                 l10n = context.getService(EMFFormsLocalizationService.class);
370:                 placeholderFactory = new ThresholdDiagnostic.Factory(l10n);
371:                 mappingProviderManager = context.getService(EMFFormsMappingProviderManager.class);
372:                 controlMapper = context.getService(EMFFormsSettingToControlMapper.class);
373:                 providerHelper = new ValidationProviderHelper(context, this);
374:                 final VElement renderable = context.getViewModel();
375:
376:                 if (renderable == null) {
377:                         throw new IllegalStateException("View model must not be null"); //$NON-NLS-1$
378:                 }
379:
380:                 final EObject domainModel = context.getDomainModel();
381:
382:                 if (domainModel == null) {
383:                         throw new IllegalStateException("Domain model must not be null"); //$NON-NLS-1$
384:                 }
385:
386:                 propagationThreshold = getPropagationThreshold();
387:
388:                 validationService = new org.eclipse.emfforms.common.internal.validation.ValidationServiceImpl();
389:                 validationService.addDiagnosticFilter(this::ignoreDiagnostic);
390:                 validationService.addObjectFilter(this::skipValidation);
391:
392:                 validationService.registerValidationResultListener(new ValidationResultListener() {
393:                         @Override
394:                         public void onValidate(EObject eObject, Diagnostic diagnostic) {
395:                                 validated.add(eObject);
396:                         }
397:
398:                         @Override
399:                         public void afterValidate(EObject eObject, Diagnostic diagnostic) {
400:                                 // nothing to do here
401:                         }
402:                 });
403:
404:                 adapterFactory = new ComposedAdapterFactory(new AdapterFactory[] {
405:                         new ReflectiveItemProviderAdapterFactory(),
406:                         new ComposedAdapterFactory(ComposedAdapterFactory.Descriptor.Registry.INSTANCE) });
407:
408:                 final ViewSubstitutionLabelProviderFactory substitutionLabelProviderFactory = getSubstitutionLabelProviderFactory();
409:
410:                 SubstitutionLabelProvider substitutionLabelProvider;
411:                 if (substitutionLabelProviderFactory != null) {
412:                         substitutionLabelProvider = substitutionLabelProviderFactory
413:                                 .createSubstitutionLabelProvider(adapterFactory);
414:                 } else {
415:                         substitutionLabelProvider = new ECPSubstitutionLabelProvider(adapterFactory);
416:                 }
417:
418:                 validationService.setSubstitutionLabelProvider(substitutionLabelProvider);
419:
420:                 providerHelper.initialize();
421:
422:                 domainChangeListener = new ValidationDomainModelChangeListener(context);
423:                 context.registerDomainChangeListener(domainChangeListener);
424:                 addViewModelChangeListener(context);
425:
426:                 contextTracker = new EMFFormsContextTracker(rootContext);
427:                 contextTracker.onContextInitialized(this::contextInitialised)
428:                         .onChildContextAdded(this::childContextAdded)
429:                         .onChildContextRemoved(this::childContextRemoved)
430:                         .open();
431:         }
432:
433:         private boolean skipValidation(EObject eObject) {
434:                 return validated.contains(eObject);
435:         }
436:
437:         private boolean ignoreDiagnostic(EObject eObject, Diagnostic diagnostic) {
438:                 return !controlMapper.hasControlsFor(eObject);
439:         }
440:
441:         private void cleanControlDiagnostics(EObject parent, EReference parentReference, EObject removedEObject) {
442:                 final Set<VElement> controls = controlMapper
443:                         .getControlsFor(UniqueSetting.createSetting(parent, parentReference));
444:                 if (controls == null) {
445:                         return;
446:                 }
447:                 for (final VElement vControl : controls) {
448:                         if (vControl == null) {
449:                                 continue;
450:                         }
451:                         if (vControl.getDiagnostic() == null) {
452:                                 continue;
453:                         }
454:                         final Set<Object> diagnosticsToRemove = new LinkedHashSet<Object>();
455:                         for (final Object diagnosticObject : vControl.getDiagnostic().getDiagnostics()) {
456:                                 final Diagnostic diagnostic = Diagnostic.class.cast(diagnosticObject);
457:                                 if (diagnostic.getData().size() < 1) {
458:                                         continue;
459:                                 }
460:                                 if (removedEObject.equals(DiagnosticHelper.getFirstInternalEObject(diagnostic.getData()))) {
461:                                         diagnosticsToRemove.add(diagnostic);
462:                                 }
463:                         }
464:                         final EList<Object> diagnostics = new BasicEList<Object>(vControl.getDiagnostic().getDiagnostics());
465:                         diagnostics.removeAll(diagnosticsToRemove);
466:                         final VDiagnostic vDiagnostic = VViewFactory.eINSTANCE.createDiagnostic();
467:                         vDiagnostic.getDiagnostics().addAll(diagnostics);
468:                         vControl.setDiagnostic(vDiagnostic);
469:                 }
470:         }
471:
472:         @Override
473:         public void dispose() {
474:                 contextTracker.close();
475:                 viewModelChangeListeners.forEach((ctx, l) -> ctx.unregisterViewChangeListener(l));
476:                 viewModelChangeListeners.clear();
477:                 rootContext.unregisterDomainChangeListener(domainChangeListener);
478:                 providerHelper.dispose();
479:                 adapterFactory.dispose();
480:         }
481:
482:         @Override
483:         public int getPriority() {
484:                 return 1;
485:         }
486:
487:         private void addViewModelChangeListener(EMFFormsViewContext context) {
488:                 final ViewModelChangeListener listener = new ViewModelChangeListener(context);
489:
490:                 if (viewModelChangeListeners.putIfAbsent(context, listener) == null) {
491:                         context.registerViewChangeListener(listener);
492:                         listener.start();
493:                 }
494:         }
495:
496:         private void removeViewModelChangeListener(EMFFormsViewContext context) {
497:                 final ViewModelChangeListener listener = viewModelChangeListeners.remove(context);
498:
499:                 if (listener != null) {
500:                         context.unregisterViewChangeListener(listener);
501:                 }
502:         }
503:
504:         /**
505:          * Returns a collection of all direct and indirect child-EObjects including the parent.
506:          *
507:          * @param eObject the parent
508:          * @return all eobjects
509:          */
510:         private Collection<EObject> getAllEObjects(EObject eObject) {
511:                 final List<EObject> result = new ArrayList<EObject>();
512:                 result.add(eObject);
513:                 final TreeIterator<EObject> iterator = EcoreUtil.getAllContents(eObject, false);
514:                 while (iterator.hasNext()) {
515:                         result.add(iterator.next());
516:                 }
517:                 return result;
518:         }
519:
520:         private Collection<EObject> getAllEObjectsToValidate(ViewModelContext context) {
521:                 return getAllEObjectsToValidate(context, controlMapper);
522:         }
523:
524:         private static Collection<EObject> getAllEObjectsToValidate(ViewModelContext context,
525:                 EMFFormsSettingToControlMapper controlMapper) {
526:
527:                 if (context.getParentContext() == null || !(controlMapper instanceof SubControlMapper)) {
528:                         // Easy: validate the whole model
529:                         return controlMapper.getEObjectsWithSettings();
530:                 }
531:
532:                 final SubControlMapper subMapper = (SubControlMapper) controlMapper;
533:                 final Collection<EObject> result = subMapper.getEObjectsWithSettings(context.getViewModel());
534:
535:                 // And all container objects that have settings
536:                 for (ViewModelContext parent = context.getParentContext(); parent != null; parent = parent
537:                         .getParentContext()) {
538:
539:                         final EObject parentObject = parent.getDomainModel();
540:                         if (controlMapper.hasControlsFor(parentObject)) {
541:                                 result.add(parentObject);
542:                         }
543:                 }
544:
545:                 return result;
546:         }
547:
548:         @Override
549:         public void validate(Iterable<? extends EObject> objects) {
550:                 objects.forEach(validationQueue::add);
551:                 processValidationQueue();
552:         }
553:
554:         @Override
555:         public void validate(Collection<EObject> eObjects) {
556:                 // Delegate the opposite direction to how the default interface method does
557:                 validationQueue.addAll(eObjects);
558:                 processValidationQueue();
559:         }
560:
561:         /**
562:          * Validate the given eObject.
563:          *
564:          * @param eObject the eObject to validate
565:          */
566:         public void validate(EObject eObject) {
567:                 /**
568:                  * We are using a queue here to allow validators to add additional eObjects
569:                  * to the current validation run. This is because we actually want a diagnostics aggregate,
570:                  * otherwise consecutive runs would replace already existing diagnostics on the UI.
571:                  * This is probably not the best way to solve this problem, but it will do for now.
572:                  */
573:                 validationQueue.offer(eObject);
574:                 processValidationQueue();
575:         }
576:
577:         private void processValidationQueue() {
578:                 if (!initialized) {
579:                         return;
580:                 }
581:                 // prohibit re-entry in recursion
582:                 if (!validationRunning.compareAndSet(false, true)) {
583:                         return;
584:                 }
585:                 EObject toValidate;
586:                 while ((toValidate = validationQueue.poll()) != null) {
587:                         validateAndCollectSettings(toValidate);
588:                 }
589:                 update();
590:                 notifyListeners();
591:                 notifyUpdateListeners();
592:                 currentUpdates.clear();
593:                 validated.clear();
594:                 validationRunning.compareAndSet(true, false);
595:         }
596:
597:         /**
598:          * Notifies all listeners.
599:          */
600:         public void notifyListeners() {
601:                 if (validationListeners.size() > 0) {
602:                         final Set<Diagnostic> result = getDiagnosticResult();
603:                         for (final ViewValidationListener l : validationListeners) {
604:                                 l.onNewValidation(result);
605:                         }
606:                 }
607:         }
608:
609:         private void update() {
610:                 // prepare Map
611:                 final Map<VElement, Set<UniqueSetting>> vElementToSettingMap = prepareVElementToSettingMap();
612:
613:                 final Map<VElement, VDiagnostic> controlDiagnosticMap = new LinkedHashMap<VElement, VDiagnostic>();
614:
615:                 for (final VElement control : vElementToSettingMap.keySet()) {
616:                         updateControlDiagnostics(control, vElementToSettingMap, controlDiagnosticMap);
617:
618:                         // add all diagnostics of control which are not in the currentUpdates
619:                         if (control.getDiagnostic() == null) {
620:                                 continue;
621:                         }
622:
623:                         for (final Object diagnosticObject : control.getDiagnostic().getDiagnostics()) {
624:                                 final Diagnostic diagnostic = Diagnostic.class.cast(diagnosticObject);
625:                                 if (diagnostic.getData().size() < 2) {
626:                                         continue;
627:                                 }
628:                                 final EObject diagnosticEobject = DiagnosticHelper.getFirstInternalEObject(diagnostic.getData());
629:                                 final EStructuralFeature eStructuralFeature = DiagnosticHelper
630:                                         .getEStructuralFeature(diagnostic.getData());
631:                                 if (diagnosticEobject == null || eStructuralFeature == null) {
632:                                         continue;
633:                                 }
634:                                 // TODO performance
635:                                 if (!isObjectStillValid(diagnosticEobject, eStructuralFeature, control)) {
636:                                         continue;
637:                                 }
638:                                 final UniqueSetting uniqueSetting2 = UniqueSetting.createSetting(
639:                                         diagnosticEobject, eStructuralFeature);
640:                                 if (!currentUpdates.containsKey(uniqueSetting2)) {
641:                                         controlDiagnosticMap.get(control).getDiagnostics().add(diagnosticObject);
642:                                 }
643:                         }
644:                 }
645:
646:                 updateAndPropagate(controlDiagnosticMap);
647:         }
648:
649:         private void updateControlDiagnostics(final VElement control,
650:                 final Map<VElement, Set<UniqueSetting>> vElementToSettingMap,
651:                 final Map<VElement, VDiagnostic> controlDiagnosticMap) {
652:
653:                 if (!controlDiagnosticMap.containsKey(control)) {
654:                         controlDiagnosticMap.put(control, VViewFactory.eINSTANCE.createDiagnostic());
655:                 }
656:
657:                 for (final UniqueSetting uniqueSetting : vElementToSettingMap.get(control)) {
658:                         final List<Diagnostic> diagnostics = currentUpdates.get(uniqueSetting);
659:                         if (!diagnostics.isEmpty()) {
660:                                 controlDiagnosticMap.get(control).getDiagnostics().addAll(diagnostics);
661:                         }
662:                 }
663:         }
664:
665:         private Map<VElement, Set<UniqueSetting>> prepareVElementToSettingMap() {
666:                 final Map<VElement, Set<UniqueSetting>> result = new LinkedHashMap<VElement, Set<UniqueSetting>>();
667:                 final Function<VElement, Set<UniqueSetting>> setFactory = __ -> new LinkedHashSet<>();
668:
669:                 for (final UniqueSetting uniqueSetting : currentUpdates.keySet()) {
670:                         final Set<VElement> controls = controlMapper.getControlsFor(uniqueSetting);
671:                         if (controls == null || controls.isEmpty()) {
672:                                 continue;
673:                         }
674:                         for (final VElement control : controls) {
675:                                 result.computeIfAbsent(control, setFactory).add(uniqueSetting);
676:                         }
677:                 }
678:                 return result;
679:         }
680:
681:         private boolean isObjectStillValid(EObject diagnosticEobject, EStructuralFeature feature, VElement element) {
682:                 final UniqueSetting setting = UniqueSetting.createSetting(diagnosticEobject, feature);
683:                 return controlMapper.hasMapping(setting, element);
684:         }
685:
686:         /**
687:          * Update the diagnostics attached to the given mapped view-model elements and propagate
688:          * roll-ups of those diagnostics up the view model hierarchy.
689:          *
690:          * @param controlDiagnosticMap the mapping of source diagnostics by (leaf) control
691:          */
692:         private void updateAndPropagate(Map<VElement, VDiagnostic> controlDiagnosticMap) {
693:                 // First, set every control's diagnostics
694:                 controlDiagnosticMap.forEach(VElement::setDiagnostic);
695:
696:                 // Breadth-first climb to the top of the view model tree, layer by layer. First,
697:                 // partition the controls into layers by depth. This map is sorted by decreasing
698:                 // depth (deepest first, to start there)
699:                 final NavigableMap<Integer, Set<VElement>> depthMap = mapByDepth(controlDiagnosticMap.keySet());
700:                 reevaluateToTop(depthMap, controlDiagnosticMap);
701:         }
702:
703:         /**
704:          * Compute a partitioning of view-model {@code elements} by their depth in the model.
705:          * This is effectively the set of layers of the tree, collecting all elements at the same
706:          * depth into subsets (these being the layers at each depth).
707:          *
708:          * @param elements the elements of the view model to partition
709:          * @return the partition into layers, in decreasing order of depth; so, ordered from deepest,
710:          * the bottom layer, to shallowest, the root
711:          */
712:         private NavigableMap<Integer, Set<VElement>> mapByDepth(Collection<? extends VElement> elements) {
713:                 final Function<Integer, Set<VElement>> factory = __ -> new LinkedHashSet<>();
714:                 final NavigableMap<Integer, Set<VElement>> result = new TreeMap<>(
715:                         Comparator.<Integer> naturalOrder().reversed());
716:
717:                 for (final VElement next : elements) {
718:                         result.computeIfAbsent(depth(next), factory).add(next);
719:                 }
720:
721:                 return result;
722:         }
723:
724:         /**
725:          * Compute the depth of an {@code object} in its containing tree.
726:          *
727:          * @param object an object
728:          * @return its depth (zero-based at the root)
729:          */
730:         private int depth(EObject object) {
731:                 int result = 0;
732:                 for (EObject container = object.eContainer(); container != null; container = container.eContainer()) {
733:                         result = result + 1;
734:                 }
735:                 return result;
736:         }
737:
738:         /**
739:          * Obtain the unique set of containers of a bunch of {@code elements} view model.
740:          *
741:          * @param elements elements in the view model
742:          * @return the complete set of view-model elements covering the containers of the {@code elements}
743:          */
744:         private Set<VElement> uniqueContainers(Collection<? extends VElement> elements) {
745:                 return elements.stream().map(EObject::eContainer)
746:                         .filter(VElement.class::isInstance).map(VElement.class::cast)
747:                         .collect(Collectors.toCollection(LinkedHashSet::new));
748:         }
749:
750:         /**
751:          * Perform a breadth-first climb to the top of the view model tree, layer by layer,
752:          * recalculating roll-ups of diagnostics for every container element to aggregate
753:          * all of the diagnostics reported in its sub-tree.
754:          *
755:          * @param depthMap a mapping of layers of elements in the view model by depth, in
756:          * order from bottom (deepest layer) to top (shallowest layer)
757:          * @param controlDiagnosticMap the mapping of source diagnostics by (leaf) control
758:          */
759:         private void reevaluateToTop(NavigableMap<Integer, Set<VElement>> depthMap,
760:                 Map<VElement, VDiagnostic> controlDiagnosticMap) {
761:                 Map.Entry<Integer, Set<VElement>> layer;
762:
763:                 for (layer = depthMap.pollFirstEntry(); layer != null; layer = depthMap.pollFirstEntry()) {
764:                         final int depth = layer.getKey();
765:
766:                         final Set<VElement> containers = uniqueContainers(layer.getValue());
767:                         containers.forEach(container -> reevaluate(container, controlDiagnosticMap));
768:
769:                         // These containers are elements at the next layer up, so we need to iterate over them
770:                         final int depthUp = depth - 1;
771:                         if (depthUp >= 0) {
772:                                 final Set<VElement> layerUp = depthMap.get(depthUp);
773:                                 if (layerUp != null) {
774:                                         layerUp.addAll(containers);
775:                                 } else {
776:                                         depthMap.put(depthUp, containers);
777:                                 }
778:                         }
779:                 }
780:         }
781:
782:         /**
783:          * Recalculate the roll-up of diagnostics fo a given element in the view model tree.
784:          *
785:          * @param vElement an element in the view model tree
786:          * @param controlDiagnosticMap the mapping of source diagnostics by (leaf) control
787:          */
788:         private void reevaluate(VElement vElement, Map<VElement, VDiagnostic> controlDiagnosticMap) {
789:                 final VDiagnostic vDiagnostic = VViewFactory.eINSTANCE.createDiagnostic();
790:                 if (controlDiagnosticMap.containsKey(vElement)) {
791:                         vDiagnostic.getDiagnostics().addAll(controlDiagnosticMap.get(vElement).getDiagnostics());
792:                 }
793:
794:                 // Propagate problems from children
795:                 final DiagnosticFrequencyMap freq = getFrequencyMap(vElement);
796:                 for (final Iterator<EObject> iter = vElement.eContents().iterator(); iter.hasNext();) {
797:                         final EObject eObject = iter.next();
798:                         if (!VElement.class.isInstance(eObject)) {
799:                                 continue;
800:                         }
801:
802:                         final VElement childElement = (VElement) eObject;
803:                         // check that the child is visible and enabled
804:                         if (childElement.getDiagnostic() != null && childElement.isEffectivelyEnabled()
805:                                 && childElement.isVisible()) {
806:
807:                                 final List<?> childDiagnostics = childElement.getDiagnostic().getDiagnostics();
808:
809:                                 // Note that we cannot fill up if we didn't add something
810:                                 if (freq.addAll(childDiagnostics) && freq.isFull()
811:                                         && freq.getDiscardedSeverity() > Diagnostic.WARNING) {
812:
813:                                         // No need to scan further children because it can't get more
814:                                         // full than this with a greater discarded severity
815:                                         break;
816:                                 }
817:                         }
818:                 }
819:                 if (!freq.isEmpty()) {
820:                         freq.appendTo(vDiagnostic.getDiagnostics());
821:                         appendPlaceholder(freq, vDiagnostic.getDiagnostics());
822:                 }
823:                 vElement.setDiagnostic(vDiagnostic);
824:         }
825:
826:         /**
827:          * Perform a breadth-first climb to the top of the view model tree, layer by layer,
828:          * recalculating roll-ups of diagnostics for every container element starting from the
829:          * given {@code element}. It is assumed unnecessary to recompute diagnostics for
830:          * layers deeper than the {@code element} because only the {@code element} has changed.
831:          * But the changes to its diagnostics necessarily can affect roll-ups for all elements
832:          * above it in the tree.
833:          *
834:          * @param element an element in the view model for which to calculate its roll-up of
835:          * diagnostics and, consequently, the roll-ups for all elements above it
836:          * @param controlDiagnosticMap the mapping of source diagnostics by (leaf) control
837:          */
838:         private void reevaluateToTop(VElement element, Map<VElement, VDiagnostic> controlDiagnosticMap) {
839:                 // Breadth-first climb to the top of the view model tree, layer by layer. First,
840:                 // partition the controls into layers by depth. This map is sorted by decreasing
841:                 // depth (deepest first, to start there)
842:                 final NavigableMap<Integer, Set<VElement>> depthMap = mapByDepth(controlDiagnosticMap.keySet());
843:
844:                 // The initial set of containers to process is just the 'element'
845:                 final Set<VElement> containers = Collections.singleton(element);
846:
847:                 // Everything below this element doesn't need to be re-evaluated: their diagnostics
848:                 // are assumed unchanged
849:                 final int cutoff = depth(element);
850:                 depthMap.navigableKeySet().headSet(cutoff, true).clear();
851:                 depthMap.put(cutoff, containers); // Start here
852:
853:                 // Re-evaluate this element
854:                 reevaluate(element, controlDiagnosticMap);
855:
856:                 // And then the rest
857:                 reevaluateToTop(depthMap, controlDiagnosticMap);
858:         }
859:
860:         /**
861:          * Get a frequency map appropriate for collecting the most severe (and throttled)
862:          * diagnostics for a view model element.
863:          *
864:          * @param vElement the view model element
865:          * @return its diagnostic frequency map
866:          */
867:         DiagnosticFrequencyMap getFrequencyMap(VElement vElement) {
868:                 final DiagnosticFrequencyMap result = propagationThreshold < 0 ? DiagnosticFrequencyMap.unlimited()
869:                         : DiagnosticFrequencyMap.limitedTo(propagationThreshold);
870:
871:                 result.addDiagnosticFilter(placeholderFactory.notThresholdDiagnostic());
872:
873:                 return result;
874:         }
875:
876:         /**
877:          * If the {@code freq}uency map is {@link DiagnosticFrequencyMap#isFull() full}, then
878:          * append one more diagnostic covering all those that were discarded (if any).
879:          *
880:          * @param freq a frequency map
881:          * @param diagnostics diagnostics being collected
882:          */
883:         private void appendPlaceholder(DiagnosticFrequencyMap freq, List<? super Diagnostic> diagnostics) {
884:                 if (freq.getDiscardedSeverity() > Diagnostic.OK) {
885:                         diagnostics.add(placeholderFactory.get(freq.getDiscardedSeverity()));
886:                 }
887:         }
888:
889:         private void validateAndCollectSettings(EObject eObject) {
890:                 final long start = System.nanoTime();
891:
892:                 try {
893:                         final Diagnostic diagnostic = validationService.validate(eObject);
894:                         if (diagnostic == null) { // happens if the eObject is being filtered
895:                                 return;
896:                         }
897:                         for (final EStructuralFeature feature : eObject.eClass().getEAllStructuralFeatures()) {
898:                                 final UniqueSetting uniqueSetting = UniqueSetting.createSetting(eObject, feature);
899:                                 currentUpdates.computeIfAbsent(uniqueSetting, diagnosticListFactory);
900:                         }
901:                         analyzeDiagnostic(diagnostic);
902:                 } finally {
903:                         if (System.nanoTime() - start > 1000L * 1000L * 1000L) {
904:                                 reportService.report(new AbstractReport(MessageFormat.format(
905:                                         "Validation took longer than expected for EObject {0}", eObject, //$NON-NLS-1$
906:                                         IStatus.INFO)));
907:                         }
908:                 }
909:         }
910:
911:         private void analyzeDiagnostic(Diagnostic diagnostic) {
912:                 if (diagnostic.getData().size() > 1) {
913:
914:                         final InternalEObject internalEObject = DiagnosticHelper.getFirstInternalEObject(diagnostic.getData());
915:                         final EStructuralFeature eStructuralFeature = DiagnosticHelper.getEStructuralFeature(diagnostic.getData());
916:                         if (internalEObject == null || eStructuralFeature == null) {
917:                                 return;
918:                         }
919:                         if (!internalEObject.eClass().getEAllStructuralFeatures().contains(eStructuralFeature)) {
920:                                 reportService.report(new AbstractReport(
921:                                         MessageFormat.format(
922:                                                 "No Setting can be created for Diagnostic {0} since the EObject's EClass does not contain the Feature.", //$NON-NLS-1$
923:                                                 diagnostic),
924:                                         IStatus.INFO));
925:                                 return;
926:                         }
927:                         final Setting setting = internalEObject.eSetting(eStructuralFeature);
928:                         final UniqueSetting uniqueSetting = UniqueSetting.createSetting(setting);
929:                         currentUpdates.computeIfAbsent(uniqueSetting, diagnosticListFactory).add(diagnostic);
930:                 } else {
931:                         for (final Diagnostic childDiagnostic : diagnostic.getChildren()) {
932:                                 analyzeDiagnostic(childDiagnostic);
933:                         }
934:                 }
935:         }
936:
937:         @Override
938:         public void addValidationProvider(ValidationProvider validationProvider) {
939:                 addValidationProvider(validationProvider, true);
940:         }
941:
942:         @Override
943:         public void addValidationProvider(ValidationProvider validationProvider, boolean revalidate) {
944:                 final ValidationProvider provider = providerHelper.wrap(validationProvider);
945:                 provider.setContext(rootContext);
946:                 validationService.addValidator(provider);
947:                 if (revalidate && rootContext != null) {
948:                         validate(getAllEObjectsToValidate(rootContext));
949:                 }
950:         }
951:
952:         @Override
953:         public void removeValidationProvider(ValidationProvider validationProvider) {
954:                 removeValidationProvider(validationProvider, true);
955:         }
956:
957:         @Override
958:         public void removeValidationProvider(ValidationProvider validationProvider, boolean revalidate) {
959:                 // Get the wrapper that we made on adding this provider
960:                 final ValidationProvider provider = providerHelper.wrap(validationProvider);
961:                 validationService.removeValidator(provider);
962:                 provider.unsetContext(rootContext);
963:                 if (revalidate && rootContext != null) {
964:                         validate(getAllEObjectsToValidate(rootContext));
965:                 }
966:         }
967:
968:         private final Set<ViewValidationListener> validationListeners = new LinkedHashSet<ViewValidationListener>();
969:         private EMFFormsMappingProviderManager mappingProviderManager;
970:         private EMFFormsSettingToControlMapper controlMapper;
971:         private boolean initialized;
972:
973:         @Override
974:         public void registerValidationListener(ViewValidationListener listener) {
975:                 validationListeners.add(listener);
976:
977:                 listener.onNewValidation(getDiagnosticResult());
978:         }
979:
980:         private Set<Diagnostic> getDiagnosticResult() {
981:                 final Set<Diagnostic> result = new LinkedHashSet<Diagnostic>();
982:                 final VDiagnostic diagnostic = rootContext.getViewModel().getDiagnostic();
983:                 if (diagnostic != null) {
984:                         for (final Object diagObject : diagnostic.getDiagnostics()) {
985:                                 result.add((Diagnostic) diagObject);
986:                         }
987:                 }
988:                 return result;
989:         }
990:
991:         @Override
992:         public void deregisterValidationListener(ViewValidationListener listener) {
993:                 validationListeners.remove(listener);
994:         }
995:
996:         @Override
997:         public void childViewModelContextAdded(ViewModelContext childContext) {
998:                 // do nothing
999:         }
1000:
1001:         private void childContextAdded(EMFFormsViewContext parentContext, VElement parentElement,
1002:                 EMFFormsViewContext childContext) {
1003:                 // We are getting this from a parent content that is a view-model context, so the
1004:                 // child really ought to be one, also
1005:                 if (childContext instanceof ViewModelContext) {
1006:                         validate(getAllEObjectsToValidate((ViewModelContext) childContext));
1007:                 }
1008:
1009:                 addViewModelChangeListener(childContext);
1010:         }
1011:
1012:         private void childContextRemoved(EMFFormsViewContext parentContext, VElement parentElement,
1013:                 EMFFormsViewContext childContext) {
1014:                 removeViewModelChangeListener(childContext);
1015:         }
1016:
1017:         private void contextInitialised(EMFFormsViewContext context) {
1018:                 if (context == rootContext) {
1019:                         initialized = true;
1020:                         validate(getAllEObjectsToValidate(rootContext));
1021:                 }
1022:         }
1023:
1024:         /**
1025:          * Returns a {@link ViewSubstitutionLabelProviderFactory}, if any is registered.
1026:          *
1027:          * @return an instance of a {@link ViewSubstitutionLabelProviderFactory}, if any is available,
1028:          * {@code null} otherwise
1029:          */
1030:         protected ViewSubstitutionLabelProviderFactory getSubstitutionLabelProviderFactory() {
1031:                 if (rootContext.hasService(ViewSubstitutionLabelProviderFactory.class)) {
1032:                         return rootContext.getService(ViewSubstitutionLabelProviderFactory.class);
1033:                 }
1034:                 return null;
1035:         }
1036:
1037:         /**
1038:          * Get the problems propagation limit from the annotation view model context.
1039:          *
1040:          * @return the propagation limit, or {@code -1} if unlimited
1041:          *
1042:          * @precondition the {@link ViewModelContext} is already set
1043:          * @precondition the {@link ReportService} is available
1044:          * @precondition the {@link EMFFormsLocalizationService} is available
1045:          */
1046:         private int getPropagationThreshold() {
1047:                 int result = -1; // Internal code for unlimited
1048:
1049:                 final Object value = rootContext.getContextValue(PROPAGATION_LIMIT_KEY);
1050:                 if (value instanceof Integer) {
1051:                         final int intValue = (Integer) value;
1052:                         if (intValue < 0) {
1053:                                 warn("ValidationServiceImpl_limitNegative", value); //$NON-NLS-1$
1054:                         } else {
1055:                                 result = intValue;
1056:                         }
1057:                 } else if (value == null || PROPAGATION_UNLIMITED_VALUE.equals(value)) {
1058:                         return result;
1059:                 } else {
1060:                         warn("ValidationServiceImpl_limitUnknown", value); //$NON-NLS-1$
1061:                 }
1062:
1063:                 return result;
1064:         }
1065:
1066:         /**
1067:          * Issue a warning composed of the indicated localized string with positional {@code arguments}.
1068:          *
1069:          * @param messageKey key to look up in the host bundle's localizations
1070:          * @param arguments zero-indexed arguments to substitute, if any, in the string
1071:          */
1072:         protected void warn(String messageKey, Object... arguments) {
1073:                 final String report = NLS.bind(l10n.getString(ValidationServiceImpl.class, messageKey), arguments);
1074:                 reportService.report(new AbstractReport(report, IStatus.WARNING));
1075:         }
1076:
1077:         /**
1078:          * @since 1.22
1079:          */
1080:         @Override
1081:         public void registerValidationUpdateListener(ValidationUpdateListener listener) {
1082:                 validationUpdateListeners.add(listener);
1083:         }
1084:
1085:         /**
1086:          * @since 1.22
1087:          */
1088:         @Override
1089:         public void deregisterValidationUpdateListener(ValidationUpdateListener listener) {
1090:                 validationUpdateListeners.remove(listener);
1091:         }
1092:
1093:         private void notifyUpdateListeners() {
1094:                 if (validationUpdateListeners.isEmpty()) {
1095:                         return;
1096:                 }
1097:
1098:                 // Build the validation state
1099:                 final Collection<Diagnostic> diagnostics = Collections.unmodifiableCollection(
1100:                         currentUpdates.entrySet().stream()
1101:                                 .map(entry -> summarize(entry.getKey(), entry.getValue()))
1102:                                 .collect(Collectors.toList()));
1103:
1104:                 validationUpdateListeners.forEach(l -> l.validationUpdated(diagnostics));
1105:         }
1106:
1107:         private Diagnostic summarize(UniqueSetting setting, List<Diagnostic> diagnostics) {
1108:                 switch (diagnostics.size()) {
1109:                 case 0:
1110:                         return new BasicDiagnostic(Diagnostic.OK, Activator.PLUGIN_ID, 0, Diagnostic.OK_INSTANCE.getMessage(),
1111:                                 new Object[] { setting.getEObject(), setting.getEStructuralFeature() });
1112:                 case 1:
1113:                         return diagnostics.get(0);
1114:                 default:
1115:                         return new BasicDiagnostic(Activator.PLUGIN_ID, 0, diagnostics, "", //$NON-NLS-1$
1116:                                 new Object[] { setting.getEObject(), setting.getEStructuralFeature() });
1117:                 }
1118:         }
1119:
1120: }