package hirondelle.web4j.ui.tag;

import hirondelle.web4j.BuildImpl;
import hirondelle.web4j.TESTAll.DateConverterImpl;
import hirondelle.web4j.model.ModelCtorException;
import hirondelle.web4j.model.ModelUtil;
import hirondelle.web4j.request.DateConverter;
import hirondelle.web4j.request.Formats;
import hirondelle.web4j.ui.tag.Populate.Style;
import hirondelle.web4j.util.Consts;
import hirondelle.web4j.util.Util;
import hirondelle.web4j.util.WebUtil;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Set;
import java.util.TimeZone;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;

import javax.servlet.ServletException;
import javax.servlet.jsp.JspException;

import junit.framework.TestCase;


/**
 (UNPUBLISHED) <a href="http://www.junit.org">JUnit</a> test cases for 
 {@link PopulateHelper}. 
 
 TO RUN THIS CLASS, some temporary adjustments need to be made to this class and the Formats class.
 - 1 change to this class
 - 2 changes to Formats
 See TESTAll as well! 
*/
public final class TESTFormPopulator extends TestCase {

  /** Run the test cases.  */
   public static void main(String args[]) throws FileNotFoundException, ServletException {
     fLogger.setLevel(Level.OFF);
     String[] testCaseName = { TESTFormPopulator.class.getName() };
     BuildImpl.adHocImplementationAdd(DateConverter.class,  DateConverterImpl.class);
     junit.textui.TestRunner.main(testCaseName);
  }

  public TESTFormPopulator (String aName) {
   super(aName);
  }

  // TEST CASES //

  public void testSingleParamsWithInputTags() throws JspException, IOException {
    //test no alteration of tag which is not related to input
    testSingleParam("<IMG src='http://www.blah.com/images/blah.gif' ALT='blah'>", "<IMG src='http://www.blah.com/images/blah.gif' ALT='blah'>", "LoginName", "Strummer");
    //test no alteration of tag whose type(submit) is not supported 
    testSingleParam("<input type=\"submit\" value='REGISTER'>", "<input type=\"submit\" value='REGISTER'>", "LoginName", "Strummer");
    //text 
    testSingleParam("<input name='LoginName' type='text' size='30'>", "<input value=\"Strummer\" name='LoginName' type='text' size='30'>", "LoginName", "Strummer");
    //caps, spacing, quotes
    testSingleParam("<INPUT    NAME='LoginName' TYPE='TEXT' SIZE='30'>", "<INPUT value=\"Strummer\"    NAME='LoginName' TYPE='TEXT' SIZE='30'>", "LoginName", "Strummer");
    //additional extraneous attr's
    testSingleParam("<input name='LoginName' type='text' size='30' class='blah'>", "<input value=\"Strummer\" name='LoginName' type='text' size='30' class='blah'>", "LoginName", "Strummer");
    //password
    testSingleParam("<input name='LoginPassword' type='password' size='30'>", "<input value=\"clash\" name='LoginPassword' type='password' size='30'>", "LoginPassword", "clash");
    //hidden
    testSingleParam("<input name='LoginPassword' type='hidden'>", "<input value=\"clash\" name='LoginPassword' type='hidden'>", "LoginPassword", "clash");
    //search
    testSingleParam("<input name='Bob' type='search'>", "<input value=\"clash\" name='Bob' type='search'>", "Bob", "clash");
    //email
    testSingleParam("<input name='Bob' type='email'>", "<input value=\"joe&#064;clash&#046;com\" name='Bob' type='email'>", "Bob", "joe@clash.com");
    //url
    testSingleParam("<input name='Bob' type='url'>", "<input value=\"http&#058;&#047;&#047;www&#046;date4j&#046;net\" name='Bob' type='url'>", "Bob", "http://www.date4j.net");
    //tel
    testSingleParam("<input name='Bob' type='tel'>", "<input value=\"1&#045;800&#045;549&#045;BLAH\" name='Bob' type='tel'>", "Bob", "1-800-549-BLAH");
    //number
    testSingleParam("<input name='Bob' type='number'>", "<input value=\"3&#046;14\" name='Bob' type='number'>", "Bob", "3.14");
    //color
    testSingleParam("<input name='Bob' type='color'>", "<input value=\"&#035;001122\" name='Bob' type='color'>", "Bob", "#001122");
    //range
    testSingleParam("<input name='Bob' type='range'>", "<input value=\"56\" name='Bob' type='range'>", "Bob", "56");
    //radio with match
    testSingleParam("<INPUT type='RADIO' name=\"StarRating\" value='1'>", "<INPUT checked type='RADIO' name=\"StarRating\" value='1'>", "StarRating", "1");
    //radio with no match
    testSingleParam("<INPUT type='RADIO' name=\"StarRating\" value='2'>", "<INPUT type='RADIO' name=\"StarRating\" value='2'>", "StarRating", "1");
    //checkbox with match 
    testSingleParam("<input type=\"CHECKBOX\" name=\"SendCard\" value='true'>", "<input checked type=\"CHECKBOX\" name=\"SendCard\" value='true'>", "SendCard", "true");
    //checkbox with no match
    testSingleParam("<input type=\"CHECKBOX\" name=\"SendCard\" value='true'>", "<input type=\"CHECKBOX\" name=\"SendCard\" value='true'>", "SendCard", "false");
    
    //text with default is overwritten
    testSingleParam("<input name='LoginName' value='Joe' type='text' size='30'>", "<input name='LoginName' value=\"Mick\" type='text' size='30'>", "LoginName", "Mick");
    //as above, but with hidden
    testSingleParam("<input name='LoginName' value='Joe' type='hidden'>", "<input name='LoginName' value=\"Mick\" type='hidden'>", "LoginName", "Mick");
    //as above, but with search
    testSingleParam("<input name='LoginName' value='Joe' type='search'>", "<input name='LoginName' value=\"Mick\" type='search'>", "LoginName", "Mick");
    //as above, but with number
    testSingleParam("<input name='LoginName' value='3.14' type='number'>", "<input name='LoginName' value=\"7&#046;95\" type='number'>", "LoginName", "7.95");
    //text with default is overwritten even if param is empty
    testSingleParam("<input name='LoginName' value='Mick' type='text' size='30'>", "<input name='LoginName' value=\"\" type='text' size='30'>", "LoginName", "");
    //as above, but with hidden
    testSingleParam("<input name='LoginName' value='Mick' type='hidden'>", "<input name='LoginName' value=\"\" type='hidden'>", "LoginName", "");
    //as above, but with search
    testSingleParam("<input name='LoginName' value='Mick' type='search'>", "<input name='LoginName' value=\"\" type='search'>", "LoginName", "");
    //text with default is overwritten even if default is empty
    testSingleParam("<input name='LoginName' value='' type='text' size='30'>", "<input name='LoginName' value=\"Mick\" type='text' size='30'>", "LoginName", "Mick");
    //radio is checked by default, but no match, so is unchecked
    testSingleParam("<INPUT type='RADIO' checked name=\"StarRating\" value='1'>", "<INPUT type='RADIO' name=\"StarRating\" value='1'>", "StarRating", "2");
    //as previous, but checked attr at end 
    testSingleParam("<INPUT type='RADIO' name=\"StarRating\" value='1' checked>", "<INPUT type='RADIO' name=\"StarRating\" value='1'>", "StarRating", "2");
    //radio is checked by default, and matches, so is unchanged
    testSingleParam("<INPUT type='RADIO' checked name=\"StarRating\" value='1'>", "<INPUT type='RADIO' checked name=\"StarRating\" value='1'>", "StarRating", "1");
    //checkbox is checked by default, but no match, so is removed
    testSingleParam("<input type=\"CHECKBOX\" name=\"SendCard\" checked value='true'>", "<input type=\"CHECKBOX\" name=\"SendCard\" value='true'>", "SendCard", "false");
    //checkbox is checked by default, with match, so is retained
    testSingleParam("<input type=\"CHECKBOX\" name=\"SendCard\" value='true' checked>", "<input type=\"CHECKBOX\" name=\"SendCard\" value='true' checked>", "SendCard", "true");
  }

