001    package edu.nrao.sss.validation;
002    
003    import java.util.ArrayList;
004    import java.util.HashMap;
005    import java.util.List;
006    import java.util.Map;
007    
008    /**
009     * The entry point for validating model objects.
010     * <p>
011     * Most clients need use only the {@link #validate(Object, ValidationPurpose)}
012     * method.  A typical usage pattern is:</p>
013     * <div style="margin-left: 2em; margin-right: 18em; border-style: double;
014     *             color:darkred; background-color:#F9F9F9; font-weight:bold">
015     * <pre>
016     *   ValidationManager myMgr = new ValidationManager();
017     *   
018     *   if (!myMgr.validate(newCar, ValidationPurpose.CERTIFY_READY_TO_USE))
019     *     communicate(myMgr.getFailures());</pre></div>
020     * <p>
021     * <b><u>Two Different Failure Lists</u></b>
022     * <br/>
023     * In the original implementation this class had one list of failures.
024     * If the <tt>validate</tt> method returned <i>true</i> (i.e., success),
025     * the <tt>getFailures()</tt> list would be empty.  If it returned
026     * <i>false</i>, it would contain one or more validation failures.
027     * This can cause problems, though, when the only failures in the list
028     * are those that are not intended for display.</p>
029     * <p>
030     * To remedy this problem,
031     * this manager now holds two lists of failures -- one for displayable
032     * failures and one for nondisplayable failures.  If the <tt>validate</tt>
033     * method returns <i>true</i>, both of these lists will be empty.  However,
034     * if it returns <i>false</i>, one, the other, or both may have failures,
035     * with a minimum of one failure in the combined lists.  This means that
036     * clients, upon seeing a <i>false</i> return value, must now see if there
037     * are any displayable failures before committing to interaction with the
038     * user.</p>
039     * <p>
040     * <b><u>How the Manager Finds Validators</u></b>
041     * <br/>
042     * The <tt>ValidationManager</tt> finds a validator for an instance of a given
043     * target validation class by either referring to a validator registered by
044     * one of its clients or by relying on its default search mechanism.
045     * <p>
046     * Clients may use one of two methods to register validators for a particular
047     * class of validation target: {@link #registerValidator(Class, Class)} and
048     * {@link #registerValidator(Validator, Class)}.  The first method associates
049     * a class of validator with the class of validation target.  The first time
050     * such a target is to be validated, the manager instantiates an instance of
051     * the validator class and uses it.  It will use that same instance each time
052     * validation is requested on that class of validation target, unless the
053     * {@link #unregisterValidator(Class)} method has been called on that
054     * class of validation target, or if a specific validator has been specified
055     * via the second method.  This second method associates a particular instance
056     * of a validator with the given class of validation target.  This validator
057     * may have been preconfigured by the client in some special way.  The manager
058     * will continue to use that validator until either the <tt>unregister</tt>
059     * method is called or a new instance of a validator is registered.</p>
060     * <p>
061     * If there is no instance or class of validator associated with a class of
062     * validation target, the manager will create and use a validator based on this
063     * naming convention: if the class of the validation target is
064     * <tt>abc.def.PqrStu</tt>, the validator class sought is
065     * <tt>abc.def.PqrStuValidator</tt>.  If this class cannot be instantiated,
066     * the same logic will be applied to the superclass of <tt>abc.def.PqrStu</tt>
067     * until a validator is found.  If a validator cannot be found for any of
068     * the classes on the inheritance tree,
069     * the return value of the <tt>validate</tt> method will be <i>false</i>
070     * and the list of failures will contain one failure that notes the lack
071     * of validator for the validation target class.</p>
072     * <p>
073     * <b>Version Info:</b>
074     * <table style="margin-left:2em">
075     *   <tr><td>$Revision: 1707 $</td></tr>
076     *   <tr><td>$Date: 2008-11-14 10:23:59 -0700 (Fri, 14 Nov 2008) $</td></tr>
077     *   <tr><td>$Author: dharland $</td></tr>
078     * </table></p>
079     * 
080     * @author David M. Harland
081     * @since 2007-02-02
082     */
083    public class ValidationManager
084    {
085      private Map<Class<?>, Class<? extends Validator>> validatorClassMap;
086      private Map<Class<?>, Validator>                  validatorMap;
087      
088      //These pertain to the most recently run validation
089      private Object                  validationTarget;
090      private List<ValidationFailure> failures;
091      private List<ValidationFailure> nonDisplayableFailures;
092      
093      private boolean validationInProgress;
094      
095      /** Creates a new instance. */
096      public ValidationManager()
097      {
098        validatorClassMap      = new HashMap<Class<?>, Class<? extends Validator>>();
099        validatorMap           = new HashMap<Class<?>, Validator>();
100        failures               = new ArrayList<ValidationFailure>();
101        nonDisplayableFailures = new ArrayList<ValidationFailure>();
102        validationInProgress   = false;
103      }
104      
105      /**
106       * Validates the target object for the given purpose.
107       * <p>
108       * If all tests were passed, this method returns <i>true</i>.
109       * If this method returns <i>false</i>, the list returned
110       * by {@link #getFailures()} will contain one or more
111       * {@link ValidationFailure}s.</p>
112       * 
113       * @param target the object to be validated.
114       * 
115       * @param purpose the reason for the validation.  Different purposes trigger
116       *                different validations and/or different levels of the same
117       *                validations.
118       *                
119       * @return <i>true</i> if {@code target} passed all the tests required for
120       *         the given {@code purpose}, <i>false</i> otherwise.
121       */
122      public boolean validate(Object target, ValidationPurpose purpose)
123      {
124        validationInProgress = true;
125        validationTarget     = target;
126        
127        failures.clear();
128        nonDisplayableFailures.clear();
129        
130        Validator validator = getValidator(target.getClass());
131        
132        if (validator != null)
133        {
134          //TODO EVL-699 asks us to look at this idea of setting mgr
135          //     first to self and then to null.
136          validator.setManager(this);
137          failures.addAll(validator.validate(target, purpose));
138          validator.setManager(null);
139        }
140        
141        boolean success = failures.size() == 0;
142        
143        reapportionFailures();
144        
145        validationInProgress = false;
146        
147        return success;
148      }
149      
150      /**
151       * Moves all failures that have no user-displayable messages from
152       * the failures variable to the nonDisplayableFailures messages.
153       */
154      private void reapportionFailures()
155      {
156        for (ValidationFailure failure : failures)
157          if (!failure.isDisplayable())
158            nonDisplayableFailures.add(failure);
159        
160        failures.removeAll(nonDisplayableFailures);
161      }
162    
163      /**
164       * Configures this manager to validate instances of {@code targetClass}
165       * with {@code validator}.
166       * <p>
167       * Note that this manager will hold a reference to {@code validator}.
168       * This means that any changes made to it after calling this method
169       * will be reflected in this object.  This same validator will be
170       * returned via the {@link #getValidator(Class)} method.</p>
171       * <p>
172       * If either parameter is <i>null</i>, this method does nothing.</p>
173       * 
174       * @param validator the validator to use on instances of {@code targetClass}.
175       * 
176       * @param targetClass the type of objects on which to use {@code validator}.
177       */
178      public void registerValidator(Validator validator, Class<?> targetClass)
179      {
180        if (validator != null && targetClass != null)
181        {
182          validatorMap.put(targetClass, validator);
183          validatorClassMap.put(targetClass, validator.getClass());
184        }
185      }
186    
187      /**
188       * Configures this manager to validate instances of {@code targetClass}
189       * with an instance of {@code validator}.
190       * <p>
191       * The next time a validator is needed of an object of type
192       * {@code targetClass} a new instance of {@code validatorClass} will
193       * be created and used.</p>
194       * <p>
195       * If either parameter is <i>null</i>, this method does nothing.</p>
196       * 
197       * @param validatorClass the type of validator to use on objects of
198       *                       type {@code targetClass}.
199       *                       
200       * @param targetClass the type of objects on which to use an instance of
201       *                    {@code validatorClass}.
202       */
203      public void registerValidator(Class<? extends Validator> validatorClass,
204                                    Class<?>                   targetClass)
205      {
206        if (validatorClass != null && targetClass != null)
207        {
208          validatorMap.remove(validatorClass);
209          validatorClassMap.put(targetClass, validatorClass);
210        }
211      }
212      
213      /**
214       * Unregisters the validator associated with {@code targetClass}.
215       * After this call no validator instance or validator class will
216       * be associated with {@code targetClass}.
217       * 
218       * @param targetClass a class of objects subject to validation.
219       * 
220       * @return the validator registered for use with instances of
221       *         {@code targetClass} prior to calling this method.
222       *         If no validator or validator class had been so
223       *         registered, <i>null</i> is returned. 
224       */
225      public Validator unregisterValidator(Class<?> targetClass)
226      {
227        Validator oldValidator = validatorMap.remove(targetClass);
228        
229        if (oldValidator == null)
230          oldValidator = instantiateValidator(targetClass);
231        
232        validatorClassMap.remove(targetClass);
233        
234        return oldValidator;
235      }
236      
237      //============================================================================
238      // FAILURES
239      //============================================================================
240      
241      /**
242       * Returns a copy of the list of displayable validation failures triggered
243       * by the most validation.  This list contains only those failures that can
244       * be shown to external users, it excludes failures that have no displayable
245       * message. 
246       * 
247       * @return a copy of the list of displayable validation failures triggered
248       *         by the most recent validation.
249       *         
250       * @see #getNonDisplayableFailures()
251       */
252      public List<ValidationFailure> getFailures()
253      {
254        //If ValidationFailure is made publicly mutable,
255        //change logic to return list of clones.
256        
257        return new ArrayList<ValidationFailure>(failures);
258      }
259      
260      /**
261       * Returns a copy of the list of nondisplayable validation failures triggered
262       * by the most validation.  This list contains only those failures that have
263       * no displayable message.  These failures should, though, have a debug
264       * message.
265       * 
266       * @return a copy of the list of nondisplayable validation failures triggered
267       *         by the most recent validation.
268       *         
269       * @see #getFailures()
270       */
271      public List<ValidationFailure> getNonDisplayableFailures()
272      {
273        //If ValidationFailure is made publicly mutable,
274        //change logic to return list of clones.
275        
276        return new ArrayList<ValidationFailure>(nonDisplayableFailures);
277      }
278    
279      //============================================================================
280      // FINDING & CREATING VALIDATOR INSTANCES
281      //============================================================================
282    
283      /**
284       * Returns the validator that this manager will use for objects of type
285       * {@code targetClass}.
286       * <p>
287       * The validator returned is the one actually held by this manager.
288       * This means that if you alter the properties of the returned validator,
289       * those changes will be reflected in this manager.
290       * If this manager holds no validator for {@code targetClass},
291       * it traverses the target class's hierarchy tree to the top.
292       * If no validator is found for any of the superclasses,
293       * <i>null</i> is returned.</p>
294       * 
295       * @param targetClass the class of object for which a validator is desired.
296       * 
297       * @return a validator for {@code targetClass}, or <i>null</i> if this manager
298       *         cannot find one.
299       */
300      public Validator getValidator(Class<?> targetClass)
301      {
302        Validator validator = null;
303    
304        //Look for a validator for targetClass.
305        //If not found, traverse its hierarchy to the top.
306        while ((validator == null) && (targetClass != null))
307        {
308          validator   = fetchOrMakeValidator(targetClass);
309          targetClass = targetClass.getSuperclass();
310        }
311        
312        //If we couldn't find or build one, create and store a failure
313        if ((validator == null) && validationInProgress)
314          failures.add(validatorNotFoundFailure());
315        
316        return validator;
317      }
318    
319      /** Fetches or makes a validator for targetClass.  Can return null. */
320      private Validator fetchOrMakeValidator(Class<?> targetClass)
321      {
322        Validator validator = validatorMap.get(targetClass);
323    
324        //If we didn't have a validator already stored, try to build one & store it
325        if (validator == null)
326          validator = instantiateValidator(targetClass);
327        
328        return validator;
329      }
330      
331      /** Tries to create a validator for {@code target}. */
332      private Validator instantiateValidator(Class<?> target)
333      {
334        Validator validator = null;
335        
336        Class<? extends Validator> vc = validatorClassMap.get(target);
337        
338        //If we didn't have a class already registered, try to build one
339        if (vc == null)
340          vc = makeValidatorClass(target);
341        
342        if (vc != null)
343        {
344          try
345          {
346            validator = vc.newInstance();         //Try to make a new validator
347            validator.setManager(this);           //If successful, set manager &
348            validatorMap.put(target, validator);  //save in map.
349          }
350          catch (Exception ex)
351          {
352            //leave validator = null, which we'll return
353          }
354        }
355        
356        return validator;
357      }
358      
359      /** Tries to create a Class that represents a validator for {@code target}. */
360      private Class<? extends Validator> makeValidatorClass(Class<?> target)
361      {
362        Class<? extends Validator> vc = null;
363        
364        try
365        {
366          //Try to make class ClassNameValidator
367          vc = Class.forName(target.getName() + "Validator")
368                    .asSubclass(Validator.class);
369          
370          validatorClassMap.put(target, vc);  //If successful, save in map
371        }
372        catch (ClassNotFoundException ex)
373        {
374          //leave vc=null, which we'll return
375        }
376    
377        return vc;
378      }
379      
380      /**
381       * Returns a validation failure indicating that a validator could
382       * not be found for {@code targetClass}.
383       * 
384       * NOTE: This method should be called ONLY while validation is
385       *       in progress because it uses the validationTarget for info.
386       */
387      private ValidationFailure validatorNotFoundFailure()
388      {
389        String debugMsg = "Could not find validator for class " +
390                          validationTarget.getClass().getName();
391        
392        return new ValidationFailure("",
393                                     debugMsg,
394                                     FailureSeverity.WARNING,
395                                     validationTarget,
396                                     "",
397                                     "no validator");
398      }
399      
400      /**
401       * Returns a validation failure indicating that a validator could
402       * not be found for {@code targetClass}.
403    
404      private ValidationFailure validatorNotFoundFailure(Class targetClass)
405      {
406        String debugMsg = "Could not find validator for class " +
407                          targetClass.getName();
408        
409        return new ValidationFailure("",
410                                     debugMsg,
411                                     FailureSeverity.WARNING,
412                                     null,
413                                     "");
414      }
415      */
416    }