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