/*
 * Copyright 2000-2004 The Apache Software Foundation.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.jetspeed.util.parser;

import java.beans.PropertyDescriptor;
import java.beans.IndexedPropertyDescriptor;
import java.beans.Introspector;
import java.lang.reflect.Method;

import java.math.BigDecimal;
import org.apache.torque.om.NumberKey;
import org.apache.torque.om.StringKey;
import java.util.Date;

import org.apache.turbine.util.ParameterParser;
import org.apache.turbine.util.pool.Recyclable;

import org.apache.jetspeed.util.ValidationException;
import org.apache.jetspeed.util.ValidationHelper;

/**
 * A Turbine parameter parser with Validation built in. 
 * Validates any bean with methods that begin with validate[AttributeName].
 * Works with Torque-generated beans.
 * To use this class, override the TurbineResources.properties:
 * 
 * services.RunDataService.default.parameter.parser=org.apache.turbine.util.parser.DefaultParameterParser
 *
 * @author <a href="mailto:taylor@apache.org">David Sean Taylor</a>
 * @version $Id: ValidationParameterParser.java,v 1.6 2004/02/23 03:18:08 jford Exp $
 */
public class ValidationParameterParser
    extends DefaultJetspeedParameterParser
    implements ParameterParser,
               Recyclable
{
                    
    public ValidationParameterParser()
    {      
        super();
    }

    public ValidationParameterParser(String characterEncoding)
    {
        super (characterEncoding);
    }


   /**
     * Uses bean introspection to set writable properties of bean from
     * the parameters, where a (case-insensitive) name match between
     * the bean property and the parameter is looked for.
     *
     * @param bean An Object.
     * @exception Exception, a generic exception.
     */
    public void setProperties(Object bean)
        throws Exception
    {
        Class beanClass = bean.getClass();
        PropertyDescriptor[] props
            = Introspector.getBeanInfo(beanClass).getPropertyDescriptors();
        StringBuffer invalidFieldMessages = new StringBuffer("");
        boolean valid = true;
        for (int i = 0; i < props.length; i++)
        {
            String propname = props[i].getName();
            Method setter = props[i].getWriteMethod();

            if (setter != null &&
                (containsKey(propname) ||
                 containsDateSelectorKeys(propname) ||
                 containsTimeSelectorKeys(propname)))
            {
                /* VALIDATION */
                if (!validateProperty(bean, props[i]))
                {
                    invalidFieldMessages.append("'");
                    invalidFieldMessages.append(propname);
                    invalidFieldMessages.append("' is not a valid field, ");
                    valid = false;
                }
                setMyProperty(bean, props[i]);
                
            }
        }
        
        // call the general validation for the entire business object
        String msg = generalValidation(bean);
        if (msg != null)
        {
            invalidFieldMessages.append(msg);
            invalidFieldMessages.append(", ");            
            valid = false;
        }
        
        //System.out.println("invalidFieldMessages ["+invalidFieldMessages+"]");    
        if (!valid)
        {
            //int lastComma = invalidFieldMessages.lastIndexOf(", ");
            int lastComma = new String(invalidFieldMessages).lastIndexOf(", ");
            String result = invalidFieldMessages.substring(0,lastComma);
            // System.err.println("Had a validation problem and am throwing: "+invalidFieldMessages);    
            throw new ValidationException(result);
        }
    }

   /**
     * Set the property 'prop' in the bean to the value of the
     * corresponding parameters.  Supports all types supported by
     * getXXX methods plus a few more that come for free because
     * primitives have to be wrapped before being passed to invoke
     * anyway.
     *
     * @param bean An Object.
     * @param prop A PropertyDescriptor.
     * @exception Exception, a generic exception.
     */
    protected void setMyProperty(Object bean,
                                 PropertyDescriptor prop)
        throws Exception
    {
        if (prop instanceof IndexedPropertyDescriptor)
        {
            throw new Exception(prop.getName() +
                                " is an indexed property (not supported)");
        }

        Method setter = prop.getWriteMethod();
        if (setter == null)
        {
            throw new Exception(prop.getName() +
                                " is a read only property");
        }

        Object[] args = getArguments(prop);
        try
        {
            setter.invoke(bean, args);
        }
        catch (Throwable t)
        {
            System.out.println("Validation: EXCEPTION (prop): " +  prop.getName());
        }
    }
    

    protected Object[] getArguments(PropertyDescriptor prop)
        throws Exception
    {
        Class propclass = prop.getPropertyType();
        Object[] args = { null };
    
        if (propclass == String.class)
        {
            args[0] = getString(prop.getName());
        }
        else if (propclass == Integer.class || propclass == Integer.TYPE)
        {
            args[0] = getInteger(prop.getName());
        }
        else if (propclass == Short.class || propclass == Short.TYPE)
        {
            args[0] = new Short((short)(getInteger(prop.getName()).intValue()));
        }        
        else if (propclass == Long.class    || propclass == Long.TYPE)
        {
            args[0] = new Long(getLong(prop.getName()));
        }
        else if (propclass == Boolean.class || propclass == Boolean.TYPE)
        {
            args[0] = getBool(prop.getName());
        }
        else if (propclass == Double.class  || propclass == Double.TYPE)
        {
            args[0] = new Double(getDouble(prop.getName()));
        }
        else if (propclass == BigDecimal.class)
        {
            args[0] = getBigDecimal(prop.getName());
        }
        else if (propclass == String[].class)
        {
            args[0] = getStrings(prop.getName());
        }
        else if (propclass == Object.class)
        {
            args[0] = getObject(prop.getName());
        }
        else if (propclass == int[].class)
        {
            args[0] = getInts(prop.getName());
        }
        else if (propclass == Integer[].class)
        {
            args[0] = getIntegers(prop.getName());
        }
        else if (propclass == Date.class)
        {
            args[0] = getDate(prop.getName());
        }
        else if (propclass == NumberKey.class)
        {
            args[0] = getNumberKey(prop.getName());
        }
        else if (propclass == StringKey.class)
        {
            args[0] = getStringKey(prop.getName());
        }
        else
        {            
            //throw new Exception("property " + prop.getName() + " is of unsupported type " + propclass.toString());                                            
        }
        return args;
    }


    /**
     * Validate a bean's property based on definition in the business object
     *
     * @param bean The bean to be validated.
     * @param prop The bean's property descriptor
     * @return true if validation was successful, false if validation failed
     **/    
    protected boolean validateProperty(Object bean,
                                       PropertyDescriptor prop)
        throws Exception
    {
        String propertyName = prop.getName();
        String methodName = "validate" + 
                            Character.toUpperCase(propertyName.charAt(0))  +
                            propertyName.substring(1);

        Class[] signatureParams = { prop.getPropertyType() }; //{ java.lang.String.class };
        Object[] methodParams = getArguments(prop);
        try
        {
            Method method = bean.getClass().getMethod(methodName, signatureParams);
            boolean isValidBool = ((Boolean)method.invoke(bean, methodParams)).booleanValue();
            return isValidBool;
        }
        catch (NoSuchMethodException nsm_e)
        {
            try{
                return validateObject(prop);
            }
            catch (Exception e)
            {
                // System.err.println("EXCEPTION With the auto validation "+ e);
                return false;
            }
        }
        catch (Exception e)
        {
            // System.err.println("EXCEPTION INVOKING METHOD " + methodName + " : " + e);
        }
        return true;
    }


    protected boolean validateObject(PropertyDescriptor prop)
        throws Exception
    {
        Class propclass = prop.getPropertyType();
        Object[] args = { null };
    
        if (propclass == String.class)
        {
            return ValidationHelper.isAlphaNumeric(getString(prop.getName()), false);
        }
        else if (propclass == Integer.class || propclass == Integer.TYPE)
        {
             return ValidationHelper.isInteger(getString(prop.getName()), false);
        }
        else if (propclass == Short.class || propclass == Short.TYPE)
        {
             return ValidationHelper.isInteger(getString(prop.getName()), false);
        }        
        else if (propclass == Long.class    || propclass == Long.TYPE)
        {
            return ValidationHelper.isDecimal(getString(prop.getName()), false);
        }
        else if (propclass == Boolean.class || propclass == Boolean.TYPE)
        {
            return true;// WORK NEEDED
        }
        else if (propclass == Double.class  || propclass == Double.TYPE)
        {
            return ValidationHelper.isDecimal(getString(prop.getName()), false);
        }
        else if (propclass == BigDecimal.class)
        {
            return ValidationHelper.isDecimal(getString(prop.getName()), false);
        }
        else if (propclass == String[].class)
        {
            return ValidationHelper.isAlphaNumeric(getString(prop.getName()), false);
        }
        else if (propclass == Object.class)
        {
            System.err.println("Auto validate: Object-- NOT IMPLEMENTED");
            return true;//work needed
        }
        else if (propclass == int[].class)
        {
            return ValidationHelper.isInteger(getString(prop.getName()), false);
        }
        else if (propclass == Integer[].class)
        {
            return ValidationHelper.isInteger(getString(prop.getName()), false);
        }
        else if (propclass == Date.class)
        {
            // System.err.println("Auto validate: Date -- NOT IMPLEMENTED");
            return true;//work needed
        }
        else if (propclass == NumberKey.class)
        {
            return ValidationHelper.isInteger(getString(prop.getName()), false);
        }
        else if (propclass == StringKey.class)
        {
            return ValidationHelper.isInteger(getString(prop.getName()), false);
        }
        else
        {
            /*
            throw new Exception("property "
                                + prop.getName()
                                + " is of unsupported type "
                                + propclass.toString());
                                */
        }
        return false; 
    }

    /**
     * Validate a bean's property based on definition in the business object
     *
     * @param bean The bean to be validated.
     * @param prop The bean's property descriptor
     * @return null if validation was successful, an error message if validation failed
     **/    
    protected String generalValidation(Object bean)
            throws Exception
        {
            String methodName = "validate"; 
            try
            {
                Method method = bean.getClass().getMethod(methodName, null);
                String msg = (String)method.invoke(bean, null);
                return msg;
            }
            catch (NoSuchMethodException nsm_e)
            {
            }
            catch (Exception e)
            {
                System.err.println("EXCEPTION INVOKING METHOD " + methodName + " : " + e);
            }
            return null;
        }


}


