classes/hirondelle/web4j/model/Check.java
changeset 0 3060119b1292
equal deleted inserted replaced
-1:000000000000 0:3060119b1292
       
     1 package hirondelle.web4j.model;                    
       
     2    
       
     3 import java.util.*;
       
     4 import java.util.logging.Logger;
       
     5 import java.util.regex.*;
       
     6 import java.math.BigDecimal;
       
     7 
       
     8 import hirondelle.web4j.BuildImpl;
       
     9 import hirondelle.web4j.security.SafeText;
       
    10 import hirondelle.web4j.security.SpamDetector;
       
    11 import hirondelle.web4j.util.Util;
       
    12 import hirondelle.web4j.util.WebUtil;
       
    13 import static hirondelle.web4j.util.Consts.PASSES;
       
    14 import static hirondelle.web4j.util.Consts.FAILS;
       
    15 import hirondelle.web4j.util.Args;
       
    16 
       
    17 /**
       
    18  <span class="highlight">Returns commonly needed {@link Validator} objects.</span>
       
    19  
       
    20  <P>In general, the number of possible validations is <em>very</em> large. It is not appropriate 
       
    21  for a framework to attempt to implement <em>all</em> possible validations. Rather, a framework should 
       
    22  provide the most common validations, and allow the application programmer to extend 
       
    23  the validation mechanism as needed.
       
    24  
       
    25  <P>Validations are important parts of your program's logic. 
       
    26  Using tools such as JUnit to test your validation code is highly recommended. 
       
    27  Since your Model Objects have no dependencies on heavyweight objects, they're almost always easy to test.
       
    28   
       
    29  <P>If a specific validation is not provided here, other options include : 
       
    30 <ul>
       
    31  <li>performing the validation directly in the Model Object, without using <tt>Check</tt> or 
       
    32  a {@link Validator}
       
    33  <li>create a new {@link Validator}, and pass it to either {@link #required(Object, Validator...)}
       
    34  or {@link #optional(Object, Validator...)}. This option is especially attractive when it will eliminate 
       
    35  code repetition.  
       
    36  <li>subclassing this class, and adding new <tt>static</tt> methods
       
    37  </ul>
       
    38  
       
    39  <P>The {@link #range(long, long)}, {@link #min(long)} and {@link #max(long)} methods return {@link Validator}s 
       
    40  that perform checks on a <tt>long</tt> value. <span class='highlight'>The <em>source</em> of the <tt>long</tt> value varies 
       
    41  according to the type of <tt>Object</tt> passed to the {@link Validator}</span>, and is taken as follows 
       
    42  (<tt>int</tt> is internally converted to <tt>long</tt> when necessary) : 
       
    43  <ul>
       
    44  <li>{@link Integer#intValue()}
       
    45  <li>{@link Long#longValue()} 
       
    46  <li>length of a trimmed {@link String} having content; the same is applied to {@link Id#toString()},
       
    47  {@link Code#getText()}, and {@link SafeText#getRawString()}.
       
    48  <li>{@link Collection#size()}
       
    49  <li>{@link Map#size()}
       
    50  <li>{@link Date#getTime()} - underlying millisecond value 
       
    51  <li>{@link Calendar#getTimeInMillis()} - underlying millisecond value 
       
    52  <li>any other class will cause an exception to be thrown by {@link #min(long)} and {@link #max(long)} 
       
    53  </ul>
       
    54 
       
    55  <P>The {@link #required(Object)}, {@link #required(Object, Validator...)} and {@link #optional(Object, Validator...)} 
       
    56  methods are important, and are separated out as distinct validations. <span class='highlight'>In addition, the 
       
    57  required/optional character of a field is always the <em>first</em> validation performed</span> (see examples below).
       
    58 
       
    59  <P><span class="highlight">In general, it is highly recommended that applications 
       
    60  aggressively perform all possible validations.</span>  
       
    61  
       
    62  <P> Note that when validation is performed in a Model Object, then it will apply both to objects created from 
       
    63  user input, and to objects created from a database <tt>ResultSet</tt>. 
       
    64  
       
    65  <P><b>Example 1</b><br>
       
    66  Example of a required field in a Model Object (that is, the field is of any type, and 
       
    67  must be non-<tt>null</tt>) :
       
    68  <PRE>
       
    69 if ( ! Check.required(fStartDate) ) {
       
    70   ex.add("Start Date is Required.");
       
    71 }
       
    72  </PRE>
       
    73 
       
    74   
       
    75  <P><b>Example 2</b><br>
       
    76  Example of a required <em>text</em> field, which must have visible content 
       
    77  (as in {@link Util#textHasContent(String)}) :
       
    78  <PRE>
       
    79 if ( ! Check.required(fTitle) ) {
       
    80   ex.add("Title is required, and must have content.");
       
    81 }
       
    82  </PRE>
       
    83   
       
    84  <P><b>Example 3</b><br>
       
    85  Example of a required text field, whose length must be in the range <tt>2..50</tt> :
       
    86  <PRE>
       
    87 if ( ! Check.required(fTitle, Check.range(2,50)) ) {
       
    88   ex.add("Title is required, and must have between 2 and 50 characters.");
       
    89 }
       
    90  </PRE>
       
    91  
       
    92  <P><b>Example 4</b><br>
       
    93  Example of an optional <tt>String</tt> field that matches the format '<tt>1234-5678</tt>' :
       
    94  <PRE>
       
    95 //compile the pattern once, when the class is loaded
       
    96 private static final Pattern fID_PATTERN = Pattern.compile("(\\d){4}-(\\d){4}");
       
    97 ...
       
    98 if ( ! Check.optional(fSomeId, Check.pattern(fID_PATTERN)) ) {
       
    99   ex.add("Id is optional, and must have the form '1234-5678'.");
       
   100 }
       
   101  </PRE>
       
   102 
       
   103  <P><b>Example 5</b><br>
       
   104  The initial <tt>!</tt> negation operator is easy to forget. Many will prefer a more explicit style, which seems 
       
   105  to be more legible :
       
   106  <PRE>
       
   107 import static hirondelle.web4j.util.Consts.FAILS;
       
   108 ...
       
   109 if ( FAILS == Check.required(fStartDate) ) {
       
   110   ex.add("Start Date is Required.");
       
   111 }
       
   112  </PRE>
       
   113  
       
   114  <P>Here is one style for implementing custom validations for your application :
       
   115  <PRE>
       
   116 //Checks that a person's age is in the range 0..150
       
   117 public static Validator ageRange(){
       
   118   class CheckAge implements Validator {
       
   119     public boolean isValid(Object aObject) {
       
   120       Integer age = (Integer)aObject;
       
   121       return 0 <= age <= 150; 
       
   122     }
       
   123   }
       
   124   return new CheckAge();
       
   125 }
       
   126  </PRE>
       
   127 */
       
   128 public class Check {
       
   129   
       
   130   /**
       
   131    Return <tt>true</tt> only if <tt>aObject</tt> is non-<tt>null</tt>.
       
   132    
       
   133    <P><em><tt>String</tt> and {@link SafeText} objects are a special case</em> : instead of just 
       
   134    being non-<tt>null</tt>, they must have content according to {@link Util#textHasContent(String)}.
       
   135    
       
   136    @param aObject possibly-null field of a Model Object.
       
   137   */
       
   138   public static boolean required(Object aObject){
       
   139     boolean result = FAILS;
       
   140     if ( isText(aObject) ) {
       
   141        result = Util.textHasContent(getText(aObject));
       
   142     }
       
   143     else { 
       
   144       result = (aObject != null);
       
   145     }
       
   146     return result;
       
   147   }
       
   148 
       
   149   /**
       
   150    Return <tt>true</tt> only if <tt>aObject</tt> satisfies {@link #required(Object)}, 
       
   151    <em>and</em> it passes all given validations
       
   152     
       
   153    @param aObject possibly-null field of a Model Object.
       
   154   */ 
       
   155   public static boolean required(Object aObject, Validator... aValidators){
       
   156     boolean result = PASSES;
       
   157     if ( ! required(aObject) ) {
       
   158       result = FAILS;
       
   159     } 
       
   160     else { 
       
   161       result = passesValidations(aObject, aValidators);
       
   162     }
       
   163     return result;
       
   164   }
       
   165   
       
   166   /**
       
   167    Return <tt>true</tt> only if <tt>aObject</tt> is <tt>null</tt>, OR if <tt>aObject</tt> is non-<tt>null</tt> 
       
   168    and passes all validations.
       
   169    
       
   170    <P><em><tt>String</tt> and {@link SafeText} objects are a special case</em> : instead of just 
       
   171    being non-<tt>null</tt>, they must have content according to {@link Util#textHasContent(String)}.
       
   172    
       
   173    @param aObject possibly-null field of a Model Object.
       
   174   */
       
   175   public static boolean optional(Object aObject, Validator... aValidators){
       
   176     boolean result = PASSES;
       
   177     if ( aObject != null ){
       
   178       if( isText(aObject) ) {
       
   179         result = Util.textHasContent(getText(aObject)) && passesValidations(aObject, aValidators);
       
   180       }        
       
   181       else {
       
   182         result = passesValidations(aObject, aValidators);
       
   183       }
       
   184     }
       
   185     return result;
       
   186   }
       
   187   
       
   188   /**
       
   189    Return a {@link Validator} to check that all passed booleans are <tt>true</tt>. 
       
   190    Note that the single parameter is a sequence parameter, so you may pass in many booleans, not just one.
       
   191    <P>This is a bizarre method, but it's actually useful. It allows checks on an object's state to be treated as any other check.   
       
   192   */
       
   193   public static Validator isTrue(Boolean... aPredicates){
       
   194     class AllTrue implements Validator{
       
   195       AllTrue(Boolean... aPreds){
       
   196         fPredicates = aPreds;
       
   197       }
       
   198       public boolean isValid(Object aObject) {
       
   199         //aObject is ignored here
       
   200         boolean result = true;
       
   201         for (Boolean predicate: fPredicates){
       
   202           result = result && predicate;
       
   203         }
       
   204         return result;
       
   205       };
       
   206       private Boolean[] fPredicates;
       
   207     }
       
   208     return new AllTrue(aPredicates);
       
   209   }
       
   210   
       
   211   /**
       
   212    Return a {@link Validator} to check that all passed booleans are <tt>false</tt>. 
       
   213    Note that the single parameter is a sequence parameter, so you may pass in many booleans, not just one.
       
   214    <P>This is a bizarre method, but it's actually useful. It allows checks on an object's state to be treated as any other check.   
       
   215   */
       
   216   public static Validator isFalse(Boolean... aPredicates){
       
   217     class AllFalse implements Validator{
       
   218       AllFalse(Boolean... aPreds){
       
   219         fPredicates = aPreds;
       
   220       }
       
   221       public boolean isValid(Object aObject) {
       
   222         //aObject is ignored here
       
   223         boolean result = true;
       
   224         for (Boolean predicate: fPredicates){
       
   225           result = result && !predicate;
       
   226         }
       
   227         return result;
       
   228       };
       
   229       private Boolean[] fPredicates;
       
   230     }
       
   231     return new AllFalse(aPredicates);
       
   232   }
       
   233   
       
   234   /**
       
   235    Return a {@link Validator} to check that a field's value is greater than or equal to <tt>aMinimumValue</tt>. 
       
   236    See class comment for the kind of objects which may be checked by the returned {@link Validator}.
       
   237   */
       
   238   public static Validator min(final long aMinimumValue){
       
   239     class Minimum implements Validator {
       
   240       Minimum(long aMinValue){
       
   241         fMinValue = aMinValue;
       
   242       }
       
   243       public boolean isValid(Object aObject){
       
   244         long value = getValueAsLong(aObject);
       
   245         return value >= fMinValue;
       
   246       }
       
   247       private final long fMinValue;
       
   248     }
       
   249     return new Minimum(aMinimumValue);
       
   250   }
       
   251   
       
   252   /**
       
   253    Return a {@link Validator} to check that a {@link BigDecimal} field is greater than or equal 
       
   254    to <tt>aMinimumValue</tt>. 
       
   255   */
       
   256   public static Validator min(final BigDecimal aMinimumValue){
       
   257     class Minimum implements Validator {
       
   258       Minimum(BigDecimal aMinValue){
       
   259         fMinValue = aMinValue;
       
   260       }
       
   261       public boolean isValid(Object aObject){
       
   262         BigDecimal value = (BigDecimal)aObject;
       
   263         return value.compareTo(fMinValue) >= 0;
       
   264       }
       
   265       private final BigDecimal fMinValue;
       
   266     }
       
   267     return new Minimum(aMinimumValue);
       
   268   }
       
   269 
       
   270   /**
       
   271    Return a {@link Validator} to check that a {@link Decimal} amount is greater than or equal 
       
   272    to <tt>aMinimumValue</tt>. This methods allows comparisons between 
       
   273    <tt>Money</tt> objects of different currency.
       
   274   */
       
   275   public static Validator min(final Decimal aMinimumValue){
       
   276     class Minimum implements Validator {
       
   277       Minimum(Decimal aMinValue){
       
   278         fMinValue = aMinValue;
       
   279       }
       
   280       public boolean isValid(Object aObject){
       
   281         Decimal value = (Decimal)aObject;
       
   282         return value.gteq(fMinValue);
       
   283       }
       
   284       private final Decimal fMinValue;
       
   285     }
       
   286     return new Minimum(aMinimumValue);
       
   287   }
       
   288   
       
   289   /**
       
   290    Return a {@link Validator} to check that a {@link DateTime} is greater than or equal 
       
   291    to <tt>aMinimumValue</tt>. 
       
   292   */
       
   293   public static Validator min(final DateTime aMinimumValue){
       
   294     class Minimum implements Validator {
       
   295       Minimum(DateTime aMinValue){
       
   296         fMinValue = aMinValue;
       
   297       }
       
   298       public boolean isValid(Object aObject){
       
   299         DateTime value = (DateTime)aObject;
       
   300         return value.gteq(fMinValue);
       
   301       }
       
   302       private final DateTime fMinValue;
       
   303     }
       
   304     return new Minimum(aMinimumValue);
       
   305   }
       
   306 
       
   307   /**
       
   308    Return a {@link Validator} to check that a field's value is less than or equal to <tt>aMaximumValue</tt>. 
       
   309    See class comment for the kind of objects which may be checked by the returned {@link Validator}.
       
   310   */
       
   311   public static Validator max(final long aMaximumValue){
       
   312     class Maximum implements Validator {
       
   313       Maximum(long aMaxValue){
       
   314         fMaxValue = aMaxValue;
       
   315       }
       
   316       public boolean isValid(Object aObject){
       
   317         long value = getValueAsLong(aObject);
       
   318         return value <= fMaxValue;
       
   319       }
       
   320       private final long fMaxValue;
       
   321     }
       
   322     return new Maximum(aMaximumValue);
       
   323   }
       
   324   
       
   325   /**
       
   326    Return a {@link Validator} to check that a {@link BigDecimal} field is less than or equal 
       
   327    to <tt>aMaximumValue</tt>. 
       
   328   */
       
   329   public static Validator max(final BigDecimal aMaximumValue){
       
   330     class Maximum implements Validator {
       
   331       Maximum(BigDecimal aMaxValue){
       
   332         fMaxValue = aMaxValue;
       
   333       }
       
   334       public boolean isValid(Object aObject){
       
   335         BigDecimal value = (BigDecimal)aObject;
       
   336         return value.compareTo(fMaxValue) <= 0;
       
   337       }
       
   338       private final BigDecimal fMaxValue;
       
   339     }
       
   340     return new Maximum(aMaximumValue);
       
   341   }
       
   342   
       
   343   /**
       
   344    Return a {@link Validator} to check that a {@link Decimal} amount is less than or equal 
       
   345    to <tt>aMaximumValue</tt>. This methods allows comparisons between 
       
   346    <tt>Money</tt> objects of different currency.
       
   347   */
       
   348   public static Validator max(final Decimal aMaximumValue){
       
   349     class Maximum implements Validator {
       
   350       Maximum(Decimal aMaxValue){
       
   351         fMaxValue = aMaxValue;
       
   352       }
       
   353       public boolean isValid(Object aObject){
       
   354         Decimal value = (Decimal)aObject;
       
   355         return value.lteq(fMaxValue);
       
   356       }
       
   357       private final Decimal fMaxValue;
       
   358     }
       
   359     return new Maximum(aMaximumValue);
       
   360   }
       
   361 
       
   362   /**
       
   363   Return a {@link Validator} to check that a {@link DateTime} is less than or equal 
       
   364   to <tt>aMaximumValue</tt>. 
       
   365   */
       
   366   public static Validator max(final DateTime aMaximumValue){
       
   367     class Maximum implements Validator {
       
   368       Maximum(DateTime aMaxValue){
       
   369         fMaxValue = aMaxValue;
       
   370       }
       
   371       public boolean isValid(Object aObject){
       
   372         DateTime value = (DateTime)aObject;
       
   373         return value.lteq(fMaxValue);
       
   374       }
       
   375       private final DateTime fMaxValue;
       
   376     }
       
   377     return new Maximum(aMaximumValue);
       
   378   }
       
   379   
       
   380   /**
       
   381    Return a {@link Validator} to check that a field's value is in a certain range (inclusive). 
       
   382    See class comment for the kind of objects which may be checked by the returned {@link Validator}.
       
   383   */
       
   384   public static Validator range(final long aMinimumValue, final long aMaximumValue){
       
   385     class Range implements Validator {
       
   386       Range(long aMin, long aMax){
       
   387         fMinValue = aMin;
       
   388         fMaxValue = aMax;
       
   389       }
       
   390       public boolean isValid(Object aObject){
       
   391         long value = getValueAsLong(aObject);
       
   392         return fMinValue <= value && value <= fMaxValue;
       
   393       }
       
   394       private final long fMinValue;
       
   395       private final long fMaxValue;
       
   396     }
       
   397     return new Range(aMinimumValue, aMaximumValue);
       
   398   }
       
   399 
       
   400   /**
       
   401    Return a {@link Validator} to check that a {@link BigDecimal} value is in a certain range (inclusive). 
       
   402   */
       
   403   public static Validator range(final BigDecimal aMinimumValue, final BigDecimal aMaximumValue){
       
   404     class Range implements Validator {
       
   405       Range(BigDecimal aMin, BigDecimal aMax){
       
   406         fMinValue = aMin;
       
   407         fMaxValue = aMax;
       
   408       }
       
   409       public boolean isValid(Object aObject){
       
   410         BigDecimal value = (BigDecimal)aObject;
       
   411         return value.compareTo(fMinValue) >= 0 && value.compareTo(fMaxValue) <= 0;
       
   412       }
       
   413       private final BigDecimal fMinValue;
       
   414       private final BigDecimal fMaxValue;
       
   415     }
       
   416     return new Range(aMinimumValue, aMaximumValue);
       
   417   }
       
   418   
       
   419   /**
       
   420    Return a {@link Validator} to check that a {@link Decimal} amount is in a certain range (inclusive). 
       
   421    This methods allows comparisons between <tt>Money</tt> objects of different currency.
       
   422   */
       
   423   public static Validator range(final Decimal aMinimumValue, final Decimal aMaximumValue){
       
   424     class Range implements Validator {
       
   425       Range(Decimal aMin, Decimal aMax){
       
   426         fMinValue = aMin;
       
   427         fMaxValue = aMax;
       
   428       }
       
   429       public boolean isValid(Object aObject){
       
   430         Decimal value = (Decimal)aObject;
       
   431         return value.gteq(fMinValue) && value.lteq(fMaxValue);
       
   432       }
       
   433       private final Decimal fMinValue;
       
   434       private final Decimal fMaxValue;
       
   435     }
       
   436     return new Range(aMinimumValue, aMaximumValue);
       
   437   }
       
   438 
       
   439   /**
       
   440    Return a {@link Validator} to check that a {@link DateTime} is in a certain range (inclusive). 
       
   441   */
       
   442   public static Validator range(final DateTime aMinimumValue, final DateTime aMaximumValue){
       
   443     class Range implements Validator {
       
   444       Range(DateTime aMin, DateTime aMax){
       
   445         fMinValue = aMin;
       
   446         fMaxValue = aMax;
       
   447       }
       
   448       public boolean isValid(Object aObject){
       
   449         DateTime value = (DateTime)aObject;
       
   450         boolean isInRange = value.gt(fMinValue) && value.lt(fMaxValue);
       
   451         boolean isAtAnEndpoint = value.equals(fMinValue) || value.equals(fMaxValue);
       
   452         return isInRange || isAtAnEndpoint;
       
   453       }
       
   454       private final DateTime fMinValue;
       
   455       private final DateTime fMaxValue;
       
   456     }
       
   457     return new Range(aMinimumValue, aMaximumValue);
       
   458   }
       
   459   
       
   460   /**
       
   461    Return a {@link Validator} to check that the number of decimal places of a {@link Decimal} or 
       
   462    {@link BigDecimal} is <em>less than or equal to</em> <tt>aMaxNumberOfDecimalPlaces</tt>. 
       
   463    
       
   464    @param aMaxNumberOfDecimalPlaces is greater than or equal to <tt>1</tt>.
       
   465   */
       
   466   public static Validator numDecimalsMax(final int aMaxNumberOfDecimalPlaces){
       
   467     Args.checkForPositive(aMaxNumberOfDecimalPlaces);
       
   468     class MaxNumPlaces implements Validator {
       
   469       MaxNumPlaces(int aMaxNumberOfDecimals){
       
   470         Args.checkForPositive(aMaxNumberOfDecimals);
       
   471         fMaxNumPlaces = aMaxNumberOfDecimals;
       
   472       }
       
   473       public boolean isValid(Object aObject){
       
   474         return Util.hasMaxDecimals(extractNumber(aObject), fMaxNumPlaces);
       
   475       }
       
   476       private final int fMaxNumPlaces;
       
   477     }
       
   478     return new MaxNumPlaces(aMaxNumberOfDecimalPlaces);
       
   479   }
       
   480   
       
   481   /**
       
   482    Return a {@link Validator} to check that the number of decimal places of a {@link Decimal} 
       
   483    or {@link BigDecimal} is <em>exactly equal to</em> <tt>aNumDecimals</tt>.
       
   484     
       
   485    @param aNumDecimals is 0 or more.
       
   486   */
       
   487   public static Validator numDecimalsAlways(final int aNumDecimals){
       
   488     class NumPlaces implements Validator {
       
   489       NumPlaces(int aNumberOfDecimalPlaces){
       
   490         fNumPlaces = aNumberOfDecimalPlaces;
       
   491       }
       
   492       public boolean isValid(Object aObject){
       
   493         return Util.hasNumDecimals(extractNumber(aObject), fNumPlaces);
       
   494       }
       
   495       private final int fNumPlaces;
       
   496     }
       
   497     return new NumPlaces(aNumDecimals);
       
   498   }
       
   499   
       
   500   /**
       
   501    Return a {@link Validator} that checks a {@link String} or {@link SafeText} 
       
   502    field versus a regular expression {@link Pattern}.
       
   503    
       
   504    <P>This method might be used to validate a zip code, phone number, and so on - any text which has a 
       
   505    well defined format.
       
   506    
       
   507    <P>There must be a complete match versus the whole text, as in {@link Matcher#matches()}.
       
   508    In addition, the returned {@link Validator} will not trim the text before performing the validation.
       
   509    
       
   510    @param aRegex is a {@link Pattern}, which holds a regular expression. 
       
   511   */
       
   512   public static Validator pattern(final Pattern aRegex){
       
   513     class PatternValidator implements Validator {
       
   514       PatternValidator(Pattern aSomeRegex){
       
   515         fRegex = aSomeRegex;
       
   516       }
       
   517       public boolean isValid(Object aObject) {
       
   518           Matcher matcher = fRegex.matcher(getText(aObject));
       
   519           return matcher.matches();
       
   520       }
       
   521       private final Pattern fRegex;
       
   522     }
       
   523     return new PatternValidator(aRegex);
       
   524   }
       
   525   
       
   526   /**
       
   527    Return a {@link Validator} to verify a {@link String} or {@link SafeText} field is a syntactically 
       
   528    valid email address.
       
   529    
       
   530    <P>See {@link WebUtil#isValidEmailAddress(String)}. The text is not trimmed by the returned 
       
   531    {@link Validator}.
       
   532   */
       
   533   public static Validator email(){
       
   534     class EmailValidator implements Validator {
       
   535       public boolean isValid(Object aObject) {
       
   536         return WebUtil.isValidEmailAddress(getText(aObject));
       
   537       }
       
   538     }
       
   539     return new EmailValidator();
       
   540   }
       
   541   
       
   542   /** 
       
   543     Return a {@link Validator} to check that a {@link String} or {@link SafeText} field is not spam, 
       
   544    according to {@link SpamDetector}.   
       
   545   */
       
   546   public static Validator forSpam(){ 
       
   547     class SpamValidator implements Validator {
       
   548       public boolean isValid(Object aObject) {
       
   549         SpamDetector spamDetector = BuildImpl.forSpamDetector();
       
   550         return !spamDetector.isSpam(getText(aObject));
       
   551       }
       
   552     }
       
   553     return new SpamValidator();
       
   554   }
       
   555   
       
   556   /*
       
   557    Note : forURL() method was attempted, but abandoned. 
       
   558    The JDK implementation of the URL constructor seems very flaky.
       
   559   */
       
   560 
       
   561   /**
       
   562    This no-argument constructor is empty. 
       
   563    
       
   564    <P>This constructor exists only because of it has <tt>protected</tt> scope. 
       
   565    Having <tt>protected</tt> scope has two desirable effects:
       
   566    <ul>
       
   567    <li>typical callers cannot create <tt>Check</tt> objects. This is appropriate since this class contains 
       
   568    only static methods.
       
   569    <li>if needed, this class may be subclassed. This is useful when you need to add custom validations.
       
   570    </ul>
       
   571   */
       
   572   protected Check(){
       
   573     //empty - prevent construction by caller, but allow it for subclasses
       
   574   }
       
   575   
       
   576   // PRIVATE //
       
   577 
       
   578   private static final Logger fLogger = Util.getLogger(Check.class);
       
   579   
       
   580   private static boolean passesValidations(Object aObject, Validator... aValidators) {
       
   581     boolean result = PASSES;
       
   582     for(Validator validator: aValidators){
       
   583       if ( ! validator.isValid(aObject) ) {
       
   584         result = FAILS;
       
   585         fLogger.fine("Failed a validation.");
       
   586         break;
       
   587       }
       
   588     }
       
   589     return result;
       
   590   }
       
   591 
       
   592   private static long getValueAsLong(Object aObject){
       
   593     long result = 0;
       
   594     if ( aObject instanceof Integer){
       
   595       Integer value = (Integer)aObject;
       
   596       result = value.intValue();
       
   597     }
       
   598     else if (aObject instanceof Long) {
       
   599       Long value = (Long)aObject;
       
   600       result = value.longValue();
       
   601     }
       
   602     else if (aObject instanceof String){
       
   603       String text = (String)aObject;
       
   604       if ( Util.textHasContent(text) ) {
       
   605         result = text.trim().length();
       
   606       }
       
   607     }
       
   608     else if (aObject instanceof Id){
       
   609       Id id = (Id)aObject;
       
   610       if ( Util.textHasContent(id.toString()) ) {
       
   611         result = id.toString().trim().length();
       
   612       }
       
   613     }
       
   614     else if (aObject instanceof SafeText){
       
   615       SafeText text = (SafeText)aObject;
       
   616       if ( Util.textHasContent(text.getRawString()) ) {
       
   617         result = text.getRawString().trim().length();
       
   618       }
       
   619     }
       
   620     else if (aObject instanceof Code){
       
   621       Code code = (Code)aObject;
       
   622       if ( Util.textHasContent(code.getText()) ) {
       
   623         result = code.getText().getRawString().trim().length();
       
   624       }
       
   625     }
       
   626     else if (aObject instanceof Collection) {
       
   627       Collection collection = (Collection)aObject;
       
   628       result = collection.size();
       
   629     }
       
   630     else if (aObject instanceof Map) {
       
   631       Map map = (Map)aObject;
       
   632       result = map.size();
       
   633     }
       
   634     else if (aObject instanceof Date) {
       
   635       Date date = (Date)aObject;
       
   636       result = date.getTime(); 
       
   637     }
       
   638     else if (aObject instanceof Calendar){
       
   639       Calendar calendar = (Calendar)aObject;
       
   640       result = calendar.getTimeInMillis();
       
   641     }
       
   642     else {
       
   643       String message = "Unexpected type of Object: " + aObject.getClass().getName();
       
   644       fLogger.severe(message);
       
   645       throw new AssertionError(message);
       
   646     }
       
   647     return result;
       
   648   }
       
   649   
       
   650   private static boolean isText(Object aObject){
       
   651     return (aObject instanceof String) || (aObject instanceof SafeText);
       
   652   }
       
   653   
       
   654   private static String getText(Object aObject){
       
   655     String result = null;
       
   656     if ( aObject instanceof String ){
       
   657       String text = (String) aObject;
       
   658       result = text.toString();
       
   659     }
       
   660     else if (aObject instanceof SafeText ){
       
   661       SafeText text = (SafeText)aObject;
       
   662       result = text.getRawString();
       
   663     }
       
   664     return result;
       
   665   }
       
   666   
       
   667   /** aObject must be a BigDecimal or a Money object.  */
       
   668   private static BigDecimal extractNumber(Object aObject){
       
   669     BigDecimal result = null;
       
   670     if( aObject instanceof BigDecimal){
       
   671       result = (BigDecimal)aObject;
       
   672     }
       
   673     else if (aObject instanceof Decimal) {
       
   674       Decimal decimal = (Decimal)aObject;
       
   675       result = decimal.getAmount();
       
   676     }
       
   677     else {
       
   678       throw new IllegalArgumentException("Unexpected class: " + aObject.getClass());
       
   679     }
       
   680     return result;
       
   681   }
       
   682 }