Revision: 1763 http://svn.sourceforge.net/spring-rich-c/?rev=1763&view=rev Author: jhoskens Date: 2007-06-07 01:15:25 -0700 (Thu, 07 Jun 2007)
Log Message: ----------- Fixed event forwarding of child ValidationResultsModels. Added some testcases to back this up. Delegated error checking in DefaultFormModel to ValidationResultsModel. Modified Paths: -------------- trunk/spring-richclient/support/src/main/java/org/springframework/binding/form/support/DefaultFormModel.java trunk/spring-richclient/support/src/main/java/org/springframework/binding/validation/support/DefaultValidationResultsModel.java trunk/spring-richclient/support/src/test/java/org/springframework/binding/validation/DefaultValidationResultsModelTests.java Modified: trunk/spring-richclient/support/src/main/java/org/springframework/binding/form/support/DefaultFormModel.java =================================================================== --- trunk/spring-richclient/support/src/main/java/org/springframework/binding/form/support/DefaultFormModel.java 2007-05-31 13:07:52 UTC (rev 1762) +++ trunk/spring-richclient/support/src/main/java/org/springframework/binding/form/support/DefaultFormModel.java 2007-06-07 08:15:25 UTC (rev 1763) @@ -178,19 +178,7 @@ } public boolean getHasErrors() { - if (validationResultsModel.getHasErrors()) - return true; - - FormModel[] children = getChildren(); - for (int i = 0; i < children.length;++i) - { - if (children[i] instanceof ValidatingFormModel) - { - if (((ValidatingFormModel)children[i]).getHasErrors()) - return true; - } - } - return false; + return validationResultsModel.getHasErrors(); } protected void hasErrorsUpdated() { Modified: trunk/spring-richclient/support/src/main/java/org/springframework/binding/validation/support/DefaultValidationResultsModel.java =================================================================== --- trunk/spring-richclient/support/src/main/java/org/springframework/binding/validation/support/DefaultValidationResultsModel.java 2007-05-31 13:07:52 UTC (rev 1762) +++ trunk/spring-richclient/support/src/main/java/org/springframework/binding/validation/support/DefaultValidationResultsModel.java 2007-06-07 08:15:25 UTC (rev 1763) @@ -35,11 +35,61 @@ import org.springframework.util.ObjectUtils; /** - * Default implementation of ValidationResultsModel + * Default implementation of [EMAIL PROTECTED] ValidationResultsModel}. Several events are + * fired when validationResults are set and can be tracked by registering the + * appropriate listener. * + * <p> + * You can register listeners on: + * </p> + * <ul> + * <li>Changes of the ValidationResults in general. ([EMAIL PROTECTED] #addValidationListener(ValidationListener)})</li> + * <li>Changes of validationResults concerning a specific property of the + * FormModel. ([EMAIL PROTECTED] #addValidationListener(String, ValidationListener)})</li> + * <li>Specific events concerning errors, warnings and info. ([EMAIL PROTECTED] #addPropertyChangeListener(String, PropertyChangeListener) with one of: + * ValidationResultsModel#HAS_ERRORS_PROPERTY, + * ValidationResultsModel#HAS_INFO_PROPERTY or + * ValidationResultsModel#HAS_WARNINGS_PROPERTY)</li> + * </ul> + * + * <p> + * A child-parent relation can be used to bundle events and results. A listener + * set on a parent will receive events originating from the child and when + * polling for messages, childMessages will be available as well. This makes it + * possible to efficiently couple formModels and their validation aspect and + * provides a means to bundle validation reporting. When eg using a + * [EMAIL PROTECTED] org.springframework.richclient.form.ValidationResultsReporter}, you have the opportunity to bundle + * results from various unrelated formModels to report to one end point.</p> + * + * <p>Example:</p> + * <pre> + * DefaultFormModel formModelA = ... + * DefaultFormModel formModelChildOfA = ... + * formModelA.addChild(formModelChildOfA); + * + * DefaultFormModel formModelB = ... + * + * \\ At this stage, the ValidationResultsModel of formModelChildOfA will route results & + * \\ events to the ValidationResultsModel of formModelA + * + * DefaultValidationResultsModel container = new DefaultValidationResultsModel(); + * container.add(formModelA.getValidationResults()); + * container.add(formModelB.getValidationResults()); + * + * new SimpleValidationResultsReporter(container, messagable); + * + * \\ the reporter will now receive events & results of all formModels and can show messages of each of them + * + * </pre> + * + * @see org.springframework.binding.form.support.DefaultFormModel#addChild(org.springframework.binding.form.HierarchicalFormModel) + * @see org.springframework.richclient.form.SimpleValidationResultsReporter + * * @author Oliver Hutchison + * @author Jan Hoskens */ -public class DefaultValidationResultsModel implements ValidationResultsModel, ValidationListener { +public class DefaultValidationResultsModel implements ValidationResultsModel, ValidationListener, + PropertyChangeListener { private final EventListenerListHelper validationListeners = new EventListenerListHelper(ValidationListener.class); @@ -57,16 +107,36 @@ } }; + /** Delegate or reference to this. */ private final ValidationResultsModel delegateFor; + /** All children connected to this [EMAIL PROTECTED] DefaultValidationResultsModel}. */ private List children = new ArrayList(); + /** The actual results for this instance only. */ private ValidationResults validationResults = EmptyValidationResults.INSTANCE; + /** Error bookkeeping. */ + private boolean hasErrors = false; + + /** Warning bookkeeping. */ + private boolean hasWarnings = false; + + /** Info bookkeeping. */ + private boolean hasInfo = false; + + /** + * Constructor without delegate. (Delegating for 'this'). + */ public DefaultValidationResultsModel() { delegateFor = this; } + /** + * Constructor with delegate. + * + * @param delegateFor delegate object. + */ public DefaultValidationResultsModel(ValidationResultsModel delegateFor) { this.delegateFor = delegateFor; } @@ -78,6 +148,7 @@ if (oldValidationResults.getMessageCount() == 0 && validationResults.getMessageCount() == 0) { return; } + fireChangedEvents(); for (Iterator i = propertyValidationListeners.keySet().iterator(); i.hasNext();) { String propertyName = (String) i.next(); if (oldValidationResults.getMessageCount(propertyName) > 0 @@ -85,7 +156,6 @@ fireValidationResultsChanged(propertyName); } } - fireChangedEvents(oldValidationResults); } // TODO: test @@ -95,8 +165,8 @@ List newMessages = new ArrayList(oldValidationResults.getMessages()); newMessages.add(validationMessage); validationResults = new DefaultValidationResults(newMessages); + fireChangedEvents(); fireValidationResultsChanged(validationMessage.getProperty()); - fireChangedEvents(oldValidationResults); } } @@ -107,8 +177,8 @@ List newMessages = new ArrayList(oldValidationResults.getMessages()); newMessages.remove(validationMessage); validationResults = new DefaultValidationResults(newMessages); + fireChangedEvents(); fireValidationResultsChanged(validationMessage.getProperty()); - fireChangedEvents(oldValidationResults); } } @@ -122,12 +192,12 @@ } newMessages.add(replacementMessage); validationResults = new DefaultValidationResults(newMessages); + fireChangedEvents(); if (containsMessageToReplace && !ObjectUtils.nullSafeEquals(messageToReplace.getProperty(), replacementMessage.getProperty())) { fireValidationResultsChanged(messageToReplace.getProperty()); } fireValidationResultsChanged(replacementMessage.getProperty()); - fireChangedEvents(oldValidationResults); } public void clearAllValidationResults() { @@ -135,42 +205,95 @@ } /** - * Check children too. + * @return <code>true</code> if this instance of one of its children has + * errors contained in their results. */ public boolean getHasErrors() { - if (validationResults.getHasErrors()) - return true; - Iterator childIter = children.iterator(); - while (childIter.hasNext()) { - ValidationResultsModel childModel = (ValidationResultsModel) childIter.next(); - if (childModel.getHasErrors()) - return true; + return hasErrors; + } + + /** + * Revaluate the hasErrors property and fire an event if things have + * changed. + */ + private void updateErrors() { + boolean oldErrors = hasErrors; + hasErrors = false; + if (validationResults.getHasErrors()) { + hasErrors = true; } - return false; + else { + Iterator childIter = children.iterator(); + while (childIter.hasNext()) { + ValidationResultsModel childModel = (ValidationResultsModel) childIter.next(); + if (childModel.getHasErrors()) { + hasErrors = true; + break; + } + } + } + firePropertyChange(HAS_ERRORS_PROPERTY, oldErrors, hasErrors); } + /** + * @return <code>true</code> if this instance of one of its children has + * info contained in their results. + */ public boolean getHasInfo() { - if (validationResults.getHasInfo()) - return true; - Iterator childIter = children.iterator(); - while (childIter.hasNext()) { - ValidationResultsModel childModel = (ValidationResultsModel) childIter.next(); - if (childModel.getHasInfo()) - return true; + return hasInfo; + } + + /** + * Revaluate the hasInfo property and fire an event if things have changed. + */ + private void updateInfo() { + boolean oldInfo = hasInfo; + hasInfo = false; + if (validationResults.getHasInfo()) { + hasInfo = true; } - return false; + else { + Iterator childIter = children.iterator(); + while (childIter.hasNext()) { + ValidationResultsModel childModel = (ValidationResultsModel) childIter.next(); + if (childModel.getHasInfo()) { + hasInfo = true; + break; + } + } + } + firePropertyChange(HAS_INFO_PROPERTY, oldInfo, hasInfo); } + /** + * @return <code>true</code> if this instance of one of its children has + * warnings contained in their results. + */ public boolean getHasWarnings() { - if (validationResults.getHasWarnings()) - return true; - Iterator childIter = children.iterator(); - while (childIter.hasNext()) { - ValidationResultsModel childModel = (ValidationResultsModel) childIter.next(); - if (childModel.getHasWarnings()) - return true; + return hasWarnings; + } + + /** + * Revaluate the hasWarnings property and fire an event if things have + * changed. + */ + private void updateWarnings() { + boolean oldWarnings = hasWarnings; + hasWarnings = false; + if (validationResults.getHasWarnings()) { + hasWarnings = true; } - return false; + else { + Iterator childIter = children.iterator(); + while (childIter.hasNext()) { + ValidationResultsModel childModel = (ValidationResultsModel) childIter.next(); + if (childModel.getHasWarnings()) { + hasWarnings = true; + break; + } + } + } + firePropertyChange(HAS_WARNINGS_PROPERTY, oldWarnings, hasWarnings); } public int getMessageCount() { @@ -268,11 +391,11 @@ getPropertyChangeListeners(propertyName).remove(listener); } - protected void fireChangedEvents(ValidationResults oldValidationResults) { + protected void fireChangedEvents() { + updateErrors(); + updateWarnings(); + updateInfo(); fireValidationResultsChanged(); - firePropertyChange(HAS_ERRORS_PROPERTY, oldValidationResults.getHasErrors(), getHasErrors()); - firePropertyChange(HAS_WARNINGS_PROPERTY, oldValidationResults.getHasWarnings(), getHasWarnings()); - firePropertyChange(HAS_INFO_PROPERTY, oldValidationResults.getHasInfo(), getHasInfo()); } protected void fireValidationResultsChanged() { @@ -309,33 +432,58 @@ } /** - * Add a validationResultsModel as a child to this one. If it already has - * messages, fire events. + * Add a validationResultsModel as a child to this one. Attach listeners and + * if it already has messages, fire events. * * @param validationResultsModel */ public void add(ValidationResultsModel validationResultsModel) { - children.add(validationResultsModel); - validationResultsModel.addValidationListener(this); - if (validationResultsModel.getMessageCount() > 0) - fireValidationResultsChanged(); + if (children.add(validationResultsModel)) { + validationResultsModel.addValidationListener(this); + validationResultsModel.addPropertyChangeListener(HAS_ERRORS_PROPERTY, this); + validationResultsModel.addPropertyChangeListener(HAS_WARNINGS_PROPERTY, this); + validationResultsModel.addPropertyChangeListener(HAS_INFO_PROPERTY, this); + if ((validationResultsModel.getMessageCount() > 0)) + fireChangedEvents(); + } } /** - * Remove the given validationResultsModel from the list of children. If it - * had messages, fire events. + * Remove the given validationResultsModel from the list of children. Remove + * listeners and if it had messages, fire events. * * @param validationResultsModel */ public void remove(ValidationResultsModel validationResultsModel) { if (children.remove(validationResultsModel)) { validationResultsModel.removeValidationListener(this); + validationResultsModel.removePropertyChangeListener(HAS_ERRORS_PROPERTY, this); + validationResultsModel.removePropertyChangeListener(HAS_WARNINGS_PROPERTY, this); + validationResultsModel.removePropertyChangeListener(HAS_INFO_PROPERTY, this); if (validationResultsModel.getMessageCount() > 0) - fireValidationResultsChanged(); + fireChangedEvents(); } } + /** + * [EMAIL PROTECTED] DefaultValidationResultsModel} registers itself as a + * validationListener on it's children to forward the event. + */ public void validationResultsChanged(ValidationResults results) { fireValidationResultsChanged(); } + + /** + * Forwarding of known property events coming from child models. Each event + * triggers a specific evaluation of the parent property, which will trigger + * events as needed. + */ + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName() == HAS_ERRORS_PROPERTY) + updateErrors(); + else if (evt.getPropertyName() == HAS_WARNINGS_PROPERTY) + updateWarnings(); + else if (evt.getPropertyName() == HAS_INFO_PROPERTY) + updateInfo(); + } } \ No newline at end of file Modified: trunk/spring-richclient/support/src/test/java/org/springframework/binding/validation/DefaultValidationResultsModelTests.java =================================================================== --- trunk/spring-richclient/support/src/test/java/org/springframework/binding/validation/DefaultValidationResultsModelTests.java 2007-05-31 13:07:52 UTC (rev 1762) +++ trunk/spring-richclient/support/src/test/java/org/springframework/binding/validation/DefaultValidationResultsModelTests.java 2007-06-07 08:15:25 UTC (rev 1763) @@ -143,6 +143,9 @@ assertEquals(3, nullListener.eventCount()); } + /** + * Simply check if [EMAIL PROTECTED] DefaultValidationResultsModel} counts its messages correctly. + */ public void testMessageCount() { DefaultValidationResultsModel resultsModel = new DefaultValidationResultsModel(); resultsModel.addMessage(new DefaultValidationMessage("property1", Severity.ERROR, "message1")); @@ -152,29 +155,63 @@ assertEquals("Number of messages registered for property1 should be 2", 2, resultsModel.getMessageCount("property1")); assertEquals("Number of messages flagged as INFO should be 1", 1, resultsModel.getMessageCount(Severity.INFO)); } - - public void testAddAndRemoveChild() { - DefaultValidationResultsModel parentModel = new DefaultValidationResultsModel(); + + /** + * Check if adding a child triggers the parent to fire appropriate events. + */ + public void testAddChildEvents() { DefaultValidationResultsModel childModel = new DefaultValidationResultsModel(); - int events = 0; - TestValidationListener validationListener = new TestValidationListener(); - parentModel.addValidationListener(validationListener); - assertEquals("Init: no events yet", events, validationListener.eventCount()); - - parentModel.addMessage(new DefaultValidationMessage("parentProperty1", Severity.ERROR, "parentMessage1")); - assertEquals("ParentModel added ErrorMessage.", ++events, validationListener.eventCount()); + childModel.addMessage(new DefaultValidationMessage("childProperty1", Severity.ERROR, "childErrorMessage1")); + childModel.addMessage(new DefaultValidationMessage("childProperty1", Severity.WARNING, "childWarningMessage1")); + childModel.addMessage(new DefaultValidationMessage("childProperty1", Severity.INFO, "childInfoMessage1")); + vrm.add(childModel); + assertEquals("ParentModel adds child with Error.", 1, listener.eventCount()); + assertEquals("ChildModel has ErrorMessage.", 1, errorsListener.eventCount()); + assertEquals("ChildModel has WarningMessage.", 1, warnListener.eventCount()); + assertEquals("ChildModel has InfoMessage.", 1, infoListener.eventCount()); + assertEquals("ChildModel has ErrorMessage.", Boolean.TRUE, errorsListener.lastEvent().getNewValue()); + assertEquals("ChildModel has WarningMessage.", Boolean.TRUE, warnListener.lastEvent().getNewValue()); + assertEquals("ChildModel has InfoMessage.", Boolean.TRUE, infoListener.lastEvent().getNewValue()); + } - childModel.addMessage(new DefaultValidationMessage("childProperty1", Severity.ERROR, "childMessage1")); - parentModel.add(childModel); - assertEquals("ParentModel adds child with Error.", ++events, validationListener.eventCount()); + /** + * Check if adding a child triggers the parent to fire appropriate events. + */ + public void testChildEvents() { + DefaultValidationResultsModel childModel = new DefaultValidationResultsModel(); - childModel.addMessage(new DefaultValidationMessage("childProperty2", Severity.ERROR, "childMessage2")); - assertEquals("Child added errorMessage.", ++events, validationListener.eventCount()); + vrm.add(childModel); + childModel.addMessage(new DefaultValidationMessage("childProperty1", Severity.ERROR, "childErrorMessage1")); + childModel.addMessage(new DefaultValidationMessage("childProperty1", Severity.WARNING, "childWarningMessage1")); + childModel.addMessage(new DefaultValidationMessage("childProperty1", Severity.INFO, "childInfoMessage1")); + assertEquals("Child added errorMessage, warningMessage and InfoMessage.", 3, listener.eventCount()); + assertEquals("ChildModel added ErrorMessage.", 1, errorsListener.eventCount()); + assertEquals("ChildModel added WarningMessage.", 1, warnListener.eventCount()); + assertEquals("ChildModel added InfoMessage.", 1, infoListener.eventCount()); + assertEquals("ChildModel added ErrorMessage.", Boolean.TRUE, errorsListener.lastEvent().getNewValue()); + assertEquals("ChildModel added WarningMessage.", Boolean.TRUE, warnListener.lastEvent().getNewValue()); + assertEquals("ChildModel added InfoMessage.", Boolean.TRUE, infoListener.lastEvent().getNewValue()); + } + + /** + * Check if adding a child triggers the parent to fire appropriate events. + */ + public void testRemoveChildEvents() { + DefaultValidationResultsModel childModel = new DefaultValidationResultsModel(); - parentModel.remove(childModel); - assertEquals("Child removed, revalidate.", ++events, validationListener.eventCount()); - + childModel.addMessage(new DefaultValidationMessage("childProperty1", Severity.ERROR, "childErrorMessage1")); + childModel.addMessage(new DefaultValidationMessage("childProperty1", Severity.WARNING, "childWarningMessage1")); + childModel.addMessage(new DefaultValidationMessage("childProperty1", Severity.INFO, "childInfoMessage1")); + vrm.add(childModel); + vrm.remove(childModel); + assertEquals("Child removed, revalidate.", 2, listener.eventCount()); + assertEquals("Child removed, revalidate ErrorMessages.", 2, errorsListener.eventCount()); + assertEquals("Child removed, revalidate WarningMessages.", 2, warnListener.eventCount()); + assertEquals("Child removed, revalidate InfoMessages.", 2, infoListener.eventCount()); + assertEquals("Child removed, revalidate ErrorMessages.", Boolean.FALSE, errorsListener.lastEvent().getNewValue()); + assertEquals("Child removed, revalidate WarningMessages.", Boolean.FALSE, warnListener.lastEvent().getNewValue()); + assertEquals("Child removed, revalidate InfoMessages.", Boolean.FALSE, infoListener.lastEvent().getNewValue()); } private ValidationResults getResults(String field, Severity severity) { This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. ------------------------------------------------------------------------- This SF.net email is sponsored by DB2 Express Download DB2 Express C - the FREE version of DB2 express and take control of your XML. No limits. Just data. Click to get it now. http://sourceforge.net/powerbar/db2/ _______________________________________________ spring-rich-c-cvs mailing list spring-rich-c-cvs@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/spring-rich-c-cvs