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 }