001/**
002 * Copyright 2005-2016 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 */
016package org.kuali.rice.krad.util;
017
018import org.apache.commons.lang.StringEscapeUtils;
019import org.apache.commons.lang.StringUtils;
020import org.apache.commons.lang.builder.EqualsBuilder;
021import org.apache.commons.lang.builder.HashCodeBuilder;
022import org.apache.commons.lang.builder.ToStringBuilder;
023import org.springframework.util.AutoPopulatingList;
024
025import java.io.Serializable;
026import java.util.ArrayList;
027import java.util.Iterator;
028import java.util.LinkedHashMap;
029import java.util.List;
030import java.util.Map;
031import 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 */
044public 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    public AutoPopulatingList<ErrorMessage> putError(String propertyName, String errorKey, String... errorParameters) {
118        ErrorMessage message = new ErrorMessage(errorKey, errorParameters);
119        return putMessageInMap(errorMessages, propertyName, message, true, true);
120    }
121
122    public AutoPopulatingList<ErrorMessage> putWarning(String propertyName, String messageKey,
123            String... messageParameters) {
124        ErrorMessage message = new ErrorMessage(messageKey, messageParameters);
125        return putMessageInMap(warningMessages, propertyName, message, true, true);
126    }
127
128    public AutoPopulatingList<ErrorMessage> putInfo(String propertyName, String messageKey,
129            String... messageParameters) {
130        ErrorMessage message = new ErrorMessage(messageKey, messageParameters);
131        return putMessageInMap(infoMessages, propertyName, message, true, true);
132    }
133
134    public AutoPopulatingList<ErrorMessage> putError(String propertyName, ErrorMessage message) {
135        return putMessageInMap(errorMessages, propertyName, message, true, true);
136    }
137
138    public AutoPopulatingList<ErrorMessage> putWarning(String propertyName, ErrorMessage message) {
139        return putMessageInMap(warningMessages, propertyName, message, true, true);
140    }
141
142    public AutoPopulatingList<ErrorMessage> putInfo(String propertyName, ErrorMessage message) {
143        return putMessageInMap(infoMessages, propertyName, message, true, true);
144    }
145
146    public AutoPopulatingList<ErrorMessage> putErrorWithoutFullErrorPath(String propertyName, String errorKey,
147            String... errorParameters) {
148        ErrorMessage message = new ErrorMessage(errorKey, errorParameters);
149        return putMessageInMap(errorMessages, propertyName, message, false, true);
150    }
151
152    public AutoPopulatingList<ErrorMessage> putWarningWithoutFullErrorPath(String propertyName, String messageKey,
153            String... messageParameters) {
154        ErrorMessage message = new ErrorMessage(messageKey, messageParameters);
155        return putMessageInMap(warningMessages, propertyName, message, false, true);
156    }
157
158    public AutoPopulatingList<ErrorMessage> putInfoWithoutFullErrorPath(String propertyName, String messageKey,
159            String... messageParameters) {
160        ErrorMessage message = new ErrorMessage(messageKey, messageParameters);
161        return putMessageInMap(infoMessages, propertyName, message, false, true);
162    }
163
164    public AutoPopulatingList<ErrorMessage> putErrorWithoutFullErrorPath(String propertyName, ErrorMessage message) {
165        return putMessageInMap(errorMessages, propertyName, message, false, true);
166    }
167
168    public AutoPopulatingList<ErrorMessage> putWarningWithoutFullErrorPath(String propertyName, ErrorMessage message) {
169        return putMessageInMap(warningMessages, propertyName, message, false, true);
170    }
171
172    public AutoPopulatingList<ErrorMessage> putInfoWithoutFullErrorPath(String propertyName, ErrorMessage message) {
173        return putMessageInMap(infoMessages, propertyName, message, false, true);
174    }
175
176    public AutoPopulatingList<ErrorMessage> putErrorForSectionId(String sectionId, String errorKey,
177            String... errorParameters) {
178        return putErrorWithoutFullErrorPath(sectionId, errorKey, errorParameters);
179    }
180
181    public AutoPopulatingList<ErrorMessage> putWarningForSectionId(String sectionId, String messageKey,
182            String... messageParameters) {
183        return putWarningWithoutFullErrorPath(sectionId, messageKey, messageParameters);
184    }
185
186    public AutoPopulatingList<ErrorMessage> putInfoForSectionId(String sectionId, String messageKey,
187            String... messageParameters) {
188        return putInfoWithoutFullErrorPath(sectionId, messageKey, messageParameters);
189    }
190
191    public AutoPopulatingList<ErrorMessage> putErrorForSectionId(String sectionId, ErrorMessage message) {
192        return putErrorWithoutFullErrorPath(sectionId, message);
193    }
194
195    public AutoPopulatingList<ErrorMessage> putWarningForSectionId(String sectionId, ErrorMessage message) {
196        return putWarningWithoutFullErrorPath(sectionId, message);
197    }
198
199    public AutoPopulatingList<ErrorMessage> putInfoForSectionId(String sectionId, ErrorMessage message) {
200        return putInfoWithoutFullErrorPath(sectionId, message);
201    }
202
203    /**
204     * Adds a growl (using the default theme) to the message map with the given title and message
205     *
206     * @param growlTitle - title for the growl
207     * @param messageKey - key for the message in resources
208     * @param messageParameters - parameters for the message
209     */
210    public void addGrowlMessage(String growlTitle, String messageKey, String... messageParameters) {
211        GrowlMessage growl = new GrowlMessage();
212
213        growl.setTitle(growlTitle);
214        growl.setMessageKey(messageKey);
215        growl.setMessageParameters(messageParameters);
216
217        growlMessages.add(growl);
218    }
219
220    /**
221     * Add a growl to the message map
222     *
223     * @param growl - growl instance to add
224     */
225    public void addGrowlMessage(GrowlMessage growl) {
226        growlMessages.add(growl);
227    }
228
229    /**
230     * Adds an error message to the given message map, adjusting the error path and message parameters if necessary
231     *
232     * @param messagesMap
233     * @param propertyName name of the property to add error under
234     * @param errorMessage
235     * @param prependFullErrorPath true if you want the whole parent error path prepended, false otherwise
236     * @param escapeHtmlMessageParameters whether to escape HTML characters in the message parameters, provides
237     * protection against XSS attacks
238     * @return TypeArrayList
239     */
240    protected AutoPopulatingList<ErrorMessage> putMessageInMap(Map<String, AutoPopulatingList<ErrorMessage>> messagesMap,
241            String propertyName, ErrorMessage errorMessage, boolean prependFullErrorPath,
242            boolean escapeHtmlMessageParameters) {
243        if (StringUtils.isBlank(propertyName)) {
244            throw new IllegalArgumentException("invalid (blank) propertyName");
245        }
246        if (StringUtils.isBlank(errorMessage.getErrorKey())) {
247            throw new IllegalArgumentException("invalid (blank) errorKey");
248        }
249
250        // check if we have previous errors for this property
251        AutoPopulatingList<ErrorMessage> errorList = null;
252        String propertyKey = getKeyPath(propertyName, prependFullErrorPath);
253        if (messagesMap.containsKey(propertyKey)) {
254            errorList = messagesMap.get(propertyKey);
255        } else {
256            errorList = new AutoPopulatingList<ErrorMessage>(ErrorMessage.class);
257        }
258
259        if (escapeHtmlMessageParameters) {
260            if (errorMessage.getMessageParameters() != null) {
261                String[] filteredMessageParameters = new String[errorMessage.getMessageParameters().length];
262                for (int i = 0; i < errorMessage.getMessageParameters().length; i++) {
263                    filteredMessageParameters[i] = StringEscapeUtils.escapeHtml(errorMessage.getMessageParameters()[i]);
264                }
265                errorMessage.setMessageParameters(filteredMessageParameters);
266            }
267
268            if (errorMessage.getMessagePrefixParameters() != null) {
269                String[] filteredMessageParameters = new String[errorMessage.getMessagePrefixParameters().length];
270                for (int i = 0; i < errorMessage.getMessagePrefixParameters().length; i++) {
271                    filteredMessageParameters[i] = StringEscapeUtils.escapeHtml(
272                            errorMessage.getMessagePrefixParameters()[i]);
273                }
274                errorMessage.setMessagePrefixParameters(filteredMessageParameters);
275            }
276
277            if (errorMessage.getMessageSuffixParameters() != null) {
278                String[] filteredMessageParameters = new String[errorMessage.getMessageSuffixParameters().length];
279                for (int i = 0; i < errorMessage.getMessageSuffixParameters().length; i++) {
280                    filteredMessageParameters[i] = StringEscapeUtils.escapeHtml(
281                            errorMessage.getMessageSuffixParameters()[i]);
282                }
283                errorMessage.setMessageSuffixParameters(filteredMessageParameters);
284            }
285        }
286
287        // check if this error has already been added to the list
288        boolean alreadyAdded = false;
289        for (ErrorMessage e : errorList) {
290            if (e.equals(errorMessage)) {
291                alreadyAdded = true;
292                break;
293            }
294        }
295        if (!alreadyAdded) {
296            errorList.add(errorMessage);
297        }
298
299        return messagesMap.put(propertyKey, errorList);
300    }
301
302    /**
303     * If any error messages with the key targetKey exist in this ErrorMap for the named property, those ErrorMessages
304     * will be replaced with a new ErrorMessage with the given replaceKey and replaceParameters.
305     *
306     * @param propertyName name of the property where existing error will be replaced
307     * @param targetKey error key of message to be replaced
308     * @param replaceParameters zero or more string parameters for the replacement error message
309     * @return true if the replacement occurred
310     * @paran replaceKey error key which will replace targetKey
311     */
312    public boolean replaceError(String propertyName, String targetKey, String replaceKey, String... replaceParameters) {
313        return replaceError(propertyName, targetKey, true, replaceKey, replaceParameters);
314    }
315
316    /**
317     * If any error messages with the key targetKey exist in this ErrorMap for the named property, those ErrorMessages
318     * will be replaced with a new ErrorMessage with the given replaceKey and replaceParameters. The targetKey
319     * and replaceKey will be prepended with the current errorPath, if any.
320     *
321     * @param propertyName name of the property where existing error will be replaced
322     * @param targetKey error key of message to be replaced
323     * @param replaceParameters zero or more string parameters for the replacement error message
324     * @return true if the replacement occurred
325     * @paran replaceKey error key which will replace targetKey
326     */
327    public boolean replaceErrorWithoutFullErrorPath(String propertyName, String targetKey, String replaceKey,
328            String... replaceParameters) {
329        return replaceError(propertyName, targetKey, false, replaceKey, replaceParameters);
330    }
331
332    /**
333     * If any error messages with the key targetKey exist in this ErrorMap for the named property, those ErrorMessages
334     * will be replaced with a new ErrorMessage with the given replaceKey and replaceParameters.
335     *
336     * @param propertyName name of the property to add error under
337     * @param targetKey resource key used to retrieve the error text
338     * @param withFullErrorPath true if you want the whole parent error path appended, false otherwise
339     * @param replaceParameters zero or more string parameters for the displayed error message
340     * @return true if the replacement occurred
341     */
342    private boolean replaceError(String propertyName, String targetKey, boolean withFullErrorPath, String replaceKey,
343            String... replaceParameters) {
344        boolean replaced = false;
345
346        if (StringUtils.isBlank(propertyName)) {
347            throw new IllegalArgumentException("invalid (blank) propertyName");
348        }
349        if (StringUtils.isBlank(targetKey)) {
350            throw new IllegalArgumentException("invalid (blank) targetKey");
351        }
352        if (StringUtils.isBlank(replaceKey)) {
353            throw new IllegalArgumentException("invalid (blank) replaceKey");
354        }
355
356        // check if we have previous errors for this property
357        AutoPopulatingList<ErrorMessage> errorList = null;
358        String propertyKey = getKeyPath(propertyName, withFullErrorPath);
359        if (errorMessages.containsKey(propertyKey)) {
360            errorList = errorMessages.get(propertyKey);
361
362            // look for the specific targetKey
363            for (int i = 0; i < errorList.size(); ++i) {
364                ErrorMessage em = errorList.get(i);
365
366                // replace matching messages
367                if (em.getErrorKey().equals(targetKey)) {
368                    ErrorMessage rm = new ErrorMessage(replaceKey, replaceParameters);
369                    errorList.set(i, rm);
370                    replaced = true;
371                }
372            }
373        }
374
375        return replaced;
376    }
377
378    /**
379     * Returns true if the named field has a message with the given errorKey
380     *
381     * @param errorKey
382     * @param fieldName
383     * @return boolean
384     */
385    public boolean fieldHasMessage(String fieldName, String errorKey) {
386        boolean found = false;
387
388        List<ErrorMessage> fieldMessages = errorMessages.get(fieldName);
389        if (fieldMessages != null) {
390            for (Iterator<ErrorMessage> i = fieldMessages.iterator(); !found && i.hasNext(); ) {
391                ErrorMessage errorMessage = i.next();
392                found = errorMessage.getErrorKey().equals(errorKey);
393            }
394        }
395
396        return found;
397    }
398
399    /**
400     * Returns the number of messages for the given field
401     *
402     * @param fieldName
403     * @return int
404     */
405    public int countFieldMessages(String fieldName) {
406        int count = 0;
407
408        List<ErrorMessage> fieldMessages = errorMessages.get(fieldName);
409        if (fieldMessages != null) {
410            count = fieldMessages.size();
411        }
412
413        return count;
414    }
415
416    /**
417     * @return true if the given messageKey is associated with some property in this ErrorMap
418     */
419    public boolean containsMessageKey(String messageKey) {
420        ErrorMessage foundMessage = null;
421
422        if (!hasNoErrors()) {
423            for (Iterator<Map.Entry<String, AutoPopulatingList<ErrorMessage>>> i =
424                         getAllPropertiesAndErrors().iterator(); (foundMessage == null) && i.hasNext(); ) {
425                Map.Entry<String, AutoPopulatingList<ErrorMessage>> e = i.next();
426                AutoPopulatingList<ErrorMessage> entryErrorList = e.getValue();
427                for (Iterator<ErrorMessage> j = entryErrorList.iterator(); j.hasNext(); ) {
428                    ErrorMessage em = j.next();
429                    if (messageKey.equals(em.getErrorKey())) {
430                        foundMessage = em;
431                    }
432                }
433            }
434        }
435
436        return (foundMessage != null);
437    }
438
439    private int getMessageCount(Map<String, AutoPopulatingList<ErrorMessage>> messageMap) {
440        int messageCount = 0;
441        for (Iterator<String> iter = messageMap.keySet().iterator(); iter.hasNext(); ) {
442            String errorKey = iter.next();
443            List<ErrorMessage> errors = messageMap.get(errorKey);
444            messageCount += errors.size();
445        }
446
447        return messageCount;
448    }
449
450    /**
451     * Counts the total number of error messages in the map
452     *
453     * @return returns an int for the total number of errors
454     */
455    public int getErrorCount() {
456        return getMessageCount(errorMessages);
457    }
458
459    /**
460     * Counts the total number of warning messages in the map
461     *
462     * @return returns an int for the total number of warnings
463     */
464    public int getWarningCount() {
465        return getMessageCount(warningMessages);
466    }
467
468    /**
469     * Counts the total number of info messages in the map
470     *
471     * @return returns an int for the total number of info
472     */
473    public int getInfoCount() {
474        return getMessageCount(infoMessages);
475    }
476
477    /**
478     * @param path
479     * @return Returns a List of ErrorMessages for the given path
480     */
481    public AutoPopulatingList<ErrorMessage> getMessages(String path) {
482        return errorMessages.get(path);
483    }
484
485    /**
486     * Adds a string prefix to the error path.
487     *
488     * @param parentName
489     */
490    public void addToErrorPath(String parentName) {
491        errorPath.add(parentName);
492    }
493
494    /**
495     * This method returns the list that holds the error path values.
496     *
497     * @return List
498     */
499    public List<String> getErrorPath() {
500        return errorPath;
501    }
502
503    /**
504     * Removes a string prefix from the error path.
505     *
506     * @param parentName
507     * @return boolean Returns true if the parentName existed, false otherwise.
508     */
509    public boolean removeFromErrorPath(String parentName) {
510        return errorPath.remove(parentName);
511    }
512
513    /**
514     * Clears the errorPath.
515     */
516    public void clearErrorPath() {
517        errorPath.clear();
518    }
519
520    /**
521     * This is what's prepended to the beginning of the key. This is built by iterating over all of the entries in the
522     * errorPath
523     * list and concatenating them together with a "."
524     *
525     * @param propertyName
526     * @param prependFullErrorPath
527     * @return String Returns the keyPath.
528     */
529    public String getKeyPath(String propertyName, boolean prependFullErrorPath) {
530        String keyPath = "";
531
532        if (KRADConstants.GLOBAL_ERRORS.equals(propertyName)) {
533            return KRADConstants.GLOBAL_ERRORS;
534        }
535
536        if (!errorPath.isEmpty() && prependFullErrorPath) {
537            keyPath = StringUtils.join(errorPath.iterator(), ".");
538            keyPath += (keyPath != null && keyPath.endsWith(".")) ? propertyName : "." + propertyName;
539        } else {
540            keyPath = propertyName;
541        }
542
543        return keyPath;
544    }
545
546    /**
547     * @return List of the property names that have errors.
548     */
549    public List<String> getPropertiesWithErrors() {
550        List<String> properties = new ArrayList<String>();
551
552        for (Iterator<String> iter = errorMessages.keySet().iterator(); iter.hasNext(); ) {
553            properties.add(iter.next());
554        }
555
556        return properties;
557    }
558
559    /**
560     * @return List of the property names that have warnings.
561     */
562    public List<String> getPropertiesWithWarnings() {
563        List<String> properties = new ArrayList<String>(warningMessages.keySet());
564        return properties;
565    }
566
567    /**
568     * @return List of the property names that have info.
569     */
570    public List<String> getPropertiesWithInfo() {
571        List<String> properties = new ArrayList<String>(infoMessages.keySet());
572        return properties;
573    }
574
575    public void clearErrorMessages() {
576        errorMessages.clear();
577    }
578
579    public boolean doesPropertyHaveError(String key) {
580        return errorMessages.containsKey(key);
581    }
582
583    /**
584     * @param pattern comma separated list of keys, optionally ending with * wildcard
585     */
586    public boolean containsKeyMatchingPattern(String pattern) {
587        List<String> simplePatterns = new ArrayList<String>();
588        List<String> wildcardPatterns = new ArrayList<String>();
589        String[] patterns = pattern.split(",");
590        for (int i = 0; i < patterns.length; i++) {
591            String s = patterns[i];
592            if (s.endsWith("*")) {
593                wildcardPatterns.add(s.substring(0, s.length() - 1));
594            } else {
595                simplePatterns.add(s);
596            }
597        }
598        for (Iterator<String> keys = errorMessages.keySet().iterator(); keys.hasNext(); ) {
599            String key = keys.next();
600            if (simplePatterns.contains(key)) {
601                return true;
602            }
603            for (Iterator<String> wildcardIterator = wildcardPatterns.iterator(); wildcardIterator.hasNext(); ) {
604                String wildcard = wildcardIterator.next();
605                if (key.startsWith(wildcard)) {
606                    return true;
607                }
608            }
609        }
610        return false;
611    }
612
613    public Set<Map.Entry<String, AutoPopulatingList<ErrorMessage>>> getAllPropertiesAndErrors() {
614        return errorMessages.entrySet();
615    }
616
617    public AutoPopulatingList<ErrorMessage> getErrorMessagesForProperty(String propertyName) {
618        return errorMessages.get(propertyName);
619    }
620
621    public AutoPopulatingList<ErrorMessage> getWarningMessagesForProperty(String propertyName) {
622        return warningMessages.get(propertyName);
623    }
624
625    public AutoPopulatingList<ErrorMessage> getInfoMessagesForProperty(String propertyName) {
626        return infoMessages.get(propertyName);
627    }
628
629    /**
630     * Gets a list of lists that represent errors that matched by the
631     * propertyName passed in (multiple lists because the wildcard can match
632     * multiple keys). If wildcard is true, the propertyName ends with a
633     * wildcard character. Otherwise, it will only match on the single key and
634     * return a list with one list
635     *
636     * @param propertyName
637     * @param allowWildcard
638     * @return
639     */
640    public List<AutoPopulatingList<ErrorMessage>> getErrorMessagesForProperty(String propertyName,
641            boolean allowWildcard) {
642        List<AutoPopulatingList<ErrorMessage>> foundMessages = new ArrayList<AutoPopulatingList<ErrorMessage>>();
643        if (allowWildcard) {
644            boolean wildcard = false;
645            if (propertyName.endsWith("*")) {
646                wildcard = true;
647                propertyName = propertyName.substring(0, propertyName.length() - 1);
648            }
649            for (Iterator<String> keys = errorMessages.keySet().iterator(); keys.hasNext(); ) {
650                String key = keys.next();
651                if (!wildcard && propertyName.equals(key)) {
652                    foundMessages.add(errorMessages.get(key));
653                    break;
654                } else if (wildcard && key.startsWith(propertyName)) {
655                    foundMessages.add(errorMessages.get(key));
656                }
657            }
658        } else {
659            foundMessages.add(getErrorMessagesForProperty(propertyName));
660        }
661
662        return foundMessages;
663    }
664
665    /**
666     * Gets a list of lists that represent warnings that matched by the
667     * propertyName passed in (multiple lists because the wildcard can match
668     * multiple keys). If wildcard is true, the propertyName ends with a
669     * wildcard character. Otherwise, it will only match on the single key and
670     * return a list with one list.
671     *
672     * @param propertyName
673     * @param allowWildcard
674     * @return
675     */
676    public List<AutoPopulatingList<ErrorMessage>> getWarningMessagesForProperty(String propertyName,
677            boolean allowWildcard) {
678        List<AutoPopulatingList<ErrorMessage>> foundMessages = new ArrayList<AutoPopulatingList<ErrorMessage>>();
679        if (allowWildcard) {
680            boolean wildcard = false;
681            if (propertyName.endsWith("*")) {
682                wildcard = true;
683                propertyName = propertyName.substring(0, propertyName.length() - 1);
684            }
685            for (Iterator<String> keys = warningMessages.keySet().iterator(); keys.hasNext(); ) {
686                String key = keys.next();
687                if (!wildcard && propertyName.equals(key)) {
688                    foundMessages.add(warningMessages.get(key));
689                    break;
690                } else if (wildcard && key.startsWith(propertyName)) {
691                    foundMessages.add(warningMessages.get(key));
692                }
693            }
694        } else {
695            foundMessages.add(getWarningMessagesForProperty(propertyName));
696        }
697
698        return foundMessages;
699    }
700
701    /**
702     * Gets a list of lists that represent info messages that matched by the
703     * propertyName passed in (multiple lists because the wildcard can match
704     * multiple keys). If wildcard is true, the propertyName ends with a
705     * wildcard character. If it is false, it will only match on the single key
706     * and return a list with one list.
707     *
708     * @param propertyName
709     * @param allowWildcard
710     * @return
711     */
712    public List<AutoPopulatingList<ErrorMessage>> getInfoMessagesForProperty(String propertyName,
713            boolean allowWildcard) {
714        List<AutoPopulatingList<ErrorMessage>> foundMessages = new ArrayList<AutoPopulatingList<ErrorMessage>>();
715        if (allowWildcard) {
716            boolean wildcard = false;
717            if (propertyName.endsWith("*")) {
718                wildcard = true;
719                propertyName = propertyName.substring(0, propertyName.length() - 1);
720            }
721            for (Iterator<String> keys = infoMessages.keySet().iterator(); keys.hasNext(); ) {
722                String key = keys.next();
723                if (!wildcard && propertyName.equals(key)) {
724                    foundMessages.add(infoMessages.get(key));
725                    break;
726                } else if (wildcard && key.startsWith(propertyName)) {
727                    foundMessages.add(infoMessages.get(key));
728                }
729            }
730        } else {
731            foundMessages.add(getInfoMessagesForProperty(propertyName));
732        }
733
734        return foundMessages;
735    }
736
737    public boolean hasErrors() {
738        return !errorMessages.isEmpty();
739    }
740
741    public boolean hasNoErrors() {
742        return errorMessages.isEmpty();
743    }
744
745    public boolean hasWarnings() {
746        return !warningMessages.isEmpty();
747    }
748
749    public boolean hasNoWarnings() {
750        return warningMessages.isEmpty();
751    }
752
753    public boolean hasInfo() {
754        return !infoMessages.isEmpty();
755    }
756
757    public boolean hasNoInfo() {
758        return infoMessages.isEmpty();
759    }
760
761    public boolean hasMessages() {
762        if (!errorMessages.isEmpty() || !warningMessages.isEmpty() || !infoMessages.isEmpty()) {
763            return true;
764        }
765        return false;
766    }
767
768    public boolean hasNoMessages() {
769        if (errorMessages.isEmpty() && warningMessages.isEmpty() && infoMessages.isEmpty()) {
770            return true;
771        }
772        return false;
773    }
774
775    public Set<String> getAllPropertiesWithErrors() {
776        return errorMessages.keySet();
777    }
778
779    public Set<String> getAllPropertiesWithWarnings() {
780        return warningMessages.keySet();
781    }
782
783    public Set<String> getAllPropertiesWithInfo() {
784        return infoMessages.keySet();
785    }
786
787    public AutoPopulatingList<ErrorMessage> removeAllErrorMessagesForProperty(String property) {
788        return errorMessages.remove(property);
789    }
790
791    public AutoPopulatingList<ErrorMessage> removeAllWarningMessagesForProperty(String property) {
792        return warningMessages.remove(property);
793    }
794
795    public AutoPopulatingList<ErrorMessage> removeAllInfoMessagesForProperty(String property) {
796        return infoMessages.remove(property);
797    }
798
799    public int getNumberOfPropertiesWithErrors() {
800        return errorMessages.size();
801    }
802
803    public Map<String, AutoPopulatingList<ErrorMessage>> getErrorMessages() {
804        return this.errorMessages;
805    }
806
807    public Map<String, AutoPopulatingList<ErrorMessage>> getWarningMessages() {
808        return this.warningMessages;
809    }
810
811    public Map<String, AutoPopulatingList<ErrorMessage>> getInfoMessages() {
812        return this.infoMessages;
813    }
814
815    /**
816     * Returns the list of growl messages (@{link GrowlMessage}) that have been added to
817     * the message map
818     *
819     * @return List<GrowlMessage>
820     */
821    public List<GrowlMessage> getGrowlMessages() {
822        return this.growlMessages;
823    }
824
825    @Override
826    public boolean equals(Object o) {
827        return EqualsBuilder.reflectionEquals(this, o);
828    }
829
830    @Override
831    public int hashCode() {
832        return HashCodeBuilder.reflectionHashCode(this);
833    }
834
835    @Override
836    public String toString() {
837        return ToStringBuilder.reflectionToString(this);
838    }
839}