View Javadoc

1   /*
2    * Copyright 2006-2016 The JGUIraffe Team.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License")
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package net.sf.jguiraffe.transform;
17  
18  import java.util.ArrayList;
19  import java.util.Collection;
20  import java.util.Collections;
21  import java.util.Iterator;
22  
23  import org.apache.commons.lang.StringUtils;
24  
25  /**
26   * <p>
27   * A default implementation of the {@code ValidationResult} interface.
28   * </p>
29   * <p>
30   * Instances of this class store a collection of validation error messages. If
31   * no messages exist with a {@link ValidationMessageLevel} of {@code ERROR}, the
32   * validation result is considered to be valid. It may contain other messages
33   * which are only warning messages.
34   * </p>
35   * <p>
36   * Clients do not create instances directly through the constructor, but use the
37   * nested {@code Builder} class. Once a builder is created, an arbitrary number
38   * of validation error messages can be added. If all messages have been added,
39   * the {@code build()} method creates an immutable instance of
40   * {@code DefaultValidationResult}. This has the advantage that all fields of
41   * {@code DefaultValidationResult} can be made final. Therefore objects of this
42   * class are thread-safe (note that is not true for the {@code Builder}
43   * objects).
44   * </p>
45   * <p>
46   * There is also a constant representing a validation result for a successful
47   * validation. This field can be used in custom validator implementations rather
48   * than creating a new instance of this class.
49   * </p>
50   *
51   * @author Oliver Heger
52   * @version $Id: DefaultValidationResult.java 205 2012-01-29 18:29:57Z oheger $
53   */
54  public final class DefaultValidationResult implements ValidationResult
55  {
56      /** Constant for a validation result object for a successful validation. */
57      public static final ValidationResult VALID = new Builder().build();
58  
59      /** Stores the error messages. */
60      private final Collection<ValidationMessage> messages;
61  
62      /** A flag whether error messages are available. */
63      private final boolean hasErrors;
64  
65      /** A flag whether warning messages are available. */
66      private final boolean hasWarnings;
67  
68      /**
69       * Creates a new instance of {@code DefaultValidationResult} and initializes
70       * it with the collection of error messages obtained from the given {@code
71       * Builder}. Note that this constructor is private, so instances can only be
72       * created through the builder.
73       *
74       * @param builder the {@code Builder}
75       */
76      private DefaultValidationResult(Builder builder)
77      {
78          boolean foundErrors = false;
79          boolean foundWarnings = false;
80  
81          if (builder.messages.isEmpty())
82          {
83              messages = Collections.emptyList();
84          }
85          else
86          {
87              messages = Collections.unmodifiableCollection(builder.messages);
88  
89              for (ValidationMessage vm : messages)
90              {
91                  if (vm.getLevel() == ValidationMessageLevel.ERROR)
92                  {
93                      foundErrors = true;
94                  }
95                  else if (vm.getLevel() == ValidationMessageLevel.WARNING)
96                  {
97                      foundWarnings = true;
98                  }
99              }
100         }
101 
102         hasErrors = foundErrors;
103         hasWarnings = foundWarnings;
104     }
105 
106     /**
107      * Returns a flag whether the validation was successful. This implementation
108      * tests if error messages have been added to this object.
109      *
110      * @return a flag if validation was successful
111      */
112     public boolean isValid()
113     {
114         return !hasErrors;
115     }
116 
117     /**
118      * Returns an unmodifiable collection with error messages.
119      *
120      * @return a collection with the error messages
121      */
122     public Collection<ValidationMessage> getValidationMessages()
123     {
124         return messages;
125     }
126 
127     /**
128      * Returns an unmodifiable collection with the {@code ValidationMessage}
129      * objects with the specified {@code ValidationMessageLevel}.
130      *
131      * @param level the {@code ValidationMessageLevel}
132      * @return a collection with the requested messages
133      */
134     public Collection<ValidationMessage> getValidationMessages(
135             ValidationMessageLevel level)
136     {
137         if (hasMessages(level))
138         {
139             Collection<ValidationMessage> result = new ArrayList<ValidationMessage>(
140                     messages.size());
141             for (ValidationMessage vm : messages)
142             {
143                 if (level == vm.getLevel())
144                 {
145                     result.add(vm);
146                 }
147             }
148 
149             return Collections.unmodifiableCollection(result);
150         }
151 
152         return Collections.emptyList();
153     }
154 
155     /**
156      * Returns a flag whether messages of the specified level are available.
157      * These flags are initialized at construction time, so this method is
158      * pretty efficient.
159      *
160      * @param level the {@code ValidationMessageLevel} in question
161      * @return a flag whether messages with this level are available
162      */
163     public boolean hasMessages(ValidationMessageLevel level)
164     {
165         if (level == null)
166         {
167             return false;
168         }
169 
170         switch (level)
171         {
172         case ERROR:
173             return hasErrors;
174         default:
175             return hasWarnings;
176         }
177     }
178 
179     /**
180      * Compares this object to another one. Two validation result objects are
181      * considered equal if and only if they contain the same error messages.
182      *
183      * @param obj the object to compare to
184      * @return a flag whether the objects are equal
185      */
186     @Override
187     public boolean equals(Object obj)
188     {
189         if (obj == this)
190         {
191             return true;
192         }
193         if (!(obj instanceof DefaultValidationResult))
194         {
195             return false;
196         }
197 
198         DefaultValidationResult c = (DefaultValidationResult) obj;
199         // obviously equals() does not work for unmodifiable collections, so we
200         // have to check the elements ourselves
201         if (messages.size() != c.messages.size())
202         {
203             return false;
204         }
205         Iterator<ValidationMessage> it = c.messages.iterator();
206         for (ValidationMessage vm : messages)
207         {
208             if (!vm.equals(it.next()))
209             {
210                 return false;
211             }
212         }
213         return true;
214     }
215 
216     /**
217      * Returns a hash code for this object.
218      *
219      * @return a hash code
220      */
221     @Override
222     public int hashCode()
223     {
224         final int seed = 17;
225         final int factor = 31;
226 
227         int result = seed;
228         for (ValidationMessage vm : messages)
229         {
230             result = factor * result + vm.hashCode();
231         }
232 
233         return result;
234     }
235 
236     /**
237      * Returns a string representation for this object. This string will also
238      * contain the string representations for all contained error messages.
239      *
240      * @return a string for this object
241      */
242     @Override
243     public String toString()
244     {
245         StringBuilder buf = new StringBuilder();
246         buf.append(getClass().getName()).append('@');
247         buf.append(System.identityHashCode(this));
248         buf.append("[ ");
249 
250         if (isValid())
251         {
252             buf.append("VALID");
253         }
254         else
255         {
256             buf.append("messages = ");
257             buf.append(StringUtils.join(messages, ", "));
258         }
259 
260         buf.append(" ]");
261         return buf.toString();
262     }
263 
264     /**
265      * Combines two validation result objects to a combined result. If one of
266      * the passed in result objects is invalid, the resulting object will also
267      * be invalid. It will contain the union of all error messages. A new
268      * {@code ValidationResult} object is created only if necessary: If one of
269      * the arguments is valid and has no warning messages, the other argument is
270      * returned. The same is true for <b>null</b> arguments. This method returns
271      * <b>null</b> only if both arguments are <b>null</b>.
272      *
273      * @param vr1 the first validation result object
274      * @param vr2 the second validation result object
275      * @return a combined validation result
276      */
277     public static ValidationResult merge(ValidationResult vr1,
278             ValidationResult vr2)
279     {
280         if (isPureValid(vr1))
281         {
282             return vr2;
283         }
284         else if (isPureValid(vr2))
285         {
286             return vr1;
287         }
288 
289         // both are non valid => create a combined result
290         Builder builder = new Builder();
291         return builder.addValidationMessages(vr1.getValidationMessages())
292                 .addValidationMessages(vr2.getValidationMessages()).build();
293     }
294 
295     /**
296      * Creates a {@code ValidationMessage} object for the specified error
297      * message. This is a convenience method that obtains the
298      * {@link ValidationMessageHandler} from the {@code TransformerContext} and
299      * obtains a {@link ValidationMessage} for the specified parameters.
300      *
301      * @param context the {@code TransformerContext} (must not be <b>null</b>)
302      * @param key the key of the error message
303      * @param params optional parameters for the error message
304      * @return a {@code ValidationResult} object with the specified message
305      * @throws IllegalArgumentException if the {@code TransformerContext} is
306      *         <b>null</b>
307      */
308     public static ValidationMessage createValidationMessage(
309             TransformerContext context, String key, Object... params)
310     {
311         if (context == null)
312         {
313             throw new IllegalArgumentException(
314                     "TransformerContext must not be null!");
315         }
316 
317         ValidationMessageHandler msgHandler = context
318                 .getValidationMessageHandler();
319         return msgHandler.getValidationMessage(context, key, params);
320     }
321 
322     /**
323      * Creates a {@code ValidationResult} object with the specified error
324      * message. This is a convenience method for the frequent case that a
325      * {@code ValidationResult} object with exactly one error message has to be
326      * created. It obtains the {@link ValidationMessageHandler} from the {@code
327      * TransformerContext}, obtains a {@link ValidationMessage} for the
328      * specified parameters, and creates a {@code ValidationResult} object with
329      * exactly this message.
330      *
331      * @param context the {@code TransformerContext} (must not be <b>null</b>)
332      * @param key the key of the error message
333      * @param params optional parameters for the error message
334      * @return a {@code ValidationResult} object with this error message
335      * @throws IllegalArgumentException if the {@code TransformerContext} is
336      *         <b>null</b>
337      */
338     public static ValidationResult createValidationErrorResult(
339             TransformerContext context, String key, Object... params)
340     {
341         return new DefaultValidationResult.Builder().addValidationMessage(
342                 createValidationMessage(context, key, params)).build();
343     }
344 
345     /**
346      * Helper method for checking whether a validation result can be considered
347      * "pure" valid. This means that there are no messages at all. A null object
348      * is also pure valid!
349      *
350      * @param vr the validation result to check
351      * @return a flag whether this result object is pure valid
352      */
353     private static boolean isPureValid(ValidationResult vr)
354     {
355         return vr == null
356                 || (vr.isValid() && !vr
357                         .hasMessages(ValidationMessageLevel.WARNING));
358     }
359 
360     /**
361      * <p>
362      * A <em>builder</em> class for creating instances of {@code
363      * DefaultValidationResult}.
364      * </p>
365      * <p>
366      * In order to create a new {@code DefaultValidationResult} instance, create
367      * a builder, call its {@code addErrorMessage()} methods, and finally invoke
368      * the {@code build()} method. This can look as follows:
369      *
370      * <pre>
371      * DefaultValidationResult vres = new DefaultValidationResult.Builder()
372      *         .addErrorMessage(msg1).addErrorMessage(msg2).build();
373      * </pre>
374      *
375      * </p>
376      */
377     public static class Builder
378     {
379         /** A collection with the validation messages added so far. */
380         private Collection<ValidationMessage> messages;
381 
382         /**
383          * Creates a new instance of {@code Builder}
384          */
385         public Builder()
386         {
387             reset();
388         }
389 
390         /**
391          * Adds an object with a validation message to this instance. If the
392          * message has the level {@code ERROR}, this also
393          * means that the validation failed.
394          *
395          * @param msg the message object (must not be <b>null</b>)
396          * @return a reference to this builder
397          * @throws IllegalArgumentException if the message is <b>null</b>
398          */
399         public Builder addValidationMessage(ValidationMessage msg)
400         {
401             if (msg == null)
402             {
403                 throw new IllegalArgumentException(
404                         "Error message must not be null!");
405             }
406 
407             messages.add(msg);
408             return this;
409         }
410 
411         /**
412          * Adds all messages stored in the given collection to this object.
413          *
414          * @param msgs the collection with the messages to add (must not be
415          *        <b>null</b>)
416          * @return a reference to this builder
417          * @throws IllegalArgumentException if the collection with the messages
418          *         is <b>null</b> or one of its elements is <b>null</b>
419          */
420         public Builder addValidationMessages(Collection<ValidationMessage> msgs)
421         {
422             if (msgs == null)
423             {
424                 throw new IllegalArgumentException(
425                         "Message collection must not be null!");
426             }
427 
428             Collection<ValidationMessage> copy = new ArrayList<ValidationMessage>(
429                     msgs);
430             for (ValidationMessage m : copy)
431             {
432                 if (m == null)
433                 {
434                     throw new IllegalArgumentException(
435                             "Message collection contains a null element!");
436                 }
437             }
438 
439             messages.addAll(copy);
440             return this;
441         }
442 
443         /**
444          * Returns the {@code DefaultValidationResult} defined by this builder.
445          *
446          * @return the {@code DefaultValidationResult} created by this builder
447          */
448         public DefaultValidationResult build()
449         {
450             DefaultValidationResult res = new DefaultValidationResult(this);
451             reset();
452             return res;
453         }
454 
455         /**
456          * Resets this builder. After that definition of a new validation result
457          * object can be started.
458          */
459         public final void reset()
460         {
461             messages = new ArrayList<ValidationMessage>();
462         }
463     }
464 }