Java tutorial
/* * Copyright (c) 2003-2014 JGoodies Software GmbH. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Software GmbH nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.validation; import static com.jgoodies.common.base.Preconditions.checkArgument; import static com.jgoodies.common.base.Preconditions.checkNotNull; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import com.jgoodies.common.base.Strings; import com.jgoodies.validation.message.SimpleValidationMessage; /** * Describes a validation result as a list of ValidationMessages. * You can add single validation messages, single text messages, * lists of messages, and all messages from another ValidationResult. * * @author Karsten Lentzsch * @version $Revision: 1.24 $ * * @see ValidationMessage * @see Validator */ public final class ValidationResult implements Iterable<ValidationMessage>, Serializable { /** * A constant for an empty and unmodifiable validation result. */ public static final ValidationResult EMPTY = new ValidationResult(Collections.<ValidationMessage>emptyList(), false); /** * Holds a List of ValidationMessages. */ private final List<ValidationMessage> messageList; /** * Describes if this result can be modified or not. */ private final boolean modifiable; // Instance Creation ****************************************************** /** * Constructs an empty modifiable ValidationResult. */ public ValidationResult() { this(new ArrayList<ValidationMessage>(), true); } /** * Constructs a ValidationResult on the given message list. * Used for constructing the {@code EMPTY} validation result. * * @param messageList an initial message list * @param modifiable true to allow modifications, false to prevent them */ private ValidationResult(List<ValidationMessage> messageList, boolean modifiable) { this.messageList = messageList; this.modifiable = modifiable; } /** * Returns an unmodifiable view of the given ValidationResult. * Useful to provide users with "read-only" access to internal results, * or to indicate to other validation result processors that a result * is not intended to be modified. Attempts to modify the returned * validation result throw an {@code UnsupportedOperationException}. * * @param validationResult the result for which an unmodifiable view is to be returned * @return an unmodifiable view of the specified validation result */ public static ValidationResult unmodifiableResult(ValidationResult validationResult) { return validationResult.modifiable ? new ValidationResult(new ArrayList<ValidationMessage>(validationResult.messageList), false) : validationResult; } // Adding Messages ******************************************************** /** * Creates and adds an error message to the list of validation messages * using the given text. * * @param text the error text to add * * @throws NullPointerException if the message text {@code null} * @throws UnsupportedOperationException if the result is unmodifiable * * @see #add(ValidationMessage) * @see #addWarning(String) */ public ValidationResult addError(String text) { return addError((Object) null, text); } /** * Creates and adds an error message to the list of validation messages * using the given text and validation message key. * * @param text the error text to add * @param key the optional messages key * * @throws NullPointerException if the message text is {@code null} * @throws UnsupportedOperationException if the result is unmodifiable * * @see #add(ValidationMessage) * @see #addWarning(String) * * @since 2.3 */ public ValidationResult addError(String text, Object key) { return addError(key, text); } /** * Creates and adds an error message to the list of validation messages * using the given validation message key, (format) text and optional * format arguments. * * @param key the key used to associate messages and components * @param text the format text to add * @param args optional text format arguments * * * @throws NullPointerException if the message text is {@code null} * @throws IllegalArgumentException if the message text is empty or whitespace * @throws UnsupportedOperationException if the result is unmodifiable * * @see #add(ValidationMessage) * @see #addWarning(String) * * @since 2.6 */ private ValidationResult addError(Object key, String text, Object... args) { checkModifiable(); add(new SimpleValidationMessage(Strings.get(text, args), Severity.ERROR, key)); return this; } /** * Creates and adds a warning message to the list of validation messages * using the given text. * * @param text the warning text to add * * @throws NullPointerException if the message text {@code null} * @throws UnsupportedOperationException if the result is unmodifiable * * @see #add(ValidationMessage) * @see #addError(String) */ public ValidationResult addWarning(String text) { return addWarning((Object) null, text); } /** * Creates and adds a warning message to the list of validation messages * using the given text. * * @param text the warning text to add * @param key the optional message key * * @throws NullPointerException if the message text {@code null} * @throws UnsupportedOperationException if the result is unmodifiable * * @see #add(ValidationMessage) * @see #addError(String) * * @since 2.3 */ public ValidationResult addWarning(String text, Object key) { return addWarning(key, text); } /** * Creates and adds a warning message to the list of validation messages * using the given validation message key, (format) text and optional * format arguments. * * @param key the key used to associate messages and components * @param text the format text to add * @param args optional text format arguments * * * @throws NullPointerException if the message text is {@code null} * @throws IllegalArgumentException if the message text is empty or whitespace * @throws UnsupportedOperationException if the result is unmodifiable * * @see #add(ValidationMessage) * @see #addError(String) * * @since 2.6 */ private ValidationResult addWarning(Object key, String text, Object... args) { checkModifiable(); add(new SimpleValidationMessage(Strings.get(text, args), Severity.WARNING, key)); return this; } /** * Creates and adds an info message to the list of validation messages * using the given text. * * @param text the info text to add * * @throws NullPointerException if the message text {@code null} * @throws UnsupportedOperationException if the result is unmodifiable * * @see #add(ValidationMessage) */ public ValidationResult addInfo(String text) { return addInfo((Object) null, text); } /** * Creates and adds an info message to the list of validation messages * using the given text and key. * * @param text the info text to add * @param key the optional message key * * @throws NullPointerException if the message text {@code null} * @throws UnsupportedOperationException if the result is unmodifiable * * @see #add(ValidationMessage) */ public ValidationResult addInfo(String text, Object key) { return addInfo(key, text); } /** * Creates and adds an info message to the list of validation messages * using the given validation message key, (format) text and optional * format arguments. * * @param key the key used to associate messages and components * @param text the format text to add * @param args optional text format arguments * * * @throws NullPointerException if the message text is {@code null} * @throws IllegalArgumentException if the message text is empty or whitespace * @throws UnsupportedOperationException if the result is unmodifiable * * @see #add(ValidationMessage) * @see #addError(String) * * @since 2.6 */ private ValidationResult addInfo(Object key, String text, Object... args) { checkModifiable(); add(new SimpleValidationMessage(Strings.get(text, args), Severity.INFO, key)); return this; } // Adding Messages ******************************************************** /** * Adds a new ValidationMessage to the list of messages. * * @param validationMessage the message to add * * @return this validation result * * @throws NullPointerException if the message is {@code null} * @throws UnsupportedOperationException if the result is unmodifiable * @throws IllegalArgumentException if the severity is {@code OK} * * @see #addError(String) * @see #addWarning(String) */ public ValidationResult add(ValidationMessage validationMessage) { checkModifiable(); checkNotNull(validationMessage, "The validation message must not be null."); checkArgument(validationMessage.severity() != Severity.OK, "You must not add a validation message with severity OK."); messageList.add(validationMessage); return this; } /** * Adds all messages from the given list to this validation result. * * @param messages the messages to be added * * @throws NullPointerException if the messages list is {@code null} * @throws UnsupportedOperationException if the result is unmodifiable * @throws IllegalArgumentException if the messages list contains * a message with severity {@code OK} * * @see #addAllFrom(ValidationResult) */ public void addAll(List<ValidationMessage> messages) { checkModifiable(); checkNotNull(messages, "The messages list must not be null."); for (ValidationMessage message : messages) { checkArgument(message.severity() != Severity.OK, "You must not add a validation message with severity OK."); } messageList.addAll(messages); } /** * Adds all messages from the given ValidationResult * to the list of messages that this validation result holds. * * @param validationResult the validation result to add messages from * * @throws NullPointerException if the validation result is {@code null} * @throws UnsupportedOperationException if the result is unmodifiable * * @see #addAll(List) */ public void addAllFrom(ValidationResult validationResult) { checkModifiable(); checkNotNull(validationResult, "The validation result to add must not be null."); addAll(validationResult.messageList); } // List Operations ******************************************************** /** * Checks and answers whether this validation result contains no messages. * * @return true if this validation result contains no messages * * @see #hasErrors() * @see #hasWarnings() */ public boolean isEmpty() { return messageList.isEmpty(); } /** * Returns the number of messages in this result. * * @return the number of elements in this list */ public int size() { return messageList.size(); } /** * Checks and answers whether this result contains the specified message. * More formally, returns {@code true} if and only if this result * contains at least one message {@code m} such that * {@code (message.equals(m))}. * * @param message message whose presence in this result is to be tested * @return {@code true} if this result contains the specified message * @throws NullPointerException if the specified message is * {@code null} */ public boolean contains(ValidationMessage message) { return messageList.contains(message); } /** * Returns the message at the specified position in this result. * * @param index index of the message to return. * @return the ValidationMessage at the specified position in this result's * underlying message list. * * @throws IndexOutOfBoundsException if the index is out of range (index * < 0 || index >= size()). * * @since 2.1.0 */ public ValidationMessage get(int index) { return messageList.get(index); } /** * Returns an iterator over the validation messages in this result * in proper sequence. * * @return an iterator over the ValidationMessages in this result * in proper sequence. * * @since 2.1.0 */ @Override public Iterator<ValidationMessage> iterator() { return messageList.iterator(); } /** * Returns an unmodifiable view of the portion of this result between * the specified {@code fromIndex}, inclusive, and {@code toIndex}, * exclusive. * (If {@code fromIndex} and {@code toIndex} are equal, * the returned result is empty.) The returned result is a copy, * so changes in the returned result won't affect this result, * and vice-versa. * * @param fromIndex low end point (inclusive) of the subResult * @param toIndex high end point (exclusive) of the subResult * @return a view of the specified range within this result. * * @throws IndexOutOfBoundsException for an illegal end point index value * (fromIndex < 0 || toIndex > size || fromIndex > toIndex). * * @see #subResult(Object) */ public ValidationResult subResult(int fromIndex, int toIndex) { List<ValidationMessage> messages = messageList.subList(fromIndex, toIndex); return new ValidationResult(messages, false); } /** * Returns an unmodifiable sub result of this result that consists of * all messages that share the specified message key. If the specified key * is {@code null}, this method returns an empty result. * The returned result is a copy, so changes in this result won't affect it. * * @param messageKey the key to look for, can be {@code null} * @return a sub result containing all messages that share the * specified key, or the empty result if the key is {@code null} * * @see #subResult(int, int) */ public ValidationResult subResult(Object messageKey) { if (messageKey == null) { return EMPTY; } List<ValidationMessage> messages = new ArrayList<ValidationMessage>(); for (ValidationMessage message : messageList) { if (messageKey.equals(message.key())) { messages.add(message); } } return new ValidationResult(messages, false); } /** * Returns an unmodifiable sub result of this result that consists of * all messages that share the specified message keys. If the array of keys * is {@code null}, this method returns an empty result. * The returned result is a copy, so changes in this result won't affect it. * * @param messageKeys the keys to look for, can be {@code null} * @return a sub result containing all messages that share the specified * keys, or the empty result if the key array is {@code null} * * @see #subResult(int, int) * * @since 1.4 */ public ValidationResult subResult(Object[] messageKeys) { if (messageKeys == null) { return EMPTY; } List<ValidationMessage> messages = new ArrayList<ValidationMessage>(); for (ValidationMessage message : messageList) { Object messageKey = message.key(); for (Object key : messageKeys) { if (messageKey.equals(key)) { messages.add(message); } } } return new ValidationResult(messages, false); } /** * Creates and returns an unmodifiable Map that maps the message keys * of this validation result to unmodifiable sub results that share the key.<p> * * More formally: * for each key {@code key} in the created map {@code map}, * {@code map.get(key)} returns a {@code ValidationResult} * {@code result}, such that for each {@code ValidationMessage} * {@code message} in {@code result} we have: * {@code message.key().equals(key)}. * * @return a mapping from message key to an associated validation result * that consist only of messages with this key * * @see ValidationMessage#key() */ public Map<Object, ValidationResult> keyMap() { Map<Object, List<ValidationMessage>> messageMap = new HashMap<Object, List<ValidationMessage>>(); for (ValidationMessage message : messageList) { Object key = message.key(); List<ValidationMessage> associatedMessages = messageMap.get(key); if (associatedMessages == null) { associatedMessages = new LinkedList<ValidationMessage>(); messageMap.put(key, associatedMessages); } associatedMessages.add(message); } Map<Object, ValidationResult> resultMap = new HashMap<Object, ValidationResult>(messageMap.size()); for (Map.Entry<Object, List<ValidationMessage>> entry : messageMap.entrySet()) { Object key = entry.getKey(); List<ValidationMessage> messages = entry.getValue(); resultMap.put(key, new ValidationResult(messages, false)); } return Collections.unmodifiableMap(resultMap); } // Requesting Information ************************************************* /** * Returns the highest severity of this result's messages, * {@code Severity.OK} if there are no messages. * * @return the highest severity of this result's messages, * {@code Severity.OK} if there are no messages * * @see #hasMessages() * @see #hasErrors() * @see #hasWarnings() */ public Severity getSeverity() { return getSeverity(messageList); } /** * Checks and answers whether this validation result has messages or not. * * @return true if there are messages, false if not * * @see #getSeverity() * @see #hasErrors() * @see #hasWarnings() */ public boolean hasMessages() { return !isEmpty(); } /** * Checks and answers whether this validation result * contains a message of type {@code ERROR}. * * @return true if there are error messages, false if not * * @see #getSeverity() * @see #hasMessages() * @see #hasWarnings() */ public boolean hasErrors() { return hasSeverity(messageList, Severity.ERROR); } /** * Checks and answers whether this validation result * contains a message of type {@code WARNING}.<p> * * Note that this method checks for warning messages only. * It'll return false, if there are errors but no warnings. * If you want to test whether this result contains * warning and/or errors, use {@code #hasMessages} instead. * * @return true if there are warnings, false if not * * @see #getSeverity() * @see #hasMessages() * @see #hasErrors() */ public boolean hasWarnings() { return hasSeverity(messageList, Severity.WARNING); } /** * Checks and answers whether this validation result * contains a message of type {@code INFO}. * * @return true if there are info messages, false if not * * @see #getSeverity() * @see #hasMessages() * @see #hasErrors() * @see #hasWarnings() */ public boolean hasInfos() { return hasSeverity(messageList, Severity.INFO); } /** * Returns an unmodifiable List of all validation messages. * * @return the {@code List} of all validation messages * * @see #getErrors() * @see #getWarnings() */ public List<ValidationMessage> getMessages() { return Collections.unmodifiableList(messageList); } /** * Returns an unmodifiable List of the validation messages * that indicate errors. * * @return the List of error validation messages * * @see #getMessages() * @see #getWarnings() */ public List<ValidationMessage> getErrors() { return getMessagesWithSeverity(messageList, Severity.ERROR); } /** * Returns an unmodifiable List of the validation messages * that indicate warnings. * * @return the List of validation warnings * * @see #getMessages() * @see #getErrors() */ public List<ValidationMessage> getWarnings() { return getMessagesWithSeverity(messageList, Severity.WARNING); } /** * Returns an unmodifiable List of the validation messages * that indicate informations. * * @return the List of info validation messages * * @see #getMessages() * @see #getErrors() * @see #getWarnings() */ public List<ValidationMessage> getInfos() { return getMessagesWithSeverity(messageList, Severity.INFO); } // Requesting State ******************************************************* /** * Returns if this validation result is modifiable or not. * Can be used to cache data from unmodifiable result. * * @return true if modifiable, false if unmodifiable */ public boolean isModifiable() { return modifiable; } // String Conversion ****************************************************** /** * Returns a string representation of the message list. * * @return a string representation of the message list */ public String getMessagesText() { return getMessagesText(messageList); } /** * Returns a string representation intended for debugging purposes. * * @return a string representation intended for debugging * @see Object#toString() */ @Override public String toString() { if (isEmpty()) { return "Empty ValidationResult"; } StringBuilder builder = new StringBuilder(); builder.append(modifiable ? "Modifiable" : "Unmodifiable"); builder.append(" ValidationResult:"); for (ValidationMessage message : messageList) { builder.append("\n\t").append(message); } return builder.toString(); } // Comparison and Hashing ************************************************* /** * Compares the specified object with this validation result for equality. * Returns {@code true} if and only if the specified object is also * a validation result, both results have the same size, and all * corresponding pairs of validation messages in the two validation results * are <i>equal</i>. (Two validation messages {@code m1} and * {@code m2} are <i>equal</i> if {@code (m1==null ? m2==null : * m1.equals(m2))}.) In other words, two validation results * are defined to be equal if and only if they contain the same * validation messages in the same order.<p> * * This implementation first checks if the specified object is this * validation result. If so, it returns {@code true}; * if not, it checks if the specified object is a validation result. * If not, it returns {@code false}; if so, it checks and returns * if the lists of messages in both results are equal. * * @param o the object to be compared for equality with this validation result. * * @return {@code true} if the specified object is equal * to this validation result. * * @see List#equals(java.lang.Object) * @see Object#equals(java.lang.Object) */ @Override public boolean equals(Object o) { if (o == this) { return true; } if (!(o instanceof ValidationResult)) { return false; } return messageList.equals(((ValidationResult) o).messageList); } /** * Returns the hash code value for this validation result. This * implementation returns the hash from the List of messages. * * @return the hash code value for this validation result. * * @see List#hashCode() * @see Object#hashCode() */ @Override public int hashCode() { return messageList.hashCode(); } // Helper Code ************************************************************ /** * Throws an {@link UnsupportedOperationException} if this * completion manager is unmodifiable. */ private void checkModifiable() { if (!modifiable) { throw new UnsupportedOperationException("This validation result is unmodifiable."); } } /** * Returns the highest severity of this result's messages, * {@code Severity.OK} if there are no messages. * A single validation message can have only the severity * error or warning. Hence, this method returns the error severity * if there's at least one error message; and it returns * the warning severity, otherwise - assuming that there are * no other severities.<p> * * @param messages the List of ValidationMessages to check * @return the highest severity of this result's messages, * {@code Severity.OK} if there are no messages */ private static Severity getSeverity(List<ValidationMessage> messages) { Severity severity = Severity.OK; for (ValidationMessage message : messages) { severity = Severity.max(severity, message.severity()); if (severity.ordinal() == 0) { // we found the maximum severity. break; } } return severity; } /** * Checks and answers whether the given list of validation messages * includes message with the specified Severity. * * @param messages the List of ValidationMessages to check * @param severity the Severity to check * @return true if the given messages list includes error messages, * false if not */ private static boolean hasSeverity(List<ValidationMessage> messages, Severity severity) { for (ValidationMessage message : messages) { if (message.severity() == severity) { return true; } } return false; } /** * Returns an unmodifiable List of ValidationMessage that * that is the sublist of message with the given Severity. * * @param messages the List of ValidationMessages to iterate * @param severity the Severity to look for * @return the sublist of error messages */ private static List<ValidationMessage> getMessagesWithSeverity(List<ValidationMessage> messages, Severity severity) { List<ValidationMessage> errorMessages = new ArrayList<ValidationMessage>(); for (ValidationMessage message : messages) { if (message.severity() == severity) { errorMessages.add(message); } } return Collections.unmodifiableList(errorMessages); } /** * Returns a string representation of the given list of messages. * * @param messages the List of ValidationMessages to iterate * @return a string representation of the given list of messages */ private static String getMessagesText(List<ValidationMessage> messages) { if (messages.isEmpty()) { return "OK"; } StringBuilder builder = new StringBuilder(); for (ValidationMessage message : messages) { if (builder.length() > 0) { builder.append('\n'); } builder.append(message.formattedText()); } return builder.toString(); } }