  public void testInputTagWithSpecialRegexChar() throws JspException, IOException {
    //this test failed with the first version of PopulateHelper, since an item 
    //was not 'escaped' for special regex chars. As well, this case was untested 
    //in the original test cases.
    testSingleParam("<input name='DesiredSalary' type='text' size='30'>", "<input value=\"100&#046;00U&#036;\" name='DesiredSalary' type='text' size='30'>", "DesiredSalary", "100.00U$");
  }
  
  public void testBigFailures() throws JspException, IOException {
    //invalid flavor
    testBigFailure("<inputt name='LoginName' type='text' size='30'>", "<input value=\"Strummer\" name='LoginName' type='text' size='30'>", "LoginName", "Strummer");
    //compulsory attr not found (type)
    testBigFailure("<input name='LoginName' size='30'>", "<input value=\"Strummer\" name='LoginName' type='text' size='30'>", "LoginName", "Strummer");
    //compulsory attr not found (name)
    testBigFailure("<input type='text' size='30'>", "<input value=\"Strummer\" name='LoginName' type='text' size='30'>", "LoginName", "Strummer");
    //no param of expected name in scope
    testBigFailure("<input name='LoginName' type='text' size='30'>", "<input value=\"Strummer\" name='LoginName' type='text' size='30'>", "Login", "Strummer");
  }
  
  public void testModelObjectWithInputTags() throws JspException, IOException, ModelCtorException  {
    User user = getUser();
    //text 
    testModelObject("<input name='LoginName' type='text' size='30'>", "<input value=\"Strummer\" name='LoginName' type='text' size='30'>", user);
    //test no alteration of tag which is not related to input
    testModelObject("<IMG src='http://www.blah.com/images/blah.gif' ALT='blah'>", "<IMG src='http://www.blah.com/images/blah.gif' ALT='blah'>", user);
    //test no alteration of tag whose type(submit) is not supported 
    testModelObject("<input type=\"submit\" value='REGISTER'>", "<input type=\"submit\" value='REGISTER'>", user);
    //caps, spacing, quotes
    testModelObject("<INPUT    NAME='LoginName' TYPE='TEXT' SIZE='30'>", "<INPUT value=\"Strummer\"    NAME='LoginName' TYPE='TEXT' SIZE='30'>", user);
    //additional extraneous attr's
    testModelObject("<input name='LoginName' type='text' size='30' class='blah'>", "<input value=\"Strummer\" name='LoginName' type='text' size='30' class='blah'>", user);
    //password
    testModelObject("<input name='LoginPassword' type='password' size='30'>", "<input value=\"clash\" name='LoginPassword' type='password' size='30'>", user);
    //hidden
    testModelObject("<input name='LoginPassword' type='hidden'>", "<input value=\"clash\" name='LoginPassword' type='hidden'>", user);
    //search
    testModelObject("<input name='LoginPassword' type='search'>", "<input value=\"clash\" name='LoginPassword' type='search'>", user);
    //email
    testModelObject("<input name='EmailAddress' type='email'>", "<input value=\"joe&#064;clash&#046;com\" name='EmailAddress' type='email'>", user);
    //number
    testModelObject("<input name='DesiredSalary' type='number'>", "<input value=\"17500&#046;00\" name='DesiredSalary' type='number'>", user);
    //range
    testModelObject("<input name='Range' type='range'>", "<input value=\"17&#046;5\" name='Range' type='range'>", user);
    //color
    testModelObject("<input name='Color' type='color'>", "<input value=\"&#035;001122\" name='Color' type='color'>", user);
    
    //radio with match
    testModelObject("<INPUT type='RADIO' name=\"StarRating\" value='1'>", "<INPUT checked type='RADIO' name=\"StarRating\" value='1'>", user);
    //radio with no match
    testModelObject("<INPUT type='RADIO' name=\"StarRating\" value='2'>", "<INPUT type='RADIO' name=\"StarRating\" value='2'>", user);
    //checkbox with match 
    testModelObject("<input type=\"CHECKBOX\" name=\"SendCard\" value='false'>", "<input checked type=\"CHECKBOX\" name=\"SendCard\" value='false'>", user);
    //checkbox with no match
    testModelObject("<input type=\"CHECKBOX\" name=\"SendCard\" value='true'>", "<input type=\"CHECKBOX\" name=\"SendCard\" value='true'>", user);
    
    //text with default is overwritten
    testModelObject("<input name='LoginName' value='Mick' type='text' size='30'>", "<input name='LoginName' value=\"Strummer\" type='text' size='30'>", user);
    //as above, but for hidden
    testModelObject("<input name='LoginName' value='Mick' type='hidden'>", "<input name='LoginName' value=\"Strummer\" type='hidden'>", user);
    //text with default is overwritten even if default is empty
    testModelObject("<input name='LoginName' value='' type='text' size='30'>", "<input name='LoginName' value=\"Strummer\" type='text' size='30'>", user);
    //as above, but with hidden
    testModelObject("<input name='LoginName' value='' type='hidden'>", "<input name='LoginName' value=\"Strummer\" type='hidden'>", user);
    //as above, but with search
    testModelObject("<input name='LoginName' value='' type='search'>", "<input name='LoginName' value=\"Strummer\" type='search'>", user);
    //radio is checked by default, but no match, so is unchecked
    testModelObject("<INPUT type='RADIO' checked name=\"StarRating\" value='2'>", "<INPUT type='RADIO' name=\"StarRating\" value='2'>", user);
    //as previous, but checked attr at end 
    testModelObject("<INPUT type='RADIO' name=\"StarRating\" value='2' checked>", "<INPUT type='RADIO' name=\"StarRating\" value='2'>", user);
    //radio is checked by default, and matches, so is unchanged
    testModelObject("<INPUT type='RADIO' checked name=\"StarRating\" value='1'>", "<INPUT type='RADIO' checked name=\"StarRating\" value='1'>", user);
    //checkbox is checked by default, but no match, so is removed
    testModelObject("<input type=\"CHECKBOX\" name=\"SendCard\" checked value='true'>", "<input type=\"CHECKBOX\" name=\"SendCard\" value='true'>", user);
    //checkbox is checked by default, with match, so is retained
    testModelObject("<input type=\"CHECKBOX\" name=\"SendCard\" value='false' checked>", "<input type=\"CHECKBOX\" name=\"SendCard\" value='false' checked>", user);

    //basic sanity for Locale and TimeZone
    testModelObject("<input name='Country' type='text' size='30'>", "<input value=\"en&#095;ca\" name='Country' type='text' size='30'>", user);
    testModelObject("<input name='TZ' type='text' size='30'>", "<input value=\"Canada&#047;Atlantic\" name='TZ' type='text' size='30'>", user);
  }
  
