001: /*
002: * Copyright (c) 2003-2007 JGoodies Karsten Lentzsch. All Rights Reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * o Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * o Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * o Neither the name of JGoodies Karsten Lentzsch nor the names of
015: * its contributors may be used to endorse or promote products derived
016: * from this software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020: * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
022: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
027: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package com.jgoodies.validation;
032:
033: import java.io.Serializable;
034: import java.util.*;
035:
036: import com.jgoodies.validation.message.SimpleValidationMessage;
037:
038: /**
039: * Describes a validation result as a list of ValidationMessages.
040: * You can add single validation messages, single text messages,
041: * lists of messages, and all messages from another ValidationResult.
042: *
043: * @author Karsten Lentzsch
044: * @version $Revision: 1.16 $
045: *
046: * @see ValidationMessage
047: * @see Validator
048: */
049: public final class ValidationResult implements Serializable {
050:
051: /**
052: * A constant for an empty and unmodifiable validation result.
053: */
054: public static final ValidationResult EMPTY = new ValidationResult(
055: Collections.<ValidationMessage> emptyList(), false);
056:
057: /**
058: * Holds a List of ValidationMessages.
059: */
060: private final List<ValidationMessage> messageList;
061:
062: /**
063: * Describes if this result can be modified or not.
064: */
065: private final boolean modifiable;
066:
067: // Instance Creation ******************************************************
068:
069: /**
070: * Constructs an empty modifiable ValidationResult.
071: */
072: public ValidationResult() {
073: this (new ArrayList<ValidationMessage>(), true);
074: }
075:
076: /**
077: * Constructs a ValidationResult on the given message list.
078: * Used for constructing the <code>EMPTY</code> validation result.
079: *
080: * @param messageList an initial message list
081: * @param modifiable true to allow modifications, false to prevent them
082: */
083: private ValidationResult(List<ValidationMessage> messageList,
084: boolean modifiable) {
085: this .messageList = messageList;
086: this .modifiable = modifiable;
087: }
088:
089: /**
090: * Returns an unmodifiable view of the given ValidationResult.
091: * Useful to provide users with "read-only" access to internal results,
092: * or to indicate to other validation result processors that a result
093: * is not intended to be modified. Attempts to modify the returned
094: * validation result throw an <code>UnsupportedOperationException</code>.
095: *
096: * @param validationResult the result for which an unmodifiable view is to be returned
097: * @return an unmodifiable view of the specified validation result
098: */
099: public static ValidationResult unmodifiableResult(
100: ValidationResult validationResult) {
101: return validationResult.modifiable ? new ValidationResult(
102: new ArrayList<ValidationMessage>(
103: validationResult.messageList), false)
104: : validationResult;
105: }
106:
107: // Adding Messages ********************************************************
108:
109: /**
110: * Adds a new ValidationMessage to the list of messages.
111: *
112: * @param validationMessage the message to add
113: *
114: * @throws NullPointerException if the message is <code>null</code>
115: * @throws UnsupportedOperationException if the result is unmodifiable
116: * @throws IllegalArgumentException if the severity is <code>OK</code>
117: *
118: * @see #addError(String)
119: * @see #addWarning(String)
120: */
121: public void add(ValidationMessage validationMessage) {
122: assertModifiable();
123: if (validationMessage == null)
124: throw new NullPointerException(
125: "The validation message must not be null.");
126: if (validationMessage.severity() == Severity.OK)
127: throw new IllegalArgumentException(
128: "You must not add a validation message with severity OK.");
129:
130: messageList.add(validationMessage);
131: }
132:
133: /**
134: * Creates and adds an error message to the list of validation messages
135: * using the given text.
136: *
137: * @param text the error text to add
138: *
139: * @throws NullPointerException if the message text <code>null</code>
140: * @throws UnsupportedOperationException if the result is unmodifiable
141: *
142: * @see #add(ValidationMessage)
143: * @see #addWarning(String)
144: */
145: public void addError(String text) {
146: assertModifiable();
147: if (text == null)
148: throw new NullPointerException(
149: "The message text must not be null.");
150:
151: add(new SimpleValidationMessage(text, Severity.ERROR));
152: }
153:
154: /**
155: * Creates and adds a warning message to the list of validation messages
156: * using the given text.
157: *
158: * @param text the warning text to add
159: *
160: * @throws NullPointerException if the message text <code>null</code>
161: * @throws UnsupportedOperationException if the result is unmodifiable
162: *
163: * @see #add(ValidationMessage)
164: * @see #addError(String)
165: */
166: public void addWarning(String text) {
167: assertModifiable();
168: if (text == null)
169: throw new NullPointerException(
170: "The message text must not be null.");
171:
172: add(new SimpleValidationMessage(text));
173: }
174:
175: /**
176: * Adds all messages from the given list to this validation result.
177: *
178: * @param messages the messages to be added
179: *
180: * @throws NullPointerException if the messages list is <code>null</code>
181: * @throws UnsupportedOperationException if the result is unmodifiable
182: * @throws IllegalArgumentException if the messages list contains
183: * a message with severity <code>OK</code>
184: *
185: * @see #addAllFrom(ValidationResult)
186: */
187: public void addAll(List<ValidationMessage> messages) {
188: assertModifiable();
189: if (messages == null)
190: throw new NullPointerException(
191: "The messages list must not be null.");
192: for (ValidationMessage message : messages) {
193: if (message.severity() == Severity.OK) {
194: throw new IllegalArgumentException(
195: "You must not add a validation message with severity OK.");
196: }
197: }
198: messageList.addAll(messages);
199: }
200:
201: /**
202: * Adds all messages from the given ValidationResult
203: * to the list of messages that this validation result holds.
204: *
205: * @param validationResult the validation result to add messages from
206: *
207: * @throws NullPointerException if the validation result is <code>null</code>
208: * @throws UnsupportedOperationException if the result is unmodifiable
209: *
210: * @see #addAll(List)
211: */
212: public void addAllFrom(ValidationResult validationResult) {
213: assertModifiable();
214: if (validationResult == null)
215: throw new NullPointerException(
216: "The validation result to add must not be null.");
217:
218: addAll(validationResult.messageList);
219: }
220:
221: // List Operations ********************************************************
222:
223: /**
224: * Checks and answers whether this validation result contains no messages.
225: *
226: * @return true if this validation result contains no messages
227: *
228: * @see #hasErrors()
229: * @see #hasWarnings()
230: */
231: public boolean isEmpty() {
232: return messageList.isEmpty();
233: }
234:
235: /**
236: * Returns the number of messages in this result.
237: *
238: * @return the number of elements in this list
239: */
240: public int size() {
241: return messageList.size();
242: }
243:
244: /**
245: * Checks and answers whether this result contains the specified message.
246: * More formally, returns <code>true</code> if and only if this result
247: * contains at least one message <code>m</code> such that
248: * <code>(message.equals(m))</code>.
249: *
250: * @param message message whose presence in this result is to be tested
251: * @return <code>true</code> if this result contains the specified message
252: * @throws NullPointerException if the specified message is
253: * <code>null</code>
254: */
255: public boolean contains(ValidationMessage message) {
256: return messageList.contains(message);
257: }
258:
259: /**
260: * Returns an unmodifiable view of the portion of this result between
261: * the specified <code>fromIndex</code>, inclusive, and <code>toIndex</code>,
262: * exclusive.
263: * (If <code>fromIndex</code> and <code>toIndex</code> are equal,
264: * the returned result is empty.) The returned result is a copy,
265: * so changes in the returned result won't affect this result,
266: * and vice-versa.
267: *
268: * @param fromIndex low end point (inclusive) of the subResult
269: * @param toIndex high end point (exclusive) of the subResult
270: * @return a view of the specified range within this result.
271: *
272: * @throws IndexOutOfBoundsException for an illegal end point index value
273: * (fromIndex < 0 || toIndex > size || fromIndex > toIndex).
274: *
275: * @see #subResult(Object)
276: */
277: public ValidationResult subResult(int fromIndex, int toIndex) {
278: List<ValidationMessage> messages = messageList.subList(
279: fromIndex, toIndex);
280: return new ValidationResult(messages, false);
281: }
282:
283: /**
284: * Returns an unmodifiable sub result of this result that consists of
285: * all messages that share the specified message key. If the specified key
286: * is <code>null</code>, this method returns an empty result.
287: * The returned result is a copy, so changes in this result won't affect it.
288: *
289: * @param messageKey the key to look for, can be <code>null</code>
290: * @return a sub result containing all messages that share the
291: * specified key, or the empty result if the key is <code>null</code>
292: *
293: * @see #subResult(int, int)
294: */
295: public ValidationResult subResult(Object messageKey) {
296: if (messageKey == null)
297: return EMPTY;
298:
299: List<ValidationMessage> messages = new ArrayList<ValidationMessage>();
300: for (ValidationMessage message : messageList) {
301: if (messageKey.equals(message.key())) {
302: messages.add(message);
303: }
304: }
305: return new ValidationResult(messages, false);
306: }
307:
308: /**
309: * Returns an unmodifiable sub result of this result that consists of
310: * all messages that share the specified message keys. If the array of keys
311: * is <code>null</code>, this method returns an empty result.
312: * The returned result is a copy, so changes in this result won't affect it.
313: *
314: * @param messageKeys the keys to look for, can be <code>null</code>
315: * @return a sub result containing all messages that share the specified
316: * keys, or the empty result if the key array is <code>null</code>
317: *
318: * @see #subResult(int, int)
319: *
320: * @since 1.4
321: */
322: public ValidationResult subResult(Object[] messageKeys) {
323: if (messageKeys == null) {
324: return EMPTY;
325: }
326: List<ValidationMessage> messages = new ArrayList<ValidationMessage>();
327: for (ValidationMessage message : messageList) {
328: Object messageKey = message.key();
329: for (Object key : messageKeys) {
330: if (messageKey.equals(key)) {
331: messages.add(message);
332: }
333: }
334: }
335: return new ValidationResult(messages, false);
336: }
337:
338: /**
339: * Creates and returns an unmodifiable Map that maps the message keys
340: * of this validation result to unmodifiable sub results that share the key.<p>
341: *
342: * More formally:
343: * for each key <code>key</code> in the created map <code>map</code>,
344: * <code>map.get(key)</code> returns a <code>ValidationResult</code>
345: * <code>result</code>, such that for each <code>ValidationMessage</code>
346: * <code>message</code> in <code>result</code> we have:
347: * <code>message.key().equals(key)</code>.
348: *
349: * @return a mapping from message key to an associated validation result
350: * that consist only of messages with this key
351: *
352: * @see ValidationMessage#key()
353: */
354: public Map<Object, ValidationResult> keyMap() {
355: Map<Object, List<ValidationMessage>> messageMap = new HashMap<Object, List<ValidationMessage>>();
356: for (ValidationMessage message : messageList) {
357: Object key = message.key();
358: List<ValidationMessage> associatedMessages = messageMap
359: .get(key);
360: if (associatedMessages == null) {
361: associatedMessages = new LinkedList<ValidationMessage>();
362: messageMap.put(key, associatedMessages);
363: }
364: associatedMessages.add(message);
365: }
366: Map<Object, ValidationResult> resultMap = new HashMap<Object, ValidationResult>(
367: messageMap.size());
368: for (Map.Entry<Object, List<ValidationMessage>> entry : messageMap
369: .entrySet()) {
370: Object key = entry.getKey();
371: List<ValidationMessage> messages = entry.getValue();
372: resultMap.put(key, new ValidationResult(messages, false));
373: }
374: return Collections.unmodifiableMap(resultMap);
375: }
376:
377: // Requesting Information *************************************************
378:
379: /**
380: * Returns the highest severity of this result's messages,
381: * <code>Severity.OK</code> if there are no messages.
382: *
383: * @return the highest severity of this result's messages,
384: * <code>Severity.OK</code> if there are no messages
385: *
386: * @see #hasMessages()
387: * @see #hasErrors()
388: * @see #hasWarnings()
389: */
390: public Severity getSeverity() {
391: return getSeverity(messageList);
392: }
393:
394: /**
395: * Checks and answers whether this validation result has messages or not.
396: *
397: * @return true if there are messages, false if not
398: *
399: * @see #getSeverity()
400: * @see #hasErrors()
401: * @see #hasWarnings()
402: */
403: public boolean hasMessages() {
404: return !isEmpty();
405: }
406:
407: /**
408: * Checks and answers whether this validation result
409: * contains a message of type <code>ERROR</code>.
410: *
411: * @return true if there are error messages, false if not
412: *
413: * @see #getSeverity()
414: * @see #hasMessages()
415: * @see #hasWarnings()
416: */
417: public boolean hasErrors() {
418: return hasSeverity(messageList, Severity.ERROR);
419: }
420:
421: /**
422: * Checks and answers whether this validation result
423: * contains a message of type <code>WARNING</code>.<p>
424: *
425: * Note that this method checks for warning messages only.
426: * It'll return false, if there are errors but no warnings.
427: * If you want to test whether this result contains
428: * warning and/or errors, use <code>#hasMessages</code> instead.
429: *
430: * @return true if there are warnings, false if not
431: *
432: * @see #getSeverity()
433: * @see #hasMessages()
434: * @see #hasErrors()
435: */
436: public boolean hasWarnings() {
437: return hasSeverity(messageList, Severity.WARNING);
438: }
439:
440: /**
441: * Returns an unmodifiable List of all validation messages.
442: *
443: * @return the <code>List</code> of all validation messages
444: *
445: * @see #getErrors()
446: * @see #getWarnings()
447: */
448: public List<ValidationMessage> getMessages() {
449: return Collections.unmodifiableList(messageList);
450: }
451:
452: /**
453: * Returns an unmodifiable List of the validation messages
454: * that indicate errors.
455: *
456: * @return the List of error validation messages
457: *
458: * @see #getMessages()
459: * @see #getWarnings()
460: */
461: public List<ValidationMessage> getErrors() {
462: return getMessagesWithSeverity(messageList, Severity.ERROR);
463: }
464:
465: /**
466: * Returns an unmodifiable List of the validation messages
467: * that indicate warnings.
468: *
469: * @return the List of validation warnings
470: *
471: * @see #getMessages()
472: * @see #getErrors()
473: */
474: public List<ValidationMessage> getWarnings() {
475: return getMessagesWithSeverity(messageList, Severity.WARNING);
476: }
477:
478: // Requesting State *******************************************************
479:
480: /**
481: * Returns if this validation result is modifiable or not.
482: * Can be used to cache data from unmodifiable result.
483: *
484: * @return true if modifiable, false if unmodifiable
485: */
486: public boolean isModifiable() {
487: return modifiable;
488: }
489:
490: // String Conversion ******************************************************
491:
492: /**
493: * Returns a string representation of the message list.
494: *
495: * @return a string representation of the message list
496: */
497: public String getMessagesText() {
498: return getMessagesText(messageList);
499: }
500:
501: /**
502: * Returns a string representation intended for debugging purposes.
503: *
504: * @return a string representation intended for debugging
505: * @see Object#toString()
506: */
507: @Override
508: public String toString() {
509: if (isEmpty()) {
510: return "Empty ValidationResult";
511: }
512: StringBuilder builder = new StringBuilder();
513: builder.append(modifiable ? "Modifiable" : "Unmodifiable");
514: builder.append(" ValidationResult:");
515: for (ValidationMessage message : messageList) {
516: builder.append("\n\t").append(message);
517: }
518: return builder.toString();
519: }
520:
521: // Comparison and Hashing *************************************************
522:
523: /**
524: * Compares the specified object with this validation result for equality.
525: * Returns <code>true</code> if and only if the specified object is also
526: * a validation result, both results have the same size, and all
527: * corresponding pairs of validation messages in the two validation results
528: * are <i>equal</i>. (Two validation messages <code>m1</code> and
529: * <code>m2</code> are <i>equal</i> if <code>(m1==null ? m2==null :
530: * m1.equals(m2))</code>.) In other words, two validation results
531: * are defined to be equal if and only if they contain the same
532: * validation messages in the same order.<p>
533: *
534: * This implementation first checks if the specified object is this
535: * validation result. If so, it returns <code>true</code>;
536: * if not, it checks if the specified object is a validation result.
537: * If not, it returns <code>false</code>; if so, it checks and returns
538: * if the lists of messages in both results are equal.
539: *
540: * @param o the object to be compared for equality with this validation result.
541: *
542: * @return <code>true</code> if the specified object is equal
543: * to this validation result.
544: *
545: * @see List#equals(java.lang.Object)
546: * @see Object#equals(java.lang.Object)
547: */
548: @Override
549: public boolean equals(Object o) {
550: if (o == this )
551: return true;
552: if (!(o instanceof ValidationResult))
553: return false;
554: return messageList.equals(((ValidationResult) o).messageList);
555: }
556:
557: /**
558: * Returns the hash code value for this validation result. This
559: * implementation returns the hash from the List of messages.
560: *
561: * @return the hash code value for this validation result.
562: *
563: * @see List#hashCode()
564: * @see Object#hashCode()
565: */
566: @Override
567: public int hashCode() {
568: return messageList.hashCode();
569: }
570:
571: // Helper Code ************************************************************
572:
573: /**
574: * Checks if this validation result is modifiable.
575: * Throws an UnsupportedOperationException otherwise.
576: */
577: private void assertModifiable() {
578: if (!modifiable)
579: throw new UnsupportedOperationException(
580: "This validation result is unmodifiable.");
581: }
582:
583: /**
584: * Returns the highest severity of this result's messages,
585: * <code>Severity.OK</code> if there are no messages.
586: * A single validation message can have only the severity
587: * error or warning. Hence, this method returns the error severity
588: * if there's at least one error message; and it returns
589: * the warning severity, otherwise - assuming that there are
590: * no other severities.<p>
591: *
592: * TODO: Consider changing the iteration to build the
593: * maximum of the message severities. This would make it
594: * easier for users that want to change this library's semantics.
595: * For example if someone adds Severity.FATAL, this algorithm
596: * may return an unexpected result.
597: *
598: * @param messages the List of ValidationMessages to check
599: * @return the highest severity of this result's messages,
600: * <code>Severity.OK</code> if there are no messages
601: */
602: private static Severity getSeverity(List<ValidationMessage> messages) {
603: if (messages.isEmpty()) {
604: return Severity.OK;
605: }
606: for (ValidationMessage message : messages) {
607: if (message.severity() == Severity.ERROR) {
608: return Severity.ERROR;
609: }
610: }
611: return Severity.WARNING;
612: }
613:
614: /**
615: * Checks and answers whether the given list of validation messages
616: * includes message with the specified Severity.
617: *
618: * @param messages the List of ValidationMessages to check
619: * @param severity the Severity to check
620: * @return true if the given messages list includes error messages,
621: * false if not
622: */
623: private static boolean hasSeverity(
624: List<ValidationMessage> messages, Severity severity) {
625: for (ValidationMessage message : messages) {
626: if (message.severity() == severity) {
627: return true;
628: }
629: }
630: return false;
631: }
632:
633: /**
634: * Returns an unmodifiable List of ValidationMessage that
635: * that is the sublist of message with the given Severity.
636: *
637: * @param messages the List of ValidationMessages to iterate
638: * @param severity the Severity to look for
639: * @return the sublist of error messages
640: */
641: private static List<ValidationMessage> getMessagesWithSeverity(
642: List<ValidationMessage> messages, Severity severity) {
643: List<ValidationMessage> errorMessages = new ArrayList<ValidationMessage>();
644: for (ValidationMessage message : messages) {
645: if (message.severity() == severity) {
646: errorMessages.add(message);
647: }
648: }
649: return Collections.unmodifiableList(errorMessages);
650: }
651:
652: /**
653: * Returns a string representation of the given list of messages.
654: *
655: * @param messages the List of ValidationMessages to iterate
656: * @return a string representation of the given list of messages
657: */
658: private static String getMessagesText(
659: List<ValidationMessage> messages) {
660: if (messages.isEmpty()) {
661: return "OK";
662: }
663: StringBuilder builder = new StringBuilder();
664: for (ValidationMessage message : messages) {
665: if (builder.length() > 0) {
666: builder.append("\n");
667: }
668: builder.append(message.formattedText());
669: }
670: return builder.toString();
671: }
672:
673: }
|