Skip to content

Package: PreSetValidationListeners

PreSetValidationListeners

nameinstructionbranchcomplexitylinemethod
PreSetValidationListeners()
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
PreSetValidationListeners(ViewModelContext)
M: 0 C: 9
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 4
100%
M: 0 C: 1
100%
create()
M: 8 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 3 C: 0
0%
M: 1 C: 0
0%
create(ViewModelContext)
M: 0 C: 21
100%
M: 0 C: 2
100%
M: 0 C: 2
100%
M: 0 C: 6
100%
M: 0 C: 1
100%
focus(Text, EStructuralFeature, PreSetValidationServiceRunnable, Runnable)
M: 8 C: 4
33%
M: 1 C: 1
50%
M: 1 C: 1
50%
M: 1 C: 2
67%
M: 0 C: 1
100%
getPreSetValidationService()
M: 19 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 7 C: 0
0%
M: 1 C: 0
0%
init(ViewModelContext)
M: 4 C: 13
76%
M: 1 C: 3
75%
M: 1 C: 2
67%
M: 1 C: 4
80%
M: 0 C: 1
100%
isString(EClassifier)
M: 6 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
validateStrict(EStructuralFeature, Object)
M: 41 C: 19
32%
M: 9 C: 1
10%
M: 5 C: 1
17%
M: 9 C: 5
36%
M: 0 C: 1
100%
verify(Combo, EStructuralFeature)
M: 0 C: 6
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 2
100%
M: 0 C: 1
100%
verify(Combo, EStructuralFeature, VElement)
M: 1 C: 23
96%
M: 2 C: 2
50%
M: 2 C: 1
33%
M: 1 C: 7
88%
M: 0 C: 1
100%
verify(Text, EStructuralFeature)
M: 0 C: 6
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 2
100%
M: 0 C: 1
100%
verify(Text, EStructuralFeature, VElement)
M: 0 C: 24
100%
M: 0 C: 4
100%
M: 0 C: 3
100%
M: 0 C: 8
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: * Edgar Mueller - initial API and implementation
13: * Christian W. Damus - bug 527686
14: ******************************************************************************/
15: package org.eclipse.emf.ecp.edit.internal.swt.util;
16:
17: import java.text.MessageFormat;
18:
19: import javax.xml.datatype.XMLGregorianCalendar;
20:
21: import org.eclipse.emf.common.util.BasicDiagnostic;
22: import org.eclipse.emf.common.util.Diagnostic;
23: import org.eclipse.emf.ecore.EAttribute;
24: import org.eclipse.emf.ecore.EClassifier;
25: import org.eclipse.emf.ecore.EStructuralFeature;
26: import org.eclipse.emf.ecore.util.EObjectValidator;
27: import org.eclipse.emf.ecore.util.EcoreUtil;
28: import org.eclipse.emf.ecore.xml.type.InvalidDatatypeValueException;
29: import org.eclipse.emf.ecp.view.spi.context.ViewModelContext;
30: import org.eclipse.emf.ecp.view.spi.model.VDiagnostic;
31: import org.eclipse.emf.ecp.view.spi.model.VElement;
32: import org.eclipse.emf.ecp.view.spi.model.VViewFactory;
33: import org.eclipse.emfforms.spi.common.validation.PreSetValidationService;
34: import org.eclipse.emfforms.spi.common.validation.PreSetValidationServiceRunnable;
35: import org.eclipse.emfforms.spi.core.services.view.EMFFormsContextListener;
36: import org.eclipse.emfforms.spi.core.services.view.EMFFormsViewContext;
37: import org.eclipse.swt.events.FocusEvent;
38: import org.eclipse.swt.events.FocusListener;
39: import org.eclipse.swt.events.VerifyEvent;
40: import org.eclipse.swt.events.VerifyListener;
41: import org.eclipse.swt.widgets.Combo;
42: import org.eclipse.swt.widgets.Control;
43: import org.eclipse.swt.widgets.Text;
44: import org.osgi.framework.BundleContext;
45: import org.osgi.framework.FrameworkUtil;
46: import org.osgi.util.tracker.ServiceTracker;
47:
48: /**
49: * Utility class for setting up a {@link VerifyListener}
50: * that performs pre-set validation.
51: *
52: */
53: public final class PreSetValidationListeners {
54:
55:         /**
56:          * Singleton instance.
57:          */
58:         @Deprecated
59:         private static PreSetValidationListeners validationListeners;
60:         @Deprecated
61:         private static ServiceTracker<PreSetValidationService, PreSetValidationService> serviceTracker;
62:
63:         private final ViewModelContext context;
64:         private PreSetValidationService preSetValidationService;
65:
66:         @Deprecated
67:         private PreSetValidationListeners() {
68:                 this(null);
69:         }
70:
71:         private PreSetValidationListeners(ViewModelContext context) {
72:                 super();
73:
74:                 this.context = context;
75:
76:                 init(context);
77:         }
78:
79:         /**
80:          * Returns the validation listeners factory.
81:          *
82:          * @return the factory that can be used to create and attach listeners
83:          * @deprecated use {@link #create(ViewModelContext)} instead
84:          */
85:         @Deprecated
86:         public static PreSetValidationListeners create() {
87:•                if (validationListeners == null) {
88:                         validationListeners = new PreSetValidationListeners();
89:                 }
90:                 return validationListeners;
91:         }
92:
93:         /**
94:          * Returns the validation listeners factory.
95:          *
96:          * @param context The {@link ViewModelContext} of the entity that needs pre-validation
97:          * @return the factory that can be used to create and attach listeners
98:          */
99:         public static PreSetValidationListeners create(ViewModelContext context) {
100:                 final String key = PreSetValidationListeners.class.getName();
101:                 PreSetValidationListeners result = (PreSetValidationListeners) context.getContextValue(key);
102:•                if (result == null) {
103:                         result = new PreSetValidationListeners(context);
104:                         context.putContextValue(key, result);
105:                 }
106:
107:                 return result;
108:         }
109:
110:         @Deprecated
111:         private static PreSetValidationService getPreSetValidationService() {
112:•                if (serviceTracker == null) {
113:                         final BundleContext bundleContext = FrameworkUtil
114:                                 .getBundle(PreSetValidationListeners.class)
115:                                 .getBundleContext();
116:                         serviceTracker = new ServiceTracker<>(bundleContext, PreSetValidationService.class, null);
117:                         serviceTracker.open();
118:                 }
119:
120:                 return serviceTracker.getService();
121:         }
122:
123:         private void init(ViewModelContext context) {
124:•                if (context == null) {
125:                         preSetValidationService = getPreSetValidationService();
126:•                } else if (context.hasService(PreSetValidationService.class)) {
127:                         preSetValidationService = context.getService(PreSetValidationService.class);
128:                 }
129:         }
130:
131:         /**
132:          * Attach a {@link VerifyListener} to the given {@link Text} widget.
133:          * Performs pre-set validation for the given {@link EStructuralFeature}
134:          *
135:          * @param text the text widget the created verify listener should be attached to
136:          * @param feature the feature to be validated
137:          */
138:         public void verify(Text text, final EStructuralFeature feature) {
139:                 verify(text, feature, null);
140:         }
141:
142:         /**
143:          * Attach a {@link VerifyListener} to the given {@link Combo} widget.
144:          * Performs pre-set validation for the given {@link EStructuralFeature}
145:          *
146:          * @param combo the combo widget the created verify listener should be attached to
147:          * @param feature the feature to be validated
148:          */
149:         public void verify(Combo combo, final EStructuralFeature feature) {
150:                 verify(combo, feature, null);
151:         }
152:
153:         /**
154:          * Attach a {@link VerifyListener} to the given {@link Text} widget.
155:          * Performs pre-set validation for the given {@link EStructuralFeature} and reports any
156:          * errors to the given {@link VElement}.
157:          *
158:          * @param text the text widget the created verify listener should be attached to
159:          * @param feature the feature to be validated
160:          * @param vElement the {@link VElement} an {@link Diagnostic} may be attached to
161:          */
162:         public void verify(Text text, final EStructuralFeature feature, final VElement vElement) {
163:
164:•                if (!EAttribute.class.isInstance(feature)) {
165:                         // this shouldn't happen as we expect only EDataTypes
166:                         return;
167:                 }
168:
169:                 final EAttribute attribute = (EAttribute) feature;
170:
171:•                if (preSetValidationService != null) {
172:                         final PreSetVerifyListener verifyListener = new PreSetVerifyListener(vElement, attribute,
173:                                 this, preSetValidationService);
174:                         verifyListener.register(text);
175:                 }
176:         }
177:
178:         /**
179:          * Attach a {@link VerifyListener} to the given {@link Combo} widget.
180:          * Performs pre-set validation for the given {@link EStructuralFeature} and reports any
181:          * errors to the given {@link VElement}.
182:          *
183:          * @param combo the combo widget the created verify listener should be attached to
184:          * @param feature the feature to be validated
185:          * @param vElement the {@link VElement} an {@link Diagnostic} may be attached to
186:          */
187:         public void verify(Combo combo, final EStructuralFeature feature, final VElement vElement) {
188:
189:•                if (!EAttribute.class.isInstance(feature)) {
190:                         // this shouldn't happen as we expect only EDataTypes
191:                         return;
192:                 }
193:
194:                 final EAttribute attribute = (EAttribute) feature;
195:
196:•                if (preSetValidationService != null) {
197:                         final PreSetVerifyListener verifyListener = new PreSetVerifyListener(vElement, attribute,
198:                                 this, preSetValidationService);
199:                         verifyListener.register(combo);
200:                 }
201:         }
202:
203:         /**
204:          * Validate a given feature value strictly based on the defined constraints.
205:          *
206:          * @param feature the feature to validate
207:          * @param value the value to validate
208:          * @return the resulting {@link VDiagnostic}
209:          */
210:         protected VDiagnostic validateStrict(EStructuralFeature feature, Object value) {
211:                 final Diagnostic strictDiag = preSetValidationService.validate(feature, value);
212:                 final VDiagnostic vDiagnostic = VViewFactory.eINSTANCE.createDiagnostic();
213:•                if (strictDiag.getSeverity() != Diagnostic.OK) {
214:                         vDiagnostic.getDiagnostics().add(strictDiag);
215:                         return vDiagnostic;
216:                 }
217:•                if (feature.isRequired()) {
218:                         /* value must not be empty, which is not an EDataType-Constraint */
219:•                        if (value == null || isString(feature.getEType()) && "".equals(value)) { //$NON-NLS-1$
220:                                 final BasicDiagnostic multiplicityDiagnostic = new BasicDiagnostic(Diagnostic.ERROR, "", //$NON-NLS-1$
221:                                         EObjectValidator.EOBJECT__EVERY_MULTIPCITY_CONFORMS,
222:                                         MessageFormat.format("The required feature ''{0}'' must be set", feature.getName()), //$NON-NLS-1$
223:                                         new Object[0]);
224:                                 vDiagnostic.getDiagnostics().add(multiplicityDiagnostic);
225:                                 return vDiagnostic;
226:                         }
227:                 }
228:                 return null;
229:         }
230:
231:         private boolean isString(EClassifier classifier) {
232:                 return classifier.getInstanceTypeName().equals(String.class.getCanonicalName());
233:         }
234:
235:         /**
236:          * Attach a {@link FocusListener} to the given {@link Text} widget.
237:          * Performs pre-set validation for the given {@link EStructuralFeature} and
238:          * executes the {@link Runnable} in case the content of the text widget is
239:          * invalid.
240:          *
241:          * @param text the text widget the created verify listener should be attached to
242:          * @param feature the feature to be validated
243:          * @param focusLost code to be executed in case the text is invalid and focus has been lost
244:          * @param focusGained code to be executed in case the focus has been gained
245:          */
246:         public void focus(final Text text, final EStructuralFeature feature,
247:                 final PreSetValidationServiceRunnable focusLost,
248:                 final Runnable focusGained) {
249:•                if (preSetValidationService != null) {
250:                         text.addFocusListener(new FocusListener() {
251:                                 @Override
252:                                 public void focusLost(FocusEvent e) {
253:                                         focusLost.run(preSetValidationService);
254:                                 }
255:
256:                                 @Override
257:                                 public void focusGained(FocusEvent e) {
258:                                         focusGained.run();
259:                                 }
260:                         });
261:                 }
262:         }
263:
264:         /**
265:          * Default VerifyListener implementation.
266:          *
267:          */
268:         public static class PreSetVerifyListener implements VerifyListener {
269:                 private final EAttribute attribute;
270:                 private final VElement vElement;
271:                 private final PreSetValidationListeners validationListeners;
272:                 private final PreSetValidationService preSetValidationService;
273:
274:                 private Control control;
275:
276:                 /**
277:                  * Constructor.
278:                  *
279:                  * @param vElement the {@link VElement} any {@link VDiagnostic} will be attached to
280:                  * @param attribute the {@link EAttribute} to be validated
281:                  * @deprecated This constructor uses the deprecated singleton instances that reference the wrong view-model
282:                  * context. As of the 1.22 release, use the
283:                  * {@link PreSetValidationListeners#verify(Text, EStructuralFeature)} API and its
284:                  * variants, and do not manage these listeners directly
285:                  */
286:                 @Deprecated
287:                 public PreSetVerifyListener(VElement vElement, EAttribute attribute) {
288:                         this(vElement, attribute, PreSetValidationListeners.validationListeners, getPreSetValidationService());
289:                 }
290:
291:                 /**
292:                  * Constructor.
293:                  *
294:                  * @param vElement the {@link VElement} any {@link VDiagnostic} will be attached to
295:                  * @param attribute the {@link EAttribute} to be validated
296:                  * @param validationListeners the validation listeners that I support
297:                  * @param preSetValidationService the validation service to delegate to
298:                  */
299:                 PreSetVerifyListener(VElement vElement, EAttribute attribute, PreSetValidationListeners validationListeners,
300:                         PreSetValidationService preSetValidationService) {
301:
302:                         this.vElement = vElement;
303:                         this.attribute = attribute;
304:                         this.validationListeners = validationListeners;
305:                         this.preSetValidationService = preSetValidationService;
306:
307:                         validationListeners.context.registerEMFFormsContextListener(createContextListener());
308:                 }
309:
310:                 @Override
311:                 public void verifyText(VerifyEvent e) {
312:                         final String changedText = obtainText(e);
313:
314:                         Object changedValue;
315:                         try {
316:                                 changedValue = EcoreUtil.createFromString(attribute.getEAttributeType(), changedText);
317:                         } catch (final IllegalArgumentException | InvalidDatatypeValueException formatException) {
318:                                 if (isInteger(attribute.getEType()) && changedText.isEmpty()
319:                                         || XMLGregorianCalendar.class.isAssignableFrom(attribute.getEType().getInstanceClass())
320:                                         || double.class.isAssignableFrom(attribute.getEType().getInstanceClass())
321:                                         || Double.class.isAssignableFrom(attribute.getEType().getInstanceClass())) {
322:
323:                                         // TODO: corner case, let change propagate in case of integer
324:                                         return;
325:                                 }
326:
327:                                 e.doit = false;
328:                                 return;
329:                         }
330:
331:                         final VDiagnostic prevDiagnostic = vElement == null ? null : vElement.getDiagnostic();
332:                         if (vElement != null) {
333:                                 vElement.setDiagnostic(validationListeners.validateStrict(attribute, changedValue));
334:                         }
335:
336:                         final Diagnostic looseDiag = preSetValidationService.validateLoose(attribute,
337:                                 changedValue);
338:                         if (looseDiag.getSeverity() == Diagnostic.OK) {
339:                                 // loose validation successfully, but keep nevertheless keep validation diagnostic
340:                                 return;
341:                         }
342:
343:                         // loose validation not successfully, revert and restore previous diagnostic, if any
344:                         // TODO: revert only for strings because of un-intuitive behavior for integers
345:                         if (validationListeners.isString(attribute.getEType())) {
346:                                 // remove diagnostic once again, since we revert the change
347:                                 e.doit = false;
348:                                 if (vElement != null) {
349:                                         vElement.setDiagnostic(prevDiagnostic);
350:                                 }
351:
352:                         }
353:                 }
354:
355:                 /**
356:                  * Obtain the text value of the widget.
357:                  *
358:                  * @param event the event
359:                  * @return the current text value
360:                  */
361:                 protected String obtainText(VerifyEvent event) {
362:                         String currentText = ""; //$NON-NLS-1$
363:                         if (event.widget instanceof Text) {
364:                                 currentText = Text.class.cast(event.widget).getText();
365:                         } else if (event.widget instanceof Combo) {
366:                                 currentText = Combo.class.cast(event.widget).getText();
367:                         }
368:                         final String changedText = currentText.substring(0, event.start) + event.text
369:                                 + currentText.substring(event.end);
370:                         return changedText;
371:                 }
372:
373:                 private boolean isInteger(EClassifier classifier) {
374:                         return classifier.getInstanceTypeName().equals(Integer.class.getCanonicalName());
375:                 }
376:
377:                 private void register(Control control) {
378:                         // while the renderer is setting up, or while the context is (re-)initializing from
379:                         // a root domain model change, we should not perform pre-set validation
380:                         this.control = control;
381:
382:                         control.getDisplay().asyncExec(this::start);
383:                 }
384:
385:                 private void start() {
386:                         if (control != null && !control.isDisposed()) {
387:                                 if (control instanceof Text) {
388:                                         ((Text) control).addVerifyListener(this);
389:                                 } else if (control instanceof Combo) {
390:                                         ((Combo) control).addVerifyListener(this);
391:                                 }
392:                         }
393:                 }
394:
395:                 private Control stop() {
396:                         final Control result = control;
397:
398:                         if (control != null && !control.isDisposed()) {
399:                                 if (control instanceof Text) {
400:                                         ((Text) control).removeVerifyListener(this);
401:                                 } else if (control instanceof Combo) {
402:                                         ((Combo) control).removeVerifyListener(this);
403:                                 }
404:                         }
405:
406:                         control = null;
407:                         return result;
408:                 }
409:
410:                 private EMFFormsContextListener createContextListener() {
411:                         return new EMFFormsContextListener() {
412:
413:                                 private Control control;
414:
415:                                 @Override
416:                                 public void contextInitialised() {
417:                                         if (control != null && !control.isDisposed()) {
418:                                                 register(control);
419:                                         }
420:                                 }
421:
422:                                 @Override
423:                                 public void contextDispose() {
424:                                         control = stop();
425:                                 }
426:
427:                                 @Override
428:                                 public void childContextAdded(VElement parentElement, EMFFormsViewContext childContext) {
429:                                         // Not interesting
430:                                 }
431:
432:                                 @Override
433:                                 public void childContextDisposed(EMFFormsViewContext childContext) {
434:                                         // Not interesting
435:                                 }
436:
437:                         };
438:                 }
439:
440:         }
441:
442: }