  public void testFailBeanMethod() throws JspException, IOException, ModelCtorException {
    try {
      //name of tag does not match any bean getXXX method
      testModelObject("<input name='LoginNameB' type='text' size='30'>", "<input value=\"Strummer\" name='LoginName' type='text' size='30'>", getUser());
    }
    catch(Throwable ex){
      return;
    }
    fail("Should have failed.");
  }
  
  public void testParamsWithSelectTags() throws JspException, IOException {
    //basic prepop
    testSelect(
      "<select name='FavoriteTheory'>"+NL+
       " <option>Quantum Electrodynamics</option>"+NL+
       " <option>Quantum Flavordynamics</option>"+NL+
       " <option>Quantum Chromodynamics</option>"+NL+
       "</select>", 

       "<select name='FavoriteTheory'>"+NL+
       " <option selected>Quantum Electrodynamics</option>"+NL+
       " <option>Quantum Flavordynamics</option>"+NL+
       " <option>Quantum Chromodynamics</option>"+NL+
       "</select>", 

       "FavoriteTheory", new Object[] {"Quantum Electrodynamics"}
     );
    //extra attr's
    testSelect(
      "<select name='FavoriteTheory'>"+NL+
       " <option class='blah'>Quantum Electrodynamics</option>"+NL+
       " <option>Quantum Flavordynamics</option>"+NL+
       " <option>Quantum Chromodynamics</option>"+NL+
       "</select>", 

       "<select name='FavoriteTheory'>"+NL+
       " <option selected class='blah'>Quantum Electrodynamics</option>"+NL+
       " <option>Quantum Flavordynamics</option>"+NL+
       " <option>Quantum Chromodynamics</option>"+NL+
       "</select>", 

       "FavoriteTheory", new Object[] {"Quantum Electrodynamics"}
     );
    //with value attr and selected
    testSelect(
      "<select name='FavoriteTheory'>"+NL+
       " <option class='blah'>Quantum Electrodynamics</option>"+NL+
       " <option value='Quantum Flavordynamics'>QFD</option>"+NL+
       " <option>Quantum Chromodynamics</option>"+NL+
       "</select>", 

       "<select name='FavoriteTheory'>"+NL+
       " <option class='blah'>Quantum Electrodynamics</option>"+NL+
       " <option selected value='Quantum Flavordynamics'>QFD</option>"+NL+
       " <option>Quantum Chromodynamics</option>"+NL+
       "</select>", 

       "FavoriteTheory", new Object[] {"Quantum Flavordynamics"}
     );
    //with value attr and not selected
    testSelect(
      "<select name='FavoriteTheory'>"+NL+
       " <option class='blah'>Quantum Electrodynamics</option>"+NL+
       " <option value='Quantum Flavordynamics'>QFD</option>"+NL+
       " <option>Quantum Chromodynamics</option>"+NL+
       "</select>", 

       "<select name='FavoriteTheory'>"+NL+
       " <option selected class='blah'>Quantum Electrodynamics</option>"+NL+
       " <option value='Quantum Flavordynamics'>QFD</option>"+NL+
       " <option>Quantum Chromodynamics</option>"+NL+
       "</select>", 

       "FavoriteTheory", new Object[] {"Quantum Electrodynamics"}
     );
    //caps, spaces, and quotes
    testSelect(
      "<SELECT   NAME=\"FavoriteTheory\">"+NL+
       " <OPTION   class=\"blah\" >Quantum Electrodynamics</OPTION>"+NL+
       " <OPTION   value=\"Quantum Flavordynamics\" >QFD</OPTION>"+NL+
       " <OPTION >Quantum Chromodynamics</OPTION>"+NL+
       "</SELECT>", 

      "<SELECT   NAME=\"FavoriteTheory\">"+NL+
       " <OPTION   class=\"blah\" >Quantum Electrodynamics</OPTION>"+NL+
       " <OPTION   value=\"Quantum Flavordynamics\" >QFD</OPTION>"+NL+
       " <OPTION selected >Quantum Chromodynamics</OPTION>"+NL+
       "</SELECT>", 

       "FavoriteTheory", new Object[] {"Quantum Chromodynamics"}
     );
    //retain default selected
    testSelect(
      "<select name='FavoriteTheory'>"+NL+
       " <option>Quantum Electrodynamics</option>"+NL+
       " <option selected>Quantum Flavordynamics</option>"+NL+
       " <option>Quantum Chromodynamics</option>"+NL+
       "</select>", 

       "<select name='FavoriteTheory'>"+NL+
       " <option>Quantum Electrodynamics</option>"+NL+
       " <option selected>Quantum Flavordynamics</option>"+NL+
       " <option>Quantum Chromodynamics</option>"+NL+
       "</select>", 

       "FavoriteTheory", new Object[] {"Quantum Flavordynamics"}
     );
    //retain default selected when other attr's present
    testSelect(
      "<select name='FavoriteTheory'>"+NL+
       " <option>Quantum Electrodynamics</option>"+NL+
       " <option value='Quantum Flavordynamics' selected class='blah'>Quantum Flavordynamics</option>"+NL+
       " <option>Quantum Chromodynamics</option>"+NL+
       "</select>", 

      "<select name='FavoriteTheory'>"+NL+
       " <option>Quantum Electrodynamics</option>"+NL+
       " <option value='Quantum Flavordynamics' selected class='blah'>Quantum Flavordynamics</option>"+NL+
       " <option>Quantum Chromodynamics</option>"+NL+
       "</select>", 

       "FavoriteTheory", new Object[] {"Quantum Flavordynamics"}
     );
    //remove default selected when no match
    testSelect(
      "<select name='FavoriteTheory'>"+NL+
       " <option>Quantum Electrodynamics</option>"+NL+
       " <option value='Quantum Flavordynamics' selected class='blah'>Quantum Flavordynamics</option>"+NL+
       " <option>Quantum Chromodynamics</option>"+NL+
       "</select>", 

      "<select name='FavoriteTheory'>"+NL+
       " <option>Quantum Electrodynamics</option>"+NL+
       " <option value='Quantum Flavordynamics' class='blah'>Quantum Flavordynamics</option>"+NL+
       " <option selected>Quantum Chromodynamics</option>"+NL+
       "</select>", 

       "FavoriteTheory", new Object[] {"Quantum Chromodynamics"}
     );
    //extra attrs in select tag
    testSelect(
      "<select class='blah' name='FavoriteTheory' size='30'>"+NL+
       " <option>Quantum Electrodynamics</option>"+NL+
       " <option>Quantum Flavordynamics</option>"+NL+
       " <option>Quantum Chromodynamics</option>"+NL+
       "</select>", 

      "<select class='blah' name='FavoriteTheory' size='30'>"+NL+
       " <option selected>Quantum Electrodynamics</option>"+NL+
       " <option>Quantum Flavordynamics</option>"+NL+
       " <option>Quantum Chromodynamics</option>"+NL+
       "</select>", 

       "FavoriteTheory", new Object[] {"Quantum Electrodynamics"}
     );
    //test leading and trailing spaces in body of option tag
    testSelect(
      "<select name='FavoriteTheory'>"+NL+
       " <option> Quantum Electrodynamics </option>"+NL+
       " <option>  Quantum Flavordynamics    </option>"+NL+
       " <option> Quantum Chromodynamics</option>"+NL+
       "</select>", 

      "<select name='FavoriteTheory'>"+NL+
       " <option selected> Quantum Electrodynamics </option>"+NL+
       " <option>  Quantum Flavordynamics    </option>"+NL+
       " <option> Quantum Chromodynamics</option>"+NL+
       "</select>", 

       "FavoriteTheory", new Object[] {"Quantum Electrodynamics"}
     );
    //as above, with different selected
    testSelect(
      "<select name='FavoriteTheory'>"+NL+
       " <option> Quantum Electrodynamics </option>"+NL+
       " <option>  Quantum Flavordynamics    </option>"+NL+
       " <option> Quantum Chromodynamics</option>"+NL+
       "</select>", 

      "<select name='FavoriteTheory'>"+NL+
       " <option> Quantum Electrodynamics </option>"+NL+
       " <option selected>  Quantum Flavordynamics    </option>"+NL+
       " <option> Quantum Chromodynamics</option>"+NL+
       "</select>", 

       "FavoriteTheory", new Object[] {"Quantum Flavordynamics"}
     );
    //as above, with different selected
    testSelect(
      "<select name='FavoriteTheory'>"+NL+
       " <option> Quantum Electrodynamics </option>"+NL+
       " <option>  Quantum Flavordynamics    </option>"+NL+
       " <option> Quantum Chromodynamics</option>"+NL+
       "</select>", 

      "<select name='FavoriteTheory'>"+NL+
       " <option> Quantum Electrodynamics </option>"+NL+
       " <option>  Quantum Flavordynamics    </option>"+NL+
       " <option selected> Quantum Chromodynamics</option>"+NL+
       "</select>", 

       "FavoriteTheory", new Object[] {"Quantum Chromodynamics"}
     );
    //multiple selects
    testSelect(
      "<select multiple name='FavoriteTheory'>"+NL+
      " <option>Quantum Electrodynamics</option>"+NL+
      " <option>Quantum Flavordynamics</option>"+NL+
      " <option>Quantum Chromodynamics</option>"+NL+
      "</select>", 
       
      "<select multiple name='FavoriteTheory'>"+NL+
      " <option>Quantum Electrodynamics</option>"+NL+
      " <option>Quantum Flavordynamics</option>"+NL+
      " <option selected>Quantum Chromodynamics</option>"+NL+
      "</select>", 
      
      "FavoriteTheory", new Object[] {"Quantum Chromodynamics"}
    );
    //special regex chars in extra attrs in option tag
    testSelect(
      "<select class='blah' name='FavoriteTheory'>"+NL+
       " <option title='Aliases [utf-1-1-unicode]'>Quantum Electrodynamics</option>"+NL+
       " <option>Quantum Flavordynamics</option>"+NL+
       " <option>Quantum Chromodynamics</option>"+NL+
       "</select>", 

      "<select class='blah' name='FavoriteTheory'>"+NL+
       " <option selected title='Aliases [utf-1-1-unicode]'>Quantum Electrodynamics</option>"+NL+
       " <option>Quantum Flavordynamics</option>"+NL+
       " <option>Quantum Chromodynamics</option>"+NL+
       "</select>", 

       "FavoriteTheory", new Object[] {"Quantum Electrodynamics"}
     );
  }
  
