View Javadoc
1   /**
2    * Copyright 2005-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.util;
17  
18  import org.apache.commons.lang.StringEscapeUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.apache.commons.lang.builder.EqualsBuilder;
21  import org.apache.commons.lang.builder.HashCodeBuilder;
22  import org.apache.commons.lang.builder.ToStringBuilder;
23  import org.springframework.util.AutoPopulatingList;
24  
25  import java.io.Serializable;
26  import java.util.ArrayList;
27  import java.util.Collections;
28  import java.util.Iterator;
29  import java.util.LinkedHashMap;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Set;
33  
34  /**
35   * Holds errors due to validation
36   *
37   * <p>Keys of map represent property paths, and value is a AutoPopulatingList that contains resource string
38   * keys (to retrieve the error message).</p>
39   *
40   * <p>Note, prior to rice 0.9.4, this class implemented {@link java.util.Map}.  The implements has been removed as of
41   * rice 0.9.4</p>
42   *
43   * @author Kuali Rice Team (rice.collab@kuali.org)
44   */
45  public class MessageMap implements Serializable {
46      private static final long serialVersionUID = -2328635367656516150L;
47  
48      private final List<String> errorPath;
49  
50      private final Map<String, List<ErrorMessage>> errorMessages;
51      private final Map<String, List<ErrorMessage>> warningMessages;
52      private final Map<String, List<ErrorMessage>> infoMessages;
53      private final List<GrowlMessage> growlMessages;
54  
55      public MessageMap() {
56          errorPath = Collections.synchronizedList(new ArrayList<String>());
57          errorMessages = Collections.synchronizedMap(new LinkedHashMap<String, List<ErrorMessage>>());
58          warningMessages = Collections.synchronizedMap(new LinkedHashMap<String, List<ErrorMessage>>());
59          infoMessages = Collections.synchronizedMap(new LinkedHashMap<String, List<ErrorMessage>>());
60          growlMessages = Collections.synchronizedList(new AutoPopulatingList<GrowlMessage>(GrowlMessage.class));
61      }
62  
63      public MessageMap(MessageMap messageMap) {
64          this.errorPath = messageMap.errorPath;
65          this.errorMessages = messageMap.errorMessages;
66          this.warningMessages = messageMap.warningMessages;
67          this.infoMessages = messageMap.infoMessages;
68  
69          growlMessages = Collections.synchronizedList(new AutoPopulatingList<GrowlMessage>(GrowlMessage.class));
70      }
71  
72      public void merge(MessageMap messageMap) {
73          if (messageMap != null) {
74              if (messageMap.hasErrors()) {
75                  merge(messageMap.getErrorMessages(), errorMessages);
76              }
77              if (messageMap.hasInfo()) {
78                  merge(messageMap.getInfoMessages(), infoMessages);
79              }
80              if (messageMap.hasWarnings()) {
81                  merge(messageMap.getWarningMessages(), warningMessages);
82              }
83              if (messageMap.getGrowlMessages() != null) {
84                  growlMessages.addAll(messageMap.getGrowlMessages());
85              }
86          }
87  
88      }
89  
90      /**
91       * Takes one message map and merges it into another.  Makes sure there are no duplicates.
92       *
93       * @param messagesFrom
94       * @param messagesTo
95       * TODO: This method is not thread-safe and should be private.
96       */
97      public void merge(Map<String, List<ErrorMessage>> messagesFrom,
98              Map<String, List<ErrorMessage>> messagesTo) {
99          for (String key : messagesFrom.keySet()) {
100 
101             if (messagesTo.containsKey(key)) {
102                 // now we need to merge the messages
103                 List<ErrorMessage> tal = messagesFrom.get(key);
104                 List<ErrorMessage> parentList = messagesTo.get(key);
105 
106                 for (Object o : tal) {
107 
108                     if (!parentList.contains(o)) {
109                         parentList.add((ErrorMessage) o);
110                     }
111                 }
112 
113             } else {
114                 messagesTo.put(key, messagesFrom.get(key));
115             }
116 
117         }
118     }
119 
120     public List<ErrorMessage> putError(String propertyName, String errorKey, String... errorParameters) {
121         ErrorMessage message = new ErrorMessage(errorKey, errorParameters);
122         return putMessageInMap(errorMessages, propertyName, message, true, true);
123     }
124 
125     public List<ErrorMessage> putWarning(String propertyName, String messageKey,
126             String... messageParameters) {
127         ErrorMessage message = new ErrorMessage(messageKey, messageParameters);
128         return putMessageInMap(warningMessages, propertyName, message, true, true);
129     }
130 
131     public List<ErrorMessage> putInfo(String propertyName, String messageKey,
132             String... messageParameters) {
133         ErrorMessage message = new ErrorMessage(messageKey, messageParameters);
134         return putMessageInMap(infoMessages, propertyName, message, true, true);
135     }
136 
137     public List<ErrorMessage> putError(String propertyName, ErrorMessage message) {
138         return putMessageInMap(errorMessages, propertyName, message, true, true);
139     }
140 
141     public List<ErrorMessage> putWarning(String propertyName, ErrorMessage message) {
142         return putMessageInMap(warningMessages, propertyName, message, true, true);
143     }
144 
145     public List<ErrorMessage> putInfo(String propertyName, ErrorMessage message) {
146         return putMessageInMap(infoMessages, propertyName, message, true, true);
147     }
148 
149     public List<ErrorMessage> putErrorWithoutFullErrorPath(String propertyName, String errorKey,
150             String... errorParameters) {
151         ErrorMessage message = new ErrorMessage(errorKey, errorParameters);
152         return putMessageInMap(errorMessages, propertyName, message, false, true);
153     }
154 
155     public List<ErrorMessage> putWarningWithoutFullErrorPath(String propertyName, String messageKey,
156             String... messageParameters) {
157         ErrorMessage message = new ErrorMessage(messageKey, messageParameters);
158         return putMessageInMap(warningMessages, propertyName, message, false, true);
159     }
160 
161     public List<ErrorMessage> putInfoWithoutFullErrorPath(String propertyName, String messageKey,
162             String... messageParameters) {
163         ErrorMessage message = new ErrorMessage(messageKey, messageParameters);
164         return putMessageInMap(infoMessages, propertyName, message, false, true);
165     }
166 
167     public List<ErrorMessage> putErrorWithoutFullErrorPath(String propertyName, ErrorMessage message) {
168         return putMessageInMap(errorMessages, propertyName, message, false, true);
169     }
170 
171     public List<ErrorMessage> putWarningWithoutFullErrorPath(String propertyName, ErrorMessage message) {
172         return putMessageInMap(warningMessages, propertyName, message, false, true);
173     }
174 
175     public List<ErrorMessage> putInfoWithoutFullErrorPath(String propertyName, ErrorMessage message) {
176         return putMessageInMap(infoMessages, propertyName, message, false, true);
177     }
178 
179     public List<ErrorMessage> putErrorForSectionId(String sectionId, String errorKey,
180             String... errorParameters) {
181         return putErrorWithoutFullErrorPath(sectionId, errorKey, errorParameters);
182     }
183 
184     public List<ErrorMessage> putWarningForSectionId(String sectionId, String messageKey,
185             String... messageParameters) {
186         return putWarningWithoutFullErrorPath(sectionId, messageKey, messageParameters);
187     }
188 
189     public List<ErrorMessage> putInfoForSectionId(String sectionId, String messageKey,
190             String... messageParameters) {
191         return putInfoWithoutFullErrorPath(sectionId, messageKey, messageParameters);
192     }
193 
194     public List<ErrorMessage> putErrorForSectionId(String sectionId, ErrorMessage message) {
195         return putErrorWithoutFullErrorPath(sectionId, message);
196     }
197 
198     public List<ErrorMessage> putWarningForSectionId(String sectionId, ErrorMessage message) {
199         return putWarningWithoutFullErrorPath(sectionId, message);
200     }
201 
202     public List<ErrorMessage> putInfoForSectionId(String sectionId, ErrorMessage message) {
203         return putInfoWithoutFullErrorPath(sectionId, message);
204     }
205 
206     /**
207      * Creates error message given the error key and parameters and adds it to the message map.
208      *
209      * @param propertyName name of the property associated with the error
210      * @param errorKey   message key for the error
211      * @param escapeHtmlMessageParameters  whether to escape HTML characters in the message parameters
212      * @param errorParameters   zero or more parameters for the message text
213      * @return TypeArrayList
214      */
215     public List<ErrorMessage> putError(String propertyName, String errorKey, Boolean escapeHtmlMessageParameters, String... errorParameters) {
216         ErrorMessage message = new ErrorMessage(errorKey, errorParameters);
217         return putMessageInMap(errorMessages, propertyName, message, true, escapeHtmlMessageParameters);
218     }
219 
220     /**
221      * Creates error message given the message key and parameters and adds it to the warning message map.
222      *
223      * @param propertyName name of the property associated with the warning
224      * @param messageKey   message key for the error
225      * @param escapeHtmlMessageParameters  whether to escape HTML characters in the message parameters
226      * @param messageParameters   zero or more parameters for the message text
227      * @return TypeArrayList
228      */
229     public List<ErrorMessage> putWarning(String propertyName, String messageKey, Boolean escapeHtmlMessageParameters,
230             String... messageParameters) {
231         ErrorMessage message = new ErrorMessage(messageKey, messageParameters);
232         return putMessageInMap(warningMessages, propertyName, message, true, escapeHtmlMessageParameters);
233     }
234 
235     /**
236      * Creates error message given the message key and parameters and adds it to the info message map.
237      *
238      * @param propertyName name of the property associated with the info
239      * @param messageKey   message key for the info
240      * @param escapeHtmlMessageParameters  whether to escape HTML characters in the message parameters
241      * @param messageParameters   zero or more parameters for the message text
242      * @return TypeArrayList
243      */
244     public List<ErrorMessage> putInfo(String propertyName, String messageKey, Boolean escapeHtmlMessageParameters,
245             String... messageParameters) {
246         ErrorMessage message = new ErrorMessage(messageKey, messageParameters);
247         return putMessageInMap(infoMessages, propertyName, message, true, escapeHtmlMessageParameters);
248     }
249 
250     /**
251      * Adds a growl (using the default theme) to the message map with the given title and message
252      *
253      * @param growlTitle - title for the growl
254      * @param messageKey - key for the message in resources
255      * @param messageParameters - parameters for the message
256      */
257     public void addGrowlMessage(String growlTitle, String messageKey, String... messageParameters) {
258         GrowlMessage growl = new GrowlMessage();
259 
260         growl.setTitle(growlTitle);
261         growl.setMessageKey(messageKey);
262         growl.setMessageParameters(messageParameters);
263 
264         growlMessages.add(growl);
265     }
266 
267     /**
268      * Add a growl to the message map
269      *
270      * @param growl - growl instance to add
271      */
272     public void addGrowlMessage(GrowlMessage growl) {
273         growlMessages.add(growl);
274     }
275 
276     /**
277      * Adds an error message to the given message map, adjusting the error path and message parameters if necessary
278      *
279      * @param messagesMap
280      * @param propertyName name of the property to add error under
281      * @param errorMessage
282      * @param prependFullErrorPath true if you want the whole parent error path prepended, false otherwise
283      * @param escapeHtmlMessageParameters whether to escape HTML characters in the message parameters, provides
284      * protection against XSS attacks
285      * @return TypeArrayList
286      */
287     protected List<ErrorMessage> putMessageInMap(Map<String, List<ErrorMessage>> messagesMap,
288             String propertyName, ErrorMessage errorMessage, boolean prependFullErrorPath,
289             boolean escapeHtmlMessageParameters) {
290         if (StringUtils.isBlank(propertyName)) {
291             throw new IllegalArgumentException("invalid (blank) propertyName");
292         }
293         if (StringUtils.isBlank(errorMessage.getErrorKey())) {
294             throw new IllegalArgumentException("invalid (blank) errorKey");
295         }
296 
297         // check if we have previous errors for this property
298         List<ErrorMessage> errorList = null;
299         String propertyKey = getKeyPath(propertyName, prependFullErrorPath);
300         if (messagesMap.containsKey(propertyKey)) {
301             errorList = messagesMap.get(propertyKey);
302         } else {
303             errorList = Collections.synchronizedList(new AutoPopulatingList<ErrorMessage>(ErrorMessage.class));
304         }
305 
306         if (escapeHtmlMessageParameters) {
307             if (errorMessage.getMessageParameters() != null) {
308                 String[] filteredMessageParameters = new String[errorMessage.getMessageParameters().length];
309                 for (int i = 0; i < errorMessage.getMessageParameters().length; i++) {
310                     filteredMessageParameters[i] = StringEscapeUtils.escapeHtml(errorMessage.getMessageParameters()[i]);
311                 }
312                 errorMessage.setMessageParameters(filteredMessageParameters);
313             }
314 
315             if (errorMessage.getMessagePrefixParameters() != null) {
316                 String[] filteredMessageParameters = new String[errorMessage.getMessagePrefixParameters().length];
317                 for (int i = 0; i < errorMessage.getMessagePrefixParameters().length; i++) {
318                     filteredMessageParameters[i] = StringEscapeUtils.escapeHtml(
319                             errorMessage.getMessagePrefixParameters()[i]);
320                 }
321                 errorMessage.setMessagePrefixParameters(filteredMessageParameters);
322             }
323 
324             if (errorMessage.getMessageSuffixParameters() != null) {
325                 String[] filteredMessageParameters = new String[errorMessage.getMessageSuffixParameters().length];
326                 for (int i = 0; i < errorMessage.getMessageSuffixParameters().length; i++) {
327                     filteredMessageParameters[i] = StringEscapeUtils.escapeHtml(
328                             errorMessage.getMessageSuffixParameters()[i]);
329                 }
330                 errorMessage.setMessageSuffixParameters(filteredMessageParameters);
331             }
332         }
333 
334         // check if this error has already been added to the list
335         boolean alreadyAdded = false;
336         for (ErrorMessage e : errorList) {
337             if (e.equals(errorMessage)) {
338                 alreadyAdded = true;
339                 break;
340             }
341         }
342         if (!alreadyAdded) {
343             errorList.add(errorMessage);
344         }
345 
346         return messagesMap.put(propertyKey, errorList);
347     }
348 
349     /**
350      * If any error messages with the key targetKey exist in this ErrorMap for the named property, those ErrorMessages
351      * will be replaced with a new ErrorMessage with the given replaceKey and replaceParameters.
352      *
353      * @param propertyName name of the property where existing error will be replaced
354      * @param targetKey error key of message to be replaced
355      * @param replaceParameters zero or more string parameters for the replacement error message
356      * @return true if the replacement occurred
357      * @paran replaceKey error key which will replace targetKey
358      */
359     public boolean replaceError(String propertyName, String targetKey, String replaceKey, String... replaceParameters) {
360         return replaceError(propertyName, targetKey, true, replaceKey, replaceParameters);
361     }
362 
363     /**
364      * If any error messages with the key targetKey exist in this ErrorMap for the named property, those ErrorMessages
365      * will be replaced with a new ErrorMessage with the given replaceKey and replaceParameters. The targetKey
366      * and replaceKey will be prepended with the current errorPath, if any.
367      *
368      * @param propertyName name of the property where existing error will be replaced
369      * @param targetKey error key of message to be replaced
370      * @param replaceParameters zero or more string parameters for the replacement error message
371      * @return true if the replacement occurred
372      * @paran replaceKey error key which will replace targetKey
373      */
374     public boolean replaceErrorWithoutFullErrorPath(String propertyName, String targetKey, String replaceKey,
375             String... replaceParameters) {
376         return replaceError(propertyName, targetKey, false, replaceKey, replaceParameters);
377     }
378 
379     /**
380      * If any error messages with the key targetKey exist in this ErrorMap for the named property, those ErrorMessages
381      * will be replaced with a new ErrorMessage with the given replaceKey and replaceParameters.
382      *
383      * @param propertyName name of the property to add error under
384      * @param targetKey resource key used to retrieve the error text
385      * @param withFullErrorPath true if you want the whole parent error path appended, false otherwise
386      * @param replaceParameters zero or more string parameters for the displayed error message
387      * @return true if the replacement occurred
388      */
389     private boolean replaceError(String propertyName, String targetKey, boolean withFullErrorPath, String replaceKey,
390             String... replaceParameters) {
391         boolean replaced = false;
392 
393         if (StringUtils.isBlank(propertyName)) {
394             throw new IllegalArgumentException("invalid (blank) propertyName");
395         }
396         if (StringUtils.isBlank(targetKey)) {
397             throw new IllegalArgumentException("invalid (blank) targetKey");
398         }
399         if (StringUtils.isBlank(replaceKey)) {
400             throw new IllegalArgumentException("invalid (blank) replaceKey");
401         }
402 
403         // check if we have previous errors for this property
404         List<ErrorMessage> errorList = null;
405         String propertyKey = getKeyPath(propertyName, withFullErrorPath);
406         if (errorMessages.containsKey(propertyKey)) {
407             errorList = errorMessages.get(propertyKey);
408 
409             // look for the specific targetKey
410             for (int i = 0; i < errorList.size(); ++i) {
411                 ErrorMessage em = errorList.get(i);
412 
413                 // replace matching messages
414                 if (em.getErrorKey().equals(targetKey)) {
415                     ErrorMessage rm = new ErrorMessage(replaceKey, replaceParameters);
416                     errorList.set(i, rm);
417                     replaced = true;
418                 }
419             }
420         }
421 
422         return replaced;
423     }
424 
425     /**
426      * Returns true if the named field has a message with the given errorKey
427      *
428      * @param errorKey
429      * @param fieldName
430      * @return boolean
431      */
432     public boolean fieldHasMessage(String fieldName, String errorKey) {
433         boolean found = false;
434 
435         List<ErrorMessage> fieldMessages = errorMessages.get(fieldName);
436         if (fieldMessages != null) {
437             for (Iterator<ErrorMessage> i = fieldMessages.iterator(); !found && i.hasNext(); ) {
438                 ErrorMessage errorMessage = i.next();
439                 found = errorMessage.getErrorKey().equals(errorKey);
440             }
441         }
442 
443         return found;
444     }
445 
446     /**
447      * Returns the number of messages for the given field
448      *
449      * @param fieldName
450      * @return int
451      */
452     public int countFieldMessages(String fieldName) {
453         int count = 0;
454 
455         List<ErrorMessage> fieldMessages = errorMessages.get(fieldName);
456         if (fieldMessages != null) {
457             count = fieldMessages.size();
458         }
459 
460         return count;
461     }
462 
463     /**
464      * @return true if the given messageKey is associated with some property in this ErrorMap
465      */
466     public boolean containsMessageKey(String messageKey) {
467         ErrorMessage foundMessage = null;
468 
469         if (!hasNoErrors()) {
470             for (Iterator<Map.Entry<String, List<ErrorMessage>>> i =
471                          getAllPropertiesAndErrors().iterator(); (foundMessage == null) && i.hasNext(); ) {
472                 Map.Entry<String, List<ErrorMessage>> e = i.next();
473                 List<ErrorMessage> entryErrorList = e.getValue();
474                 for (Iterator<ErrorMessage> j = entryErrorList.iterator(); j.hasNext(); ) {
475                     ErrorMessage em = j.next();
476                     if (messageKey.equals(em.getErrorKey())) {
477                         foundMessage = em;
478                     }
479                 }
480             }
481         }
482 
483         return (foundMessage != null);
484     }
485 
486     private int getMessageCount(Map<String, List<ErrorMessage>> messageMap) {
487         int messageCount = 0;
488         for (Iterator<String> iter = messageMap.keySet().iterator(); iter.hasNext(); ) {
489             String errorKey = iter.next();
490             List<ErrorMessage> errors = messageMap.get(errorKey);
491             messageCount += errors.size();
492         }
493 
494         return messageCount;
495     }
496 
497     /**
498      * Counts the total number of error messages in the map
499      *
500      * @return returns an int for the total number of errors
501      */
502     public int getErrorCount() {
503         return getMessageCount(errorMessages);
504     }
505 
506     /**
507      * Counts the total number of warning messages in the map
508      *
509      * @return returns an int for the total number of warnings
510      */
511     public int getWarningCount() {
512         return getMessageCount(warningMessages);
513     }
514 
515     /**
516      * Counts the total number of info messages in the map
517      *
518      * @return returns an int for the total number of info
519      */
520     public int getInfoCount() {
521         return getMessageCount(infoMessages);
522     }
523 
524     /**
525      * @param path
526      * @return Returns a List of ErrorMessages for the given path
527      */
528     public List<ErrorMessage> getMessages(String path) {
529         return errorMessages.get(path);
530     }
531 
532     /**
533      * Adds a string prefix to the error path.
534      *
535      * @param parentName
536      */
537     public void addToErrorPath(String parentName) {
538         errorPath.add(parentName);
539     }
540 
541     /**
542      * This method returns the list that holds the error path values.
543      *
544      * @return List
545      */
546     public List<String> getErrorPath() {
547         return errorPath;
548     }
549 
550     /**
551      * Removes a string prefix from the error path.
552      *
553      * @param parentName
554      * @return boolean Returns true if the parentName existed, false otherwise.
555      */
556     public boolean removeFromErrorPath(String parentName) {
557         return errorPath.remove(parentName);
558     }
559 
560     /**
561      * Clears the errorPath.
562      */
563     public void clearErrorPath() {
564         errorPath.clear();
565     }
566 
567     /**
568      * This is what's prepended to the beginning of the key. This is built by iterating over all of the entries in the
569      * errorPath
570      * list and concatenating them together with a "."
571      *
572      * @param propertyName
573      * @param prependFullErrorPath
574      * @return String Returns the keyPath.
575      */
576     public String getKeyPath(String propertyName, boolean prependFullErrorPath) {
577         String keyPath = "";
578 
579         if (KRADConstants.GLOBAL_ERRORS.equals(propertyName)) {
580             return KRADConstants.GLOBAL_ERRORS;
581         }
582 
583         if (!errorPath.isEmpty() && prependFullErrorPath) {
584             keyPath = StringUtils.join(errorPath.iterator(), ".");
585             keyPath += (keyPath != null && keyPath.endsWith(".")) ? propertyName : "." + propertyName;
586         } else {
587             keyPath = propertyName;
588         }
589 
590         return keyPath;
591     }
592 
593     /**
594      * @return List of the property names that have errors.
595      */
596     public List<String> getPropertiesWithErrors() {
597         List<String> properties = new ArrayList<String>();
598 
599         for (Iterator<String> iter = errorMessages.keySet().iterator(); iter.hasNext(); ) {
600             properties.add(iter.next());
601         }
602 
603         return properties;
604     }
605 
606     /**
607      * @return List of the property names that have warnings.
608      */
609     public List<String> getPropertiesWithWarnings() {
610         List<String> properties = new ArrayList<String>(warningMessages.keySet());
611         return properties;
612     }
613 
614     /**
615      * @return List of the property names that have info.
616      */
617     public List<String> getPropertiesWithInfo() {
618         List<String> properties = new ArrayList<String>(infoMessages.keySet());
619         return properties;
620     }
621 
622     public void clearErrorMessages() {
623         errorMessages.clear();
624     }
625 
626     public boolean doesPropertyHaveError(String key) {
627         return errorMessages.containsKey(key);
628     }
629 
630     /**
631      * @param pattern comma separated list of keys, optionally ending with * wildcard
632      */
633     public boolean containsKeyMatchingPattern(String pattern) {
634         List<String> simplePatterns = new ArrayList<String>();
635         List<String> wildcardPatterns = new ArrayList<String>();
636         String[] patterns = pattern.split(",");
637         for (int i = 0; i < patterns.length; i++) {
638             String s = patterns[i];
639             if (s.endsWith("*")) {
640                 wildcardPatterns.add(s.substring(0, s.length() - 1));
641             } else {
642                 simplePatterns.add(s);
643             }
644         }
645         for (Iterator<String> keys = errorMessages.keySet().iterator(); keys.hasNext(); ) {
646             String key = keys.next();
647             if (simplePatterns.contains(key)) {
648                 return true;
649             }
650             for (Iterator<String> wildcardIterator = wildcardPatterns.iterator(); wildcardIterator.hasNext(); ) {
651                 String wildcard = wildcardIterator.next();
652                 if (key.startsWith(wildcard)) {
653                     return true;
654                 }
655             }
656         }
657         return false;
658     }
659 
660     public Set<Map.Entry<String, List<ErrorMessage>>> getAllPropertiesAndErrors() {
661         return errorMessages.entrySet();
662     }
663 
664     public List<ErrorMessage> getErrorMessagesForProperty(String propertyName) {
665         return errorMessages.get(propertyName);
666     }
667 
668     public List<ErrorMessage> getWarningMessagesForProperty(String propertyName) {
669         return warningMessages.get(propertyName);
670     }
671 
672     public List<ErrorMessage> getInfoMessagesForProperty(String propertyName) {
673         return infoMessages.get(propertyName);
674     }
675 
676     /**
677      * Gets a list of lists that represent errors that matched by the
678      * propertyName passed in (multiple lists because the wildcard can match
679      * multiple keys). If wildcard is true, the propertyName ends with a
680      * wildcard character. Otherwise, it will only match on the single key and
681      * return a list with one list
682      *
683      * @param propertyName
684      * @param allowWildcard
685      * @return
686      */
687     public List<List<ErrorMessage>> getErrorMessagesForProperty(String propertyName,
688             boolean allowWildcard) {
689         List<List<ErrorMessage>> foundMessages = new ArrayList<List<ErrorMessage>>();
690         if (allowWildcard) {
691             boolean wildcard = false;
692             if (propertyName.endsWith("*")) {
693                 wildcard = true;
694                 propertyName = propertyName.substring(0, propertyName.length() - 1);
695             }
696             for (Iterator<String> keys = errorMessages.keySet().iterator(); keys.hasNext(); ) {
697                 String key = keys.next();
698                 if (!wildcard && propertyName.equals(key)) {
699                     foundMessages.add(errorMessages.get(key));
700                     break;
701                 } else if (wildcard && key.startsWith(propertyName)) {
702                     foundMessages.add(errorMessages.get(key));
703                 }
704             }
705         } else {
706             foundMessages.add(getErrorMessagesForProperty(propertyName));
707         }
708 
709         return foundMessages;
710     }
711 
712     /**
713      * Gets a list of lists that represent warnings that matched by the
714      * propertyName passed in (multiple lists because the wildcard can match
715      * multiple keys). If wildcard is true, the propertyName ends with a
716      * wildcard character. Otherwise, it will only match on the single key and
717      * return a list with one list.
718      *
719      * @param propertyName
720      * @param allowWildcard
721      * @return
722      */
723     public List<List<ErrorMessage>> getWarningMessagesForProperty(String propertyName,
724             boolean allowWildcard) {
725         List<List<ErrorMessage>> foundMessages = new ArrayList<List<ErrorMessage>>();
726         if (allowWildcard) {
727             boolean wildcard = false;
728             if (propertyName.endsWith("*")) {
729                 wildcard = true;
730                 propertyName = propertyName.substring(0, propertyName.length() - 1);
731             }
732             for (Iterator<String> keys = warningMessages.keySet().iterator(); keys.hasNext(); ) {
733                 String key = keys.next();
734                 if (!wildcard && propertyName.equals(key)) {
735                     foundMessages.add(warningMessages.get(key));
736                     break;
737                 } else if (wildcard && key.startsWith(propertyName)) {
738                     foundMessages.add(warningMessages.get(key));
739                 }
740             }
741         } else {
742             foundMessages.add(getWarningMessagesForProperty(propertyName));
743         }
744 
745         return foundMessages;
746     }
747 
748     /**
749      * Gets a list of lists that represent info messages that matched by the
750      * propertyName passed in (multiple lists because the wildcard can match
751      * multiple keys). If wildcard is true, the propertyName ends with a
752      * wildcard character. If it is false, it will only match on the single key
753      * and return a list with one list.
754      *
755      * @param propertyName
756      * @param allowWildcard
757      * @return
758      */
759     public List<List<ErrorMessage>> getInfoMessagesForProperty(String propertyName,
760             boolean allowWildcard) {
761         List<List<ErrorMessage>> foundMessages = new ArrayList<List<ErrorMessage>>();
762         if (allowWildcard) {
763             boolean wildcard = false;
764             if (propertyName.endsWith("*")) {
765                 wildcard = true;
766                 propertyName = propertyName.substring(0, propertyName.length() - 1);
767             }
768             for (Iterator<String> keys = infoMessages.keySet().iterator(); keys.hasNext(); ) {
769                 String key = keys.next();
770                 if (!wildcard && propertyName.equals(key)) {
771                     foundMessages.add(infoMessages.get(key));
772                     break;
773                 } else if (wildcard && key.startsWith(propertyName)) {
774                     foundMessages.add(infoMessages.get(key));
775                 }
776             }
777         } else {
778             foundMessages.add(getInfoMessagesForProperty(propertyName));
779         }
780 
781         return foundMessages;
782     }
783 
784     public boolean hasErrors() {
785         return !errorMessages.isEmpty();
786     }
787 
788     public boolean hasNoErrors() {
789         return errorMessages.isEmpty();
790     }
791 
792     public boolean hasWarnings() {
793         return !warningMessages.isEmpty();
794     }
795 
796     public boolean hasNoWarnings() {
797         return warningMessages.isEmpty();
798     }
799 
800     public boolean hasInfo() {
801         return !infoMessages.isEmpty();
802     }
803 
804     public boolean hasNoInfo() {
805         return infoMessages.isEmpty();
806     }
807 
808     public boolean hasMessages() {
809         if (!errorMessages.isEmpty() || !warningMessages.isEmpty() || !infoMessages.isEmpty()) {
810             return true;
811         }
812         return false;
813     }
814 
815     public boolean hasNoMessages() {
816         if (errorMessages.isEmpty() && warningMessages.isEmpty() && infoMessages.isEmpty()) {
817             return true;
818         }
819         return false;
820     }
821 
822     public Set<String> getAllPropertiesWithErrors() {
823         return errorMessages.keySet();
824     }
825 
826     public Set<String> getAllPropertiesWithWarnings() {
827         return warningMessages.keySet();
828     }
829 
830     public Set<String> getAllPropertiesWithInfo() {
831         return infoMessages.keySet();
832     }
833 
834     public List<ErrorMessage> removeAllErrorMessagesForProperty(String property) {
835         return errorMessages.remove(property);
836     }
837 
838     public List<ErrorMessage> removeAllWarningMessagesForProperty(String property) {
839         return warningMessages.remove(property);
840     }
841 
842     public List<ErrorMessage> removeAllInfoMessagesForProperty(String property) {
843         return infoMessages.remove(property);
844     }
845 
846     public int getNumberOfPropertiesWithErrors() {
847         return errorMessages.size();
848     }
849 
850     public Map<String, List<ErrorMessage>> getErrorMessages() {
851         return this.errorMessages;
852     }
853 
854     public Map<String, List<ErrorMessage>> getWarningMessages() {
855         return this.warningMessages;
856     }
857 
858     public Map<String, List<ErrorMessage>> getInfoMessages() {
859         return this.infoMessages;
860     }
861 
862     /**
863      * Returns the list of growl messages (@{link GrowlMessage}) that have been added to
864      * the message map
865      *
866      * @return List<GrowlMessage>
867      */
868     public List<GrowlMessage> getGrowlMessages() {
869         return this.growlMessages;
870     }
871 
872     @Override
873     public boolean equals(Object o) {
874         return EqualsBuilder.reflectionEquals(this, o);
875     }
876 
877     @Override
878     public int hashCode() {
879         return HashCodeBuilder.reflectionHashCode(this);
880     }
881 
882     @Override
883     public String toString() {
884         return ToStringBuilder.reflectionToString(this);
885     }
886 }