  public void testFailedSelects() throws JspException, IOException, ModelCtorException {
    testFailSelect(
      "<select name='FaveTheory'>"+NL+
       " <option>Quantum Electrodynamics</option>"+NL+
       " <option>Quantum Flavordynamics"+NL+
       " <option>Quantum Chromodynamics</option>"+NL+
       "</select>", 

      "<select name='FaveTheory'>"+NL+
       " <option selected>Quantum Electrodynamics</option>"+NL+
       " <option>Quantum Flavordynamics</option>"+NL+
       " <option>Quantum Chromodynamics</option>"+NL+
       "</select>", 

       "FaveTheory", new Object[] {"Quantum Electrodynamics"}
     );
  }
  
  
  public void testMultipleParamsWithSelectTags() throws JspException, IOException {
    //basic 
    testSelect(
      "<select multiple name='FavoriteTheory'>"+NL+
       " <option>Quantum Electrodynamics</option>"+NL+
       " <option>Quantum Flavordynamics</option>"+NL+
       " <option>Quantum Chromodynamics</option>"+NL+
       "</select>", 

      "<select multiple name='FavoriteTheory'>"+NL+
       " <option>Quantum Electrodynamics</option>"+NL+
       " <option selected>Quantum Flavordynamics</option>"+NL+
       " <option selected>Quantum Chromodynamics</option>"+NL+
       "</select>", 

       "FavoriteTheory", new Object[] {"Quantum Flavordynamics","Quantum Chromodynamics"}
     );
     //value attr
    testSelect(
      "<select multiple name='FavoriteTheory'>"+NL+
       " <option>Quantum Electrodynamics</option>"+NL+
       " <option value='Quantum Flavordynamics'>QFD</option>"+NL+
       " <option>Quantum Chromodynamics</option>"+NL+
       "</select>", 

      "<select multiple name='FavoriteTheory'>"+NL+
       " <option>Quantum Electrodynamics</option>"+NL+
       " <option selected value='Quantum Flavordynamics'>QFD</option>"+NL+
       " <option selected>Quantum Chromodynamics</option>"+NL+
       "</select>", 

       "FavoriteTheory", new Object[] {"Quantum Flavordynamics","Quantum Chromodynamics"}
     );
     //retain default selected
    testSelect(
      "<select multiple name='FavoriteTheory'>"+NL+
       " <option>Quantum Electrodynamics</option>"+NL+
       " <option selected value='Quantum Flavordynamics'>QFD</option>"+NL+
       " <option>Quantum Chromodynamics</option>"+NL+
       "</select>", 

      "<select multiple name='FavoriteTheory'>"+NL+
       " <option>Quantum Electrodynamics</option>"+NL+
       " <option selected value='Quantum Flavordynamics'>QFD</option>"+NL+
       " <option selected>Quantum Chromodynamics</option>"+NL+
       "</select>", 

       "FavoriteTheory", new Object[] {"Quantum Flavordynamics","Quantum Chromodynamics"}
     );
     //remove default selected
    testSelect(
      "<select multiple name='FavoriteTheory'>"+NL+
       " <option selected>Quantum Electrodynamics</option>"+NL+
       " <option selected value='Quantum Flavordynamics'>QFD</option>"+NL+
       " <option>Quantum Chromodynamics</option>"+NL+
       "</select>", 

      "<select multiple name='FavoriteTheory'>"+NL+
       " <option>Quantum Electrodynamics</option>"+NL+
       " <option selected value='Quantum Flavordynamics'>QFD</option>"+NL+
       " <option selected>Quantum Chromodynamics</option>"+NL+
       "</select>", 

       "FavoriteTheory", new Object[] {"Quantum Flavordynamics","Quantum Chromodynamics"}
     );
  }
  
  public void testBeanWithSelectTags() throws JspException, IOException, ModelCtorException  {
    //basic prepop
    testModelObject(
      "<select name='FavoriteTheory'>"+NL+
      " <option>Quantum Electrodynamics</option>"+NL+
      " <option>Quantum Flavordynamics</option>"+NL+
      " <option>Quantum Chromodynamics</option>"+NL+
      "</select>", 
       
      "<select name='FavoriteTheory'>"+NL+
      " <option selected>Quantum Electrodynamics</option>"+NL+
      " <option>Quantum Flavordynamics</option>"+NL+
      " <option>Quantum Chromodynamics</option>"+NL+
      "</select>", 
      
      getUser()
    );
    //value attr, but not selected
    testModelObject(
      "<select name='FavoriteTheory'>"+NL+
      " <option>Quantum Electrodynamics</option>"+NL+
      " <option>Quantum Flavordynamics</option>"+NL+
      " <option value='Quantum Chromodynamics'>QCD</option>"+NL+
      "</select>", 
       
      "<select name='FavoriteTheory'>"+NL+
      " <option selected>Quantum Electrodynamics</option>"+NL+
      " <option>Quantum Flavordynamics</option>"+NL+
      " <option value='Quantum Chromodynamics'>QCD</option>"+NL+
      "</select>", 
      
      getUser()
    );
    //value attr, and selected
    testModelObject(
      "<select name='FavoriteTheory'>"+NL+
      " <option value='Quantum Electrodynamics'>QED</option>"+NL+
      " <option>Quantum Flavordynamics</option>"+NL+
      " <option value='Quantum Chromodynamics'>QCD</option>"+NL+
      "</select>", 
       
      "<select name='FavoriteTheory'>"+NL+
      " <option selected value='Quantum Electrodynamics'>QED</option>"+NL+
      " <option>Quantum Flavordynamics</option>"+NL+
      " <option value='Quantum Chromodynamics'>QCD</option>"+NL+
      "</select>", 
      
      getUser()
    );
    //retain default selected
    testModelObject(
      "<select name='FavoriteTheory'>"+NL+
      " <option selected value='Quantum Electrodynamics'>QED</option>"+NL+
      " <option>Quantum Flavordynamics</option>"+NL+
      " <option>Quantum Chromodynamics</option>"+NL+
      "</select>", 
       
      "<select name='FavoriteTheory'>"+NL+
      " <option selected value='Quantum Electrodynamics'>QED</option>"+NL+
      " <option>Quantum Flavordynamics</option>"+NL+
      " <option>Quantum Chromodynamics</option>"+NL+
      "</select>", 
      
      getUser()
    );
    //remove default selected
    testModelObject(
      "<select name='FavoriteTheory'>"+NL+
      " <option value='Quantum Electrodynamics'>QED</option>"+NL+
      " <option selected>Quantum Flavordynamics</option>"+NL+
      " <option>Quantum Chromodynamics</option>"+NL+
      "</select>", 
       
      "<select name='FavoriteTheory'>"+NL+
      " <option selected value='Quantum Electrodynamics'>QED</option>"+NL+
      " <option>Quantum Flavordynamics</option>"+NL+
      " <option>Quantum Chromodynamics</option>"+NL+
      "</select>", 
      
      getUser()
    );
  }
  
  public void testBeanWithMultiSelectTags() throws JspException, IOException, ModelCtorException {
    //User is taken as a bean which does not return a Set, but a String - this can 
    //of course cause only one item to be selected
    testModelObject(
      "<select multiple name='FavoriteTheory'>"+NL+
      " <option>Quantum Electrodynamics</option>"+NL+
      " <option>Quantum Flavordynamics</option>"+NL+
      " <option>Quantum Chromodynamics</option>"+NL+
      "</select>", 
       
      "<select multiple name='FavoriteTheory'>"+NL+
      " <option selected>Quantum Electrodynamics</option>"+NL+
      " <option>Quantum Flavordynamics</option>"+NL+
      " <option>Quantum Chromodynamics</option>"+NL+
      "</select>", 
      
      getUser()
    );
    //bean which returns a Set
    //this class the bean whose getFavoriteTheory method returns a Set
    //(A fake bean was attempted, but PopulateHelper barfs on nested classes.)
    testModelObject(
      "<select multiple name='FavoriteTheory'>"+NL+
      " <option>Quantum Electrodynamics</option>"+NL+
      " <option>Quantum Flavordynamics</option>"+NL+
      " <option>Quantum Chromodynamics</option>"+NL+
      "</select>", 
       
      "<select multiple name='FavoriteTheory'>"+NL+
      " <option selected>Quantum Electrodynamics</option>"+NL+
      " <option>Quantum Flavordynamics</option>"+NL+
      " <option selected>Quantum Chromodynamics</option>"+NL+
      "</select>", 
      
      this
    );
    //defaults retained and removed
    testModelObject(
      "<select multiple name='FavoriteTheory'>"+NL+
      " <option selected>Quantum Electrodynamics</option>"+NL+
      " <option selected>Quantum Flavordynamics</option>"+NL+
      " <option selected>Quantum Chromodynamics</option>"+NL+
      "</select>", 
       
      "<select multiple name='FavoriteTheory'>"+NL+
      " <option selected>Quantum Electrodynamics</option>"+NL+
      " <option>Quantum Flavordynamics</option>"+NL+
      " <option selected>Quantum Chromodynamics</option>"+NL+
      "</select>", 
      
      this
    );
    
  }
  
  /*
   See above method.
  */
  public Set getFavoriteTheory(){
    Set result = new LinkedHashSet();
    result.add("Quantum Electrodynamics");
    result.add("Quantum Chromodynamics");
    return result;
  }
  
  public void testArgsWithTextAreaTag() throws JspException, IOException, ModelCtorException {
    //with default text
    testSingleParam("<textarea name='Comment'> This is a comment.  </textarea>", "<textarea name='Comment'>The Clash</textarea>", "Comment", "The Clash");
    //add other attr's
    testSingleParam("<textarea name='Comment' wrap='virtual' rows=20 cols=50> This is a comment.  </textarea>", "<textarea name='Comment' wrap='virtual' rows=20 cols=50>The Clash</textarea>", "Comment", "The Clash");
    //caps, spacing, quotes
    testSingleParam("<TEXTAREA    NAME='Comment'> This is a comment.  </TEXTAREA>", "<TEXTAREA    NAME='Comment'>The Clash</TEXTAREA>", "Comment", "The Clash");
    testSingleParam("<textarea NAME='Comment'> This is a comment.  </TEXTAREA>", "<textarea NAME='Comment'>The Clash</TEXTAREA>", "Comment", "The Clash");
    //without default text
    testSingleParam("<textarea name='Comment'> </textarea>", "<textarea name='Comment'>The Clash</textarea>", "Comment", "The Clash");
    testSingleParam("<textarea name='Comment'></textarea>", "<textarea name='Comment'>The Clash</textarea>", "Comment", "The Clash");
    testSingleParam("<textarea name='Comment'>   </textarea>", "<textarea name='Comment'>The Clash</textarea>", "Comment", "The Clash");
    //with new lines and white space
    testSingleParam("<textarea name='Comment'>" +Consts.NEW_LINE+ " This is a comment. " +Consts.NEW_LINE+ " </textarea>", "<textarea name='Comment'>The Clash</textarea>", "Comment", "The Clash");
    testSingleParam("<textarea name='Comment'>" +Consts.NEW_LINE+  "</textarea>", "<textarea name='Comment'>The Clash</textarea>", "Comment", "The Clash");
    testSingleParam("<textarea name='Comment'> " +Consts.NEW_LINE+  "</textarea>", "<textarea name='Comment'>The Clash</textarea>", "Comment", "The Clash");
    testSingleParam("<textarea name='Comment'> " +Consts.NEW_LINE+  " </textarea>", "<textarea name='Comment'>The Clash</textarea>", "Comment", "The Clash");
    testSingleParam("<textarea name='Comment'>  " +Consts.NEW_LINE+  "  </textarea>", "<textarea name='Comment'>The Clash</textarea>", "Comment", "The Clash");
    //special regex chars in default body
    testSingleParam("<textarea name='Comment'> $This is a comment.  </textarea>", "<textarea name='Comment'>The Clash</textarea>", "Comment", "The Clash");
    testSingleParam("<textarea name='Comment'> This is a comment.$  </textarea>", "<textarea name='Comment'>The Clash</textarea>", "Comment", "The Clash");
    testSingleParam("<textarea name='Comment'> $This is a comment.$  </textarea>", "<textarea name='Comment'>The Clash</textarea>", "Comment", "The Clash");
    testSingleParam("<textarea name='Comment'> Thi$s is a comment.  </textarea>", "<textarea name='Comment'>The Clash</textarea>", "Comment", "The Clash");
    testSingleParam("<textarea name='Comment'> \\This is a comment.  </textarea>", "<textarea name='Comment'>The Clash</textarea>", "Comment", "The Clash");
    //special regex chars in replacememt
    testSingleParam("<textarea name='Comment'>This is a comment.  </textarea>", "<textarea name='Comment'>The Cla&#036;h</textarea>", "Comment", "The Cla$h");
    testSingleParam("<textarea name='Comment'>This is a comment.  </textarea>", "<textarea name='Comment'>&#036;The Clash&#036;</textarea>", "Comment", "$The Clash$");
    testSingleParam("<textarea name='Comment'>This is a comment.  </textarea>", "<textarea name='Comment'>&#092;The Clash</textarea>", "Comment", "\\The Clash");
  }

  public void testBeanWithTextAreaTag() throws JspException, IOException, ModelCtorException {
    User user = getUser();
    //with default text
    testModelObject("<textarea name='LoginName'>This is a comment</textarea>", "<textarea name='LoginName'>Strummer</textarea>", user);
    //with other attr's
    testModelObject("<textarea name='LoginName' cols='20' rows='35'>This is a comment</textarea>", "<textarea name='LoginName' cols='20' rows='35'>Strummer</textarea>", user);
    //caps, spacing, quotes
    testModelObject("<TEXTAREA   NAME='LoginName'>This is a comment</TEXTAREA>", "<TEXTAREA   NAME='LoginName'>Strummer</TEXTAREA>", user);
    testModelObject("<textarea   NAME='LoginName'>This is a comment</TEXTAREA>", "<textarea   NAME='LoginName'>Strummer</TEXTAREA>", user);
    //without default text
    testModelObject("<textarea name='LoginName'></textarea>", "<textarea name='LoginName'>Strummer</textarea>", user);
    //with new lines and white space
    testModelObject("<textarea name='LoginName'>" +Consts.NEW_LINE+ "</textarea>", "<textarea name='LoginName'>Strummer</textarea>", user);
    testModelObject("<textarea name='LoginName'> " +Consts.NEW_LINE+ "This is a comment" + Consts.NEW_LINE + " </textarea>", "<textarea name='LoginName'>Strummer</textarea>", user);
  }
  
  // FIXTURE //

  protected void setUp(){
    //empty
  }

  protected void tearDown() {
    //empty
  }

  // PRIVATE  //
  
  private static final String NL = Consts.NEW_LINE;
  
  private User getUser(){
    User result = null;
    try {
      result = new User(
        "Strummer", 
        "clash", 
        "joe@clash.com", 
        new Integer(1), 
        "Quantum Electrodynamics", 
        Boolean.FALSE, 
        new Integer(42),
        new BigDecimal("17500.00"),
        new Date(0), 
        new BigDecimal("17.5"),
        "#001122"
      );
    }
    catch (ModelCtorException ex){
      fail("Cannot build User object.");
    }
    return result;
  }
  
  private static final Logger fLogger = Util.getLogger(TESTFormPopulator.class);
  private static void log(Object aThing){
    System.out.println(String.valueOf(aThing));
  }

  
  private void testSingleParam(String aIn, String aOut, String aParamName, String aParamValue) throws JspException, IOException {
    PopulateHelper.Context fakeParam = new FakeSingleParam(aParamName, aParamValue);
    PopulateHelper populator = new PopulateHelper( fakeParam, aIn, Style.MUST_RECYCLE_PARAMS );
    String editedBody = populator.getEditedBody();
    assertTrue( editedBody.equals(aOut) );
  }

  private void testModelObject(String aIn, String aOut, Object aBean) throws JspException, IOException {
    PopulateHelper.Context myUserBean = new FakeBeanOnly(aBean);
    PopulateHelper populator = new PopulateHelper( myUserBean, aIn, Style.USE_MODEL_OBJECT);
    String populatedBody = populator.getEditedBody();
    if( ! populatedBody.equals(aOut) ){
      System.out.println("Populated Body : " + populatedBody);
    }
    assertTrue( populator.getEditedBody().equals(aOut) );
  }
  
  private void testSelect(String aIn, String aOut, String aParamName, Object[] aParamValues) throws JspException, IOException {
    PopulateHelper.Context selectParams = new FakeSelectParams(aParamName, aParamValues);
    PopulateHelper populator = new PopulateHelper( selectParams, aIn, Style.MUST_RECYCLE_PARAMS);
    assertTrue( populator.getEditedBody().equals(aOut) );
  }
  
  private void testBigFailure(String aIn, String aOut, String aParamName, String aParamValue) throws JspException, IOException {
    try {
      testSingleParam(aIn, aOut, aParamName, aParamValue);
    }
    catch(Throwable ex){
      return;
    }
    fail("Should have failed.");
  }
  
  private void testFailSelect(String aIn, String aOut, String aParamName, Object[] aParamValues) throws JspException, IOException {
    try {
      testSelect(aIn, aOut, aParamName, aParamValues);
    }
    catch(Throwable ex){
      return;
    }
    fail("Select should have failed.");
  }
  
  /** Fake the case where a single request parameter is present.  */ 
  static private final class FakeSingleParam implements PopulateHelper.Context {
    FakeSingleParam(String aParamName, String aParamValue){
      fParamName = aParamName;
      fParamValue = aParamValue;
    }
    public String getReqParamValue(String aName){
      return aName.equals(fParamName) ? fParamValue : null;
    }
    public boolean hasRequestParamNamed(String aParamName) {
      return fParamName.equals(aParamName);
    }
    public Object getModelObject(){
      return null;
    }
    public boolean isModelObjectPresent() {
      return false;
    }
    public Formats getFormats(){
      return new Formats(new Locale("en"), TimeZone.getTimeZone("GMT"));
    }
    public Collection<String> getReqParamValues(String aParamName){
      Collection<String> result = new ArrayList<String>();
      result.add(fParamValue);
      return aParamName.equals(fParamName) ? result : Collections.EMPTY_LIST;
    }
    private final String fParamName;
    private final String fParamValue;
  }
  
  /** Fake the case where a bean is present, but no request parameters.  */ 
  static private final class FakeBeanOnly implements PopulateHelper.Context {
    FakeBeanOnly(Object aObject){
      fBean = aObject;
    }
    public String getReqParamValue(String aName){
      return null;
    }
    public boolean hasRequestParamNamed(String aParamName) {
      return false;
    }
    public boolean isModelObjectPresent(){
      return fBean != null;
    }
    public Object getModelObject(){
      return fBean;
    }
    public Formats getFormats(){
      return new Formats(new Locale("en"), TimeZone.getTimeZone("GMT") );
    }
    public Collection<String> getReqParamValues(String aParamName){
      return null;
    }
    private final Object fBean;
  }
  
  /** Fake the case where request parameters are possibly multi-valued.   */ 
  static private final class FakeSelectParams implements PopulateHelper.Context {
    FakeSelectParams(String aParamName, Object[] aParamValues){
      fParamName = aParamName;
      fParamValues = Collections.unmodifiableCollection( Arrays.asList(aParamValues) );
    }
    public String getReqParamValue(String aName){
      return null;
    }
    public boolean hasRequestParamNamed(String aParamName) {
      return fParamName.equals(aParamName);
    }
    public boolean isModelObjectPresent(){
      return false;
    }
    public Object getModelObject(){
      return null;
    }
    public Formats getFormats(){
      return new Formats(new Locale("en"), TimeZone.getTimeZone("GMT") );
    }
    public Collection<String> getReqParamValues(String aParamName){
      return aParamName.equals(fParamName) ? fParamValues : null;
    }
    private final String fParamName;
    private final Collection fParamValues;
  }
  
  private static final Pattern fLOGIN_NAME_PATTERN = Pattern.compile("(.){3,30}");
  private static final Pattern fLOGIN_PASSWORD_PATTERN = Pattern.compile("(\\S){3,20}");

  /** 
   Typical model class stolen from exampleA. 
   Even though this is repetition, it is the only area where the library 
   uses the example. It would be a shame to have to link the projects for only 
   this reason, as it is dangerous to attach the library to an app.
  */
  public static final class User  { 

    /**
     @param aLoginName has trimmed length in range <tt>3..30</tt>.
     @param aLoginPassword has length in range <tt>3..20</tt>, and contains 
     no whitespace.
     @param aEmailAddress satisfies {@link WebUtil#isValidEmailAddress} 
     @param aStarRating rating of last meal in range <tt>1..4</tt>; 
     optional - if null, replace with value of <tt>0</tt>.
     @param aFavoriteTheory favorite quantum field theory ; 
     optional - may be <tt>null</tt>.
     @param aSendCard toggle for sending Christmas card to user ; 
     optional - if <tt>null</tt>, replace with <tt>false</tt>.
     @param aAge of the user, in range <tt>0..150</tt> ; 
     optional - may be <tt>null</tt>.
     @param aDesiredSalary is <tt>0</tt> or more ; optional - may be <tt>null</tt>.
     @param aBirthDate is not in the future ; optional - may be <tt>null</tt>.
    */
    public User(
      String aLoginName, 
      String aLoginPassword, 
      String aEmailAddress, 
      Integer aStarRating, 
      String aFavoriteTheory, 
      Boolean aSendCard,
      Integer aAge,
      BigDecimal aDesiredSalary,
      Date aBirthDate, 
      BigDecimal aRange, 
      String aColor
    ) throws ModelCtorException {

      fLoginName = Util.trimPossiblyNull(aLoginName);
      fLoginPassword = aLoginPassword;
      fEmailAddress = Util.trimPossiblyNull(aEmailAddress);
      
      fStarRating = Util.replaceIfNull(aStarRating, ZERO);
      fFavoriteTheory = aFavoriteTheory;
      fSendCard = Util.replaceIfNull(aSendCard, Boolean.FALSE);
      
      fAge = aAge;
      fDesiredSalary = aDesiredSalary;
      //defensive copy
      fBirthDate = aBirthDate == null ? null : new Long(aBirthDate.getTime());
      
      fRange = aRange;
      fColor = aColor;
      
      validateState();
    }

    public String getLoginName() {
      return fLoginName;
    }

    public String getLoginPassword() {
      return fLoginPassword;
    }

    public String getEmailAddress() {
      return fEmailAddress;
    }
    
    public Integer getStarRating(){
      return fStarRating;
    }
    
    public String getFavoriteTheory(){
      return fFavoriteTheory;
    }
    
    public Boolean getSendCard(){
      return fSendCard;
    }
    
    public Integer getAge(){
      return fAge;
    }

    public BigDecimal getDesiredSalary(){
      return fDesiredSalary;
    }
    
    public Date getBirthDate(){
      //defensive copy
      return fBirthDate == null ? null : new Date(fBirthDate.longValue());
    }
    
    public Locale getCountry(){
      return new Locale("en_ca");
    }
    
    public TimeZone getTZ(){
      return TimeZone.getTimeZone("Canada/Atlantic");
    }
    
    public BigDecimal getRange(){
      return fRange;
    }
    
    public String getColor(){
      return fColor;
    }
    
    /**
     Intended for debugging only.
    */
    public String toString() {
      return ModelUtil.toStringFor(this);
    }

    public boolean equals( Object aThat ) {
      if ( this == aThat ) return true;
      if ( !(aThat instanceof User) ) return false;
      User that = (User)aThat;
      return ModelUtil.equalsFor(this.getSignificantFields(), that.getSignificantFields());
    }

    public int hashCode() {
      return ModelUtil.hashCodeFor(getSignificantFields());
    }
    
    private Object[] getSignificantFields(){
      return new Object[]{
        fLoginName, fLoginPassword, fEmailAddress, fStarRating, fFavoriteTheory, 
        fSendCard, fAge, fDesiredSalary, fBirthDate
      };
    }

    // PRIVATE // 
    private final String fLoginName;
    private final String fLoginPassword;
    private final String fEmailAddress;
    private final Integer fStarRating;
    private final String fFavoriteTheory;
    private final Boolean fSendCard;
    private final Integer fAge;
    private final BigDecimal fDesiredSalary;
    private final Long fBirthDate;
    private final BigDecimal fRange;
    private final String fColor;
    private static final Integer ZERO = new Integer(0);
    
    private void validateState() throws ModelCtorException {
      ModelCtorException ex = new ModelCtorException();
      if ( ! isValidLoginName(fLoginName) ) {
        ex.add("Login name must have 3..30 characters");
      }
      if ( ! isValidLoginPassword(fLoginPassword) ) {
        ex.add("Password must have 3..20 characters, with no spaces");
      }
      if ( ! isValidEmailAddress(fEmailAddress) ){
        ex.add("Email address has an invalid form");
      }
      if ( ! isValidStarRating(fStarRating) ){
        ex.add("Star Rating (optional) must be in range 1..4");
      }
      //no validation needed for favoriteTheory, sendCard
      if ( ! isValidAge(fAge) ) {
        ex.add("Age must be in range 0..150");
      }
      if ( ! isValidDesiredSalary(fDesiredSalary) ) {
        ex.add("Desired Salary must be 0 or more.");
      }
      if ( ! isValidBirthDate(fBirthDate) ){
        ex.add("Birth Date must be in the past.");
      }
      if ( ! ex.isEmpty() ) throw ex;
    }
    
    private boolean isValidLoginName(String aLoginName){
      return Util.matches(fLOGIN_NAME_PATTERN, aLoginName);
    }
    
    private boolean isValidLoginPassword(String aLoginPassword){
      return Util.matches(fLOGIN_PASSWORD_PATTERN, aLoginPassword);
    }

    private boolean isValidEmailAddress(String aEmailAddress){
      return WebUtil.isValidEmailAddress(aEmailAddress);
    }
    
    private boolean isValidStarRating(Integer aStarRating){
      return Util.isInRange(aStarRating.intValue(), 0, 4);
    }
    
    private boolean isValidAge(Integer aAge){
      boolean result = true;
      if ( aAge != null ){
        if ( ! Util.isInRange(aAge.intValue(), 0, 150) ){
          result = false;
        }
      }
      return result;
    }
    
    private boolean isValidDesiredSalary(BigDecimal aDesiredSalary){
      final BigDecimal ZERO_MONEY = new BigDecimal("0");
      boolean result = true;
      if ( aDesiredSalary != null ) {
        if ( aDesiredSalary.compareTo(ZERO_MONEY) < 0 ) {
          result = false;
        }
      }
      return result;
    }
    
    private boolean isValidBirthDate(Long aBirthDate){
      boolean result = true;
      if ( aBirthDate != null ) {
        if ( aBirthDate.longValue() > System.currentTimeMillis()) {
          result = false;
        }
      }
      return result;
    }
  }
}
