org.apache.click.util.ContainerUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.click.util.ContainerUtils.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.click.util;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import ognl.DefaultTypeConverter;
import ognl.Ognl;
import ognl.OgnlOps;

import org.apache.click.Control;
import org.apache.click.Page;
import org.apache.click.control.Button;
import org.apache.click.control.Container;
import org.apache.click.control.Field;
import org.apache.click.control.FieldSet;
import org.apache.click.control.Form;
import org.apache.click.control.Label;
import org.apache.click.service.LogService;
import org.apache.commons.lang.ClassUtils;

/**
 * Provides Container access and copy utilities.
 */
public class ContainerUtils {

    /**
     * Populate the given object attributes from the Containers field values.
     * <p/>
     * If a Field and object attribute matches, the object attribute is set to
     * the Object returned from the method
     * {@link org.apache.click.control.Field#getValueObject()}. If an object
     * attribute is a primitive, the Object returned from
     * {@link org.apache.click.control.Field#getValueObject()} will be converted
     * into the specific primitive e.g. Integer will become int and Boolean will
     * become boolean.
     * <p/>
     * The fieldList specifies which fields to copy to the object. This allows
     * one to include or exclude certain Container fields before populating the
     * object.
     * <p/>
     * The following example shows how to exclude disabled fields from
     * populating a customer object:
     * <pre class="prettyprint">
     * public void onInit() {
     *     List formFields = new ArrayList();
     *     for(Iterator it = form.getFieldList().iterator(); it.hasNext(); ) {
     *         Field field = (Field) formFields.next();
     *         // Exclude disabled fields
     *         if (!field.isDisabled()) {
     *             formFields.add(field);
     *         }
     *     }
     *     Customer customer = new Customer();
     *     ContainerUtils.copyContainerToObject(form, customer, formFields);
     * }
     * </pre>
     *
     * The specified Object can either be a POJO (plain old java object) or
     * a {@link java.util.Map}. If a POJO is specified, its attributes are
     * populated from  matching container fields. If a map is specified, its
     * key/value pairs are populated from matching container fields.
     *
     * @param container the fieldList Container
     * @param object the object to populate with field values
     * @param fieldList the list of fields to obtain values from
     *
     * @throws IllegalArgumentException if container, object or fieldList is
     * null
     */
    public static void copyContainerToObject(Container container, Object object, List<Field> fieldList) {

        if (container == null) {
            throw new IllegalArgumentException("Null container parameter");
        }

        if (object == null) {
            throw new IllegalArgumentException("Null object parameter");
        }

        if (fieldList == null) {
            throw new IllegalArgumentException("Null fieldList parameter");
        }

        if (fieldList.isEmpty()) {
            LogService logService = ClickUtils.getLogService();
            if (logService.isDebugEnabled()) {
                String containerClassName = ClassUtils.getShortClassName(container.getClass());
                logService.debug("   " + containerClassName + " has no fields to copy from");
            }
            //Exit early.
            return;
        }

        String objectClassname = object.getClass().getName();
        objectClassname = objectClassname.substring(objectClassname.lastIndexOf(".") + 1);

        // If the given object is a map, its key/value pair is populated from
        // the fields name/value pair.
        if (object instanceof Map<?, ?>) {
            copyFieldsToMap(fieldList, (Map) object);
            // Exit after populating the map.
            return;
        }

        LogService logService = ClickUtils.getLogService();

        Set<String> properties = getObjectPropertyNames(object);
        Map<?, ?> ognlContext = Ognl.createDefaultContext(object, null, new FixBigDecimalTypeConverter(), null);

        for (Field field : fieldList) {

            // Ignore disabled field as their values are not submitted in HTML
            // forms
            if (field.isDisabled()) {
                continue;
            }

            if (!hasMatchingProperty(field, properties)) {
                continue;
            }

            String fieldName = field.getName();

            ensureObjectPathNotNull(object, fieldName);

            try {
                PropertyUtils.setValueOgnl(object, fieldName, field.getValueObject(), ognlContext);

                if (logService.isDebugEnabled()) {
                    String containerClassName = ClassUtils.getShortClassName(container.getClass());
                    String msg = "    " + containerClassName + " -> " + objectClassname + "." + fieldName + " : "
                            + field.getValueObject();

                    logService.debug(msg);
                }

            } catch (Exception e) {
                String msg = "Error incurred invoking " + objectClassname + "." + fieldName + " with "
                        + field.getValueObject() + " error: " + e.toString();

                logService.debug(msg);
            }
        }
    }

    /**
     * Populate the given object attributes from the Containers field values.
     *
     * @see #copyContainerToObject(org.apache.click.control.Container, java.lang.Object, java.util.List)
     *
     * @param container the Container to obtain field values from
     * @param object the object to populate with field values
     */
    public static void copyContainerToObject(Container container, Object object) {
        List<Field> fieldList = getInputFields(container);
        copyContainerToObject(container, object, fieldList);
    }

    /**
     * Populate the given Container field values from the object attributes.
     * <p/>
     * If a Field and object attribute matches, the Field value is set to the
     * object attribute using the method
     * {@link org.apache.click.control.Field#setValueObject(java.lang.Object)}. If
     * an object attribute is a primitive it is first converted to its proper
     * wrapper class e.g. int will become Integer and boolean will become
     * Boolean.
     * <p/>
     * The fieldList specifies which fields to populate from the object. This
     * allows one to exclude or include specific fields.
     * <p/>
     * The specified Object can either be a POJO (plain old java object) or
     * a {@link java.util.Map}. If a POJO is specified, its attributes are
     * copied to matching container fields. If a map is specified, its key/value
     * pairs are copied to matching container fields.
     *
     * @param object the object to obtain attribute values from
     * @param container the Container to populate
     * @param fieldList the list of fields to populate from the object
     * attributes
     */
    public static void copyObjectToContainer(Object object, Container container, List<Field> fieldList) {
        if (object == null) {
            throw new IllegalArgumentException("Null object parameter");
        }

        if (container == null) {
            throw new IllegalArgumentException("Null container parameter");
        }

        if (container == null) {
            throw new IllegalArgumentException("Null fieldList parameter");
        }

        if (fieldList.isEmpty()) {
            LogService logService = ClickUtils.getLogService();
            if (logService.isDebugEnabled()) {
                String containerClassName = ClassUtils.getShortClassName(container.getClass());
                logService.debug("   " + containerClassName + " has no fields to copy to");
            }
            //Exit early.
            return;
        }

        String objectClassname = object.getClass().getName();
        objectClassname = objectClassname.substring(objectClassname.lastIndexOf(".") + 1);

        //If the given object is a map, populate the fields name/value from
        //the maps key/value pair.
        if (object instanceof Map<?, ?>) {

            copyMapToFields((Map) object, fieldList);
            //Exit after populating the fields.
            return;
        }

        Set<String> properties = getObjectPropertyNames(object);

        LogService logService = ClickUtils.getLogService();

        for (Field field : fieldList) {

            if (!hasMatchingProperty(field, properties)) {
                continue;
            }

            String fieldName = field.getName();
            try {
                Object result = PropertyUtils.getValue(object, fieldName);

                field.setValueObject(result);

                if (logService.isDebugEnabled()) {
                    String containerClassName = ClassUtils.getShortClassName(container.getClass());
                    String msg = "    " + containerClassName + " <- " + objectClassname + "." + fieldName + " : "
                            + result;
                    logService.debug(msg);
                }

            } catch (Exception e) {
                String msg = "Error incurred invoking " + objectClassname + "." + fieldName + " error: "
                        + e.toString();

                logService.debug(msg);
            }
        }
    }

    /**
     * Populate the given Container field values from the object attributes.
     *
     * @see #copyObjectToContainer(java.lang.Object, org.apache.click.control.Container, java.util.List)
     *
     * @param object the object to obtain attribute values from
     * @param container the Container to populate
     */
    public static void copyObjectToContainer(Object object, Container container) {

        List<Field> fieldList = getInputFields(container);
        copyObjectToContainer(object, container, fieldList);
    }

    /**
     * Find and return the first control with a matching name in the specified
     * container.
     * <p/>
     * If no matching control is found in the specified container, child
     * containers will be recursively scanned for a match.
     *
     * @param container the container that is searched for a control with a
     * matching name
     * @param name the name of the control to find
     * @return the control which name matched the given name
     */
    public static Control findControlByName(Container container, String name) {
        Control control = container.getControl(name);

        if (control != null) {
            return control;

        } else {
            for (Control childControl : container.getControls()) {

                if (childControl instanceof Container) {
                    Container childContainer = (Container) childControl;
                    Control found = findControlByName(childContainer, name);
                    if (found != null) {
                        return found;
                    }
                }
            }
        }
        return null;
    }

    /**
     * Find and return the specified controls parent Form or null
     * if no Form is present.
     *
     * @param control the control to check for Form
     * @return the controls parent Form or null if no parent is a Form
     */
    public static Form findForm(Control control) {
        while (control.getParent() != null && !(control.getParent() instanceof Page)) {
            control = (Control) control.getParent();
            if (control instanceof Form) {
                return (Form) control;
            }
        }
        return null;
    }

    /**
     * Return the list of Buttons for the given Container, recursively including
     * any Fields contained in child containers.
     *
     * @param container the container to obtain the buttons from
     * @return the list of contained buttons
     */
    public static List<Button> getButtons(Container container) {
        if (container == null) {
            throw new IllegalArgumentException("Null container parameter");
        }

        List<Button> buttons = new ArrayList<Button>();
        addButtons(container, buttons);
        return buttons;
    }

    /**
     * Return a list of container fields which are not valid, not hidden and not
     * disabled.
     * <p/>
     * The list of returned fields will exclude any <tt>Button</tt> fields.
     *
     * @param container the container to obtain the invalid fields from
     * @return list of container fields which are not valid, not hidden and not
     * disabled
     */
    public static List<Field> getErrorFields(Container container) {
        if (container == null) {
            throw new IllegalArgumentException("Null container parameter");
        }

        List<Field> fields = new ArrayList<Field>();
        addErrorFields(container, fields);
        return fields;
    }

    /**
     * Return a map of all Fields for the given Container, recursively including
     * any Fields contained in child containers.
     * <p/>
     * The map's key / value pair will consist of the control name and instance.
     *
     * @param container the container to obtain the fields from
     * @return the map of contained fields
     */
    public static Map<String, Field> getFieldMap(Container container) {
        if (container == null) {
            throw new IllegalArgumentException("Null container parameter");
        }

        Map<String, Field> fields = new HashMap<String, Field>();
        addFields(container, fields);
        return fields;
    }

    /**
     * Return the list of Fields for the given Container, recursively including
     * any Fields contained in child containers.
     *
     * @param container the container to obtain the fields from
     * @return the list of contained fields
     */
    public static List<Field> getFields(Container container) {
        if (container == null) {
            throw new IllegalArgumentException("Null container parameter");
        }

        List<Field> fields = new ArrayList<Field>();
        addFields(container, fields);
        return fields;
    }

    /**
     * Return the list of Fields for the given Container, recursively including
     * any Fields contained in child containers. The list of returned fields
     * will exclude any <tt>Button</tt> and <tt>FieldSet</tt> fields.
     *
     * @param container the container to obtain the fields from
     * @return the list of contained fields
     */
    public static List<Field> getFieldsAndLabels(Container container) {
        if (container == null) {
            throw new IllegalArgumentException("Null container parameter");
        }

        List<Field> fields = new ArrayList<Field>();
        addFieldsAndLabels(container, fields);
        return fields;
    }

    /**
     * Return the list of hidden Fields for the given Container, recursively including
     * any Fields contained in child containers. The list of returned fields
     * will exclude any <tt>Button</tt>, <tt>FieldSet</tt> and <tt>Label</tt>
     * fields.
     *
     * @param container the container to obtain the fields from
     * @return the list of contained fields
     */
    public static List<Field> getHiddenFields(final Container container) {
        if (container == null) {
            throw new IllegalArgumentException("Null container parameter");
        }

        List<Field> fields = new ArrayList<Field>();
        addHiddenFields(container, fields);
        return fields;
    }

    /**
     * Return the list of input Fields (TextField, Select, Radio, Checkbox etc).
     * for the given Container, recursively including any Fields contained in
     * child containers. The list of returned fields will exclude any
     * <tt>Button</tt>, <tt>FieldSet</tt> and <tt>Label</tt> fields.
     *
     * @param container the container to obtain the fields from
     * @return the list of contained fields
     */
    public static List<Field> getInputFields(final Container container) {
        if (container == null) {
            throw new IllegalArgumentException("Null container parameter");
        }

        List<Field> fields = new ArrayList<Field>();
        addInputFields(container, fields);
        return fields;
    }

    /**
     * Add the given control to the container at the specified index, and return
     * the added instance.
     * <p/>
     * <b>Please note</b>: an exception is raised if the container contains a
     * control with the same name as the given control. It is the responsibility
     * of the caller to replace existing controls.
     * <p/>
     * <b>Also note</b> if the specified control already has a parent assigned,
     * it will automatically be removed from that parent and inserted as a child
     * of the container instead.
     * <p/>
     * This method is useful for developers needing to implement the
     * {@link org.apache.click.control.Container} interface but cannot for one
     * reason or another extend from {@link org.apache.click.control.AbstractContainer}.
     * For example if the Container already extends from an existing <tt>Control</tt>
     * such as a <tt>Field</tt>, it won't be possible to extend
     * <tt>AbstractContainer</tt> as well. In such scenarios instead of
     * reimplementing {@link org.apache.click.control.Container#insert(org.apache.click.Control, int) insert},
     * one can delegate to this method.
     * <p/>
     * For example, a custom Container that extends <tt>Field</tt> and
     * implements <tt>Container</tt> could implement the <tt>insert</tt> method
     * as follows:
     * <pre class="prettyprint">
     * public class MyContainer extends Field implements Container {
     *
     *     public Control insert(Control control, int index) {
     *         return ContainerUtils.insert(this, control, index, getControlMap());
     *     }
     *
     *     ...
     * } </pre>
     *
     * @param container the container to insert the given control into
     * @param control the control to add to the container
     * @param index the index at which the control is to be inserted
     * @param controlMap the container's map of controls keyed on control name
     * @return the control that was added to the container
     *
     * @throws IllegalArgumentException if the control is null or if the control
     * and container is the same instance
     *
     * @throws IndexOutOfBoundsException if index is out of range
     * <tt>(index &lt; 0 || index &gt; container.getControls().size())</tt>
     */
    public static Control insert(Container container, Control control, int index, Map<String, Control> controlMap) {

        // Pre conditions start
        if (control == null) {
            throw new IllegalArgumentException("Null control parameter");
        }
        if (control == container) {
            throw new IllegalArgumentException("Cannot add container to itself");
        }
        int size = container.getControls().size();
        if (index > size || index < 0) {
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
        }
        // Check if container already contains the control
        if (controlMap.containsKey(control.getName()) && !(control instanceof Label)) {

            throw new IllegalArgumentException("Container already contains control named: " + control.getName());
        }

        // Pre conditions end

        // Check if control already has parent
        // If parent references the given container, there is no need to remove it
        Object currentParent = control.getParent();
        if (currentParent != null && currentParent != container) {

            // Remove control from parent Page or Container
            if (currentParent instanceof Page) {
                ((Page) currentParent).removeControl(control);

            } else if (currentParent instanceof Container) {
                ((Container) currentParent).remove(control);
            }

            // Create warning message to users that the parent has been reset
            logParentReset(container, control, currentParent);
        }

        // Note: set parent first since setParent might veto further processing
        control.setParent(container);
        container.getControls().add(index, control);

        String controlName = control.getName();
        if (controlName != null) {
            controlMap.put(controlName, control);
        }
        return control;
    }

    /**
     * Replace the current control in the container at the specified index, and
     * return the newly added control.
     * <p/>
     * <b>Please note</b> if the new control already has a parent assigned,
     * it will automatically be removed from that parent and inserted as a child
     * of the container instead.
     * <p/>
     * This method is useful for developers needing to implement the
     * {@link org.apache.click.control.Container} interface but cannot for one
     * reason or another extend from {@link org.apache.click.control.AbstractContainer}.
     * For example if the Container already extends from an existing <tt>Control</tt>
     * such as a <tt>Field</tt>, it won't be possible to extend
     * <tt>AbstractContainer</tt> as well. In such scenarios instead of
     * reimplementing {@link org.apache.click.control.Container#replace(org.apache.click.Control, org.apache.click.Control) replace},
     * one can delegate to this method.
     * <p/>
     * For example, a custom Container that extends <tt>Field</tt> and
     * implements <tt>Container</tt> could implement the <tt>replace</tt> method
     * as follows:
     *
     * <pre class="prettyprint">
     * public class MyContainer extends Field implements Container {
     *
     *     public Control replace(Control currentControl, Control newControl) {
     *         int controlIndex = getControls().indexOf(currentControl);
     *         return ContainerUtils.replace(this, currentControl, newControl,
     *             controlIndex, getControlMap());
     *     }
     *
     *     ...
     * } </pre>
     *
     * @param container the container to insert the new control into
     * @param currentControl the control currently contained in the container
     * @param newControl the control to replace the current control contained in
     * the container
     * @param controlIndex the index of the current control in the container
     * @param controlMap the container's map of controls keyed on control name
     * @return the new control that replaced the current control
     *
     * @deprecated this method was used for stateful pages, which have been deprecated
     *
     * @throws IllegalArgumentException if the currentControl or newControl is
     * null
     * @throws IllegalStateException if the controlIndex = -1
     */
    public static Control replace(Container container, Control currentControl, Control newControl, int controlIndex,
            Map<String, Control> controlMap) {

        // Pre conditions start

        // Current and new control is the same instance - exit early
        if (currentControl == newControl) {
            return newControl;
        }

        if (currentControl == null) {
            throw new IllegalArgumentException("Null current control parameter");
        }
        if (newControl == null) {
            throw new IllegalArgumentException("Null new control parameter");
        }

        if (controlIndex == -1) {
            throw new IllegalStateException(
                    "Cannot replace the given control" + " because it is not present in the container");
        }

        // Pre conditions end

        // Check if control already has parent
        // If parent references the given container, there is no need to remove it
        Object currentParent = newControl.getParent();
        if (currentParent != null && currentParent != container) {

            // Remove new control from parent Page or Container
            if (currentParent instanceof Page) {
                ((Page) currentParent).removeControl(newControl);

            } else if (currentParent instanceof Container) {
                ((Container) currentParent).remove(newControl);
            }

            // Create warning message to users that the parent has been reset
            logParentReset(container, newControl, currentParent);
        }

        // Note: set parent first since setParent might veto further processing
        newControl.setParent(container);
        currentControl.setParent(null);

        // Replace currentControl with newControl
        container.getControls().set(controlIndex, newControl);

        // Update controlMap
        String controlName = newControl.getName();
        if (controlName != null) {
            controlMap.put(controlName, newControl);
        } else {
            controlName = currentControl.getName();

            if (controlName != null) {
                controlMap.remove(controlName);
            }
        }
        return newControl;
    }

    /**
     * Remove the given control from the container, returning <tt>true</tt> if
     * the control was found in the container and removed, or <tt>false</tt> if
     * the control was not found.
     * <p/>
     * This method is useful for developers needing to implement the
     * {@link org.apache.click.control.Container} interface but cannot for one
     * reason or another extend from {@link org.apache.click.control.AbstractContainer}.
     * For example if the Container already extends from an existing <tt>Control</tt>
     * such as a <tt>Field</tt>, it won't be possible to extend
     * <tt>AbstractContainer</tt> as well. In such scenarios instead of
     * reimplementing {@link org.apache.click.control.Container#remove(org.apache.click.Control) remove},
     * one can delegate to this method.
     * <p/>
     * For example, a custom Container that extends <tt>Field</tt> and
     * implements <tt>Container</tt> could implement the <tt>remove</tt> method
     * as follows:
     * <pre class="prettyprint">
     * public class MyContainer extends Field implements Container {
     *
     *     public boolean remove (Control control) {
     *         return ContainerUtils.remove(this, control, getControlMap());
     *     }
     *
     *     ...
     * } </pre>
     *
     * @param container the container to remove the given control from
     * @param control the control to remove from the container
     * @param controlMap the container's map of controls keyed on control name
     *
     * @return true if the control was removed from the container
     * @throws IllegalArgumentException if the control is null
     */
    public static boolean remove(Container container, Control control, Map<String, Control> controlMap) {

        if (control == null) {
            throw new IllegalArgumentException("Control cannot be null");
        }

        boolean contains = container.getControls().remove(control);

        if (contains) {
            // Only nullify if the container is parent. This check is for the
            // case where a Control has two parents e.g. Page and Form.
            // NOTE the current #insert logic does not allow Controls to have
            // two parents so this check might be redundant.
            if (control.getParent() == container) {
                control.setParent(null);
            }

            String controlName = control.getName();

            if (controlName != null) {
                controlMap.remove(controlName);
            }
        }

        return contains;
    }

    // -------------------------------------------------------- Private Methods

    /**
     * Extract and return the specified object property names.
     * <p/>
     * If the object is a Map instance, this method returns the maps key set.
     *
     * @param object the object to extract property names from
     * @return the unique set of property names
     */
    private static Set<String> getObjectPropertyNames(Object object) {
        if (object instanceof Map) {
            return ((Map) object).keySet();
        }

        Set<String> hashSet = new TreeSet<String>();

        Method[] methods = object.getClass().getMethods();

        for (Method method : methods) {
            String methodName = method.getName();

            if (methodName.startsWith("get") && methodName.length() > 3) {
                String propertyName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
                hashSet.add(propertyName);
            }
            if (methodName.startsWith("is") && methodName.length() > 2) {
                String propertyName = Character.toLowerCase(methodName.charAt(2)) + methodName.substring(3);
                hashSet.add(propertyName);
            }
            if (methodName.startsWith("set") && methodName.length() > 3) {
                String propertyName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
                hashSet.add(propertyName);
            }
        }

        return hashSet;
    }

    /**
     * Return true if the specified field name is contained within the
     * specified set of properties.
     *
     * @param field the field which name should be checked
     * @param properties set of properties to check
     * @return true if the specified field name is contained in the properties,
     * false otherwise
     */
    private static boolean hasMatchingProperty(Field field, Set<String> properties) {
        String fieldName = field.getName();
        if (fieldName.indexOf(".") != -1) {
            fieldName = fieldName.substring(0, fieldName.indexOf("."));
        }
        return properties.contains(fieldName);
    }

    /**
     * This method ensures that the object can safely be navigated according
     * to the specified path.
     * <p/>
     * If any object in the graph is null, a new instance of that object class
     * is instantiated.
     *
     * @param object the object which path must be navigable without
     * encountering null values
     * @param path the navigation path
     */
    private static void ensureObjectPathNotNull(Object object, String path) {

        final int index = path.indexOf('.');

        if (index == -1) {
            return;
        }

        String property = path.substring(0, index);
        Method getterMethod = findGetter(object, property, path);
        Object result = invokeGetter(getterMethod, object, property, path);

        if (result == null) {
            // Find the target class of the object in the path to create
            Class<?> targetClass = getterMethod.getReturnType();

            Constructor<?> constructor = null;
            try {
                // Lookup default no-arg constructor
                constructor = targetClass.getConstructor((Class[]) null);

            } catch (NoSuchMethodException e) {
                // Log detailed error message of looking up constructor failed
                HtmlStringBuffer buffer = new HtmlStringBuffer();
                logBasicDescription(buffer, object, path, property);
                buffer.append("Attempt to construct instance of class '");
                buffer.append(targetClass.getName()).append("' resulted in error: '");
                buffer.append(targetClass.getName()).append("' does not seem");
                buffer.append(" to have a default no argument constructor.");
                buffer.append(" Please note another common problem is that the");
                buffer.append(" class is either not public or not static.");
                throw new RuntimeException(buffer.toString(), e);
            }

            try {
                // Create target object instance
                result = constructor.newInstance(new Object[] {});

            } catch (Exception e) {
                // Log detailed error message of why creating target failed
                HtmlStringBuffer buffer = new HtmlStringBuffer();
                logBasicDescription(buffer, object, path, property);
                buffer.append("Result: could not create");
                buffer.append(" object with constructor '");
                buffer.append(constructor.getName()).append("'.");
                throw new RuntimeException(buffer.toString(), e);
            }

            Method setterMethod = findSetter(object, property, targetClass, path);
            invokeSetter(setterMethod, object, result, property, path);
        }

        String remainingPath = path.substring(index + 1);

        ensureObjectPathNotNull(result, remainingPath);
    }

    /**
     * Find the object getter method for the given property.
     * <p/>
     * If this method cannot find a 'get' property it tries to lookup an 'is'
     * property.
     *
     * @param object the object to find the getter method on
     * @param property the getter property name specifying the getter to lookup
     * @param path the full expression path (used for logging purposes)
     * @return the getter method
     */
    private static Method findGetter(Object object, String property, String path) {

        // Find the getter for property
        String getterName = ClickUtils.toGetterName(property);

        Method method = null;
        Class<?> sourceClass = object.getClass();

        try {
            method = sourceClass.getMethod(getterName, (Class[]) null);
        } catch (Exception e) {
        }

        if (method == null) {
            String isGetterName = ClickUtils.toIsGetterName(property);
            try {
                method = sourceClass.getMethod(isGetterName, (Class[]) null);
            } catch (Exception e) {
                HtmlStringBuffer buffer = new HtmlStringBuffer();
                logBasicDescription(buffer, object, path, property);
                buffer.append("Result: neither getter methods '");
                buffer.append(getterName).append("()' nor '");
                buffer.append(isGetterName).append("()' was found on class: '");
                buffer.append(object.getClass().getName()).append("'.");
                throw new RuntimeException(buffer.toString(), e);
            }
        }
        return method;
    }

    /**
     * Invoke the getterMethod for the given source object.
     *
     * @param getterMethod the getter method to invoke
     * @param source the source object to invoke the getter method on
     * @param property the getter method property name (used for logging)
     * @param path the full expression path (used for logging)
     * @return the getter result
     */
    private static Object invokeGetter(Method getterMethod, Object source, String property, String path) {

        try {
            // Retrieve target object from getter
            return getterMethod.invoke(source, new Object[0]);

        } catch (Exception e) {
            // Log detailed error message of why getter failed
            HtmlStringBuffer buffer = new HtmlStringBuffer();
            logBasicDescription(buffer, source, path, property);
            buffer.append("Result: error occurred while trying to get");
            buffer.append(" instance of '");
            buffer.append(getterMethod.getReturnType().getName());
            buffer.append("' using method: '");
            buffer.append(getterMethod.getName()).append("()' of class '");
            buffer.append(source.getClass().getName()).append("'.");
            throw new RuntimeException(buffer.toString(), e);
        }
    }

    /**
     * Find the source object setter method for the given property.
     *
     * @param source the source object to find the setter method on
     * @param property the property which setter needs to be looked up
     * @param targetClass the setter parameter type
     * @param path the full expression path (used for logging purposes)
     * @return the setter method
     */
    private static Method findSetter(Object source, String property, Class<?> targetClass, String path) {
        Method method = null;

        // Find the setter for property
        String setterName = ClickUtils.toSetterName(property);

        Class<?> sourceClass = source.getClass();
        Class<?>[] classArgs = { targetClass };
        try {
            method = sourceClass.getMethod(setterName, classArgs);
        } catch (Exception e) {
            // Log detailed error message of why setter lookup failed
            HtmlStringBuffer buffer = new HtmlStringBuffer();
            logBasicDescription(buffer, source, path, property);
            buffer.append("Result: setter method '");
            buffer.append(setterName).append("(").append(targetClass.getName());
            buffer.append(")' was not found on class '");
            buffer.append(source.getClass().getName()).append("'.");
            throw new RuntimeException(buffer.toString(), e);
        }
        return method;
    }

    /**
     * Invoke the setter method for the given source and target object.
     *
     * @param setterMethod the setter method to invoke
     * @param source the source object to invoke the setter method on
     * @param target the target object to set
     * @param property the setter method property name (used for logging)
     * @param path the full expression path (used for logging)
     */
    private static void invokeSetter(Method setterMethod, Object source, Object target, String property,
            String path) {

        try {
            Object[] objectArgs = { target };
            setterMethod.invoke(source, objectArgs);

        } catch (Exception e) {
            // Log detailed error message of why setter failed
            HtmlStringBuffer buffer = new HtmlStringBuffer();
            logBasicDescription(buffer, source, path, property);
            buffer.append("Result: error occurred while trying to set an");
            buffer.append(" instance of '");
            buffer.append(target.getClass().getName()).append("' using method '");
            buffer.append(setterMethod.getName()).append("(");
            buffer.append(target.getClass());
            buffer.append(")' of class '").append(source.getClass()).append("'.");
            throw new RuntimeException(buffer.toString(), e);
        }
    }

    /**
     * Log a generic error message to the specified buffer for the given object,
     * path and property.
     *
     * @param buffer the buffer to append log message to
     * @param object the active object when the exception occurred
     * @param path the current expression path
     * @param property the current property being processed
     */
    private static void logBasicDescription(HtmlStringBuffer buffer, Object object, String path, String property) {
        buffer.append("Invoked ensureObjectPathNotNull");
        buffer.append(" for class: '").append(object.getClass().getName());
        buffer.append("', path: '").append(path).append("' and property: '");
        buffer.append(property).append("'. ");
    }

    /**
     * Populate the given map from the values of the specified fieldList. The
     * map's key/value pairs are populated from the fields name/value. The keys
     * of the map are matched against each field name. If a key matches a field
     * name, the value of the field will be copied to the map.
     *
     * @param fieldList the forms list of fields to obtain field values from
     * @param map the map to populate with field values
     */
    private static void copyFieldsToMap(List<Field> fieldList, Map<String, Object> map) {

        LogService logService = ClickUtils.getLogService();

        String objectClassname = map.getClass().getName();
        objectClassname = objectClassname.substring(objectClassname.lastIndexOf(".") + 1);

        for (Field field : fieldList) {

            // Check if the map contains the fields name. The fields name can
            // also be a path for example 'foo.bar'
            String fieldName = field.getName();
            if (map.containsKey(fieldName)) {

                map.put(fieldName, field.getValueObject());

                if (logService.isDebugEnabled()) {
                    String msg = "   Form -> " + objectClassname + "." + fieldName + " : " + field.getValueObject();

                    logService.debug(msg);
                }
            }
        }
    }

    /**
     * Copy the map values to the specified fieldList. For every field in the
     * field list, a lookup is done in the map for a matching value. A match is
     * found if a field name matches against a key in the map. The matching
     * value is then copied to the field.
     *
     * @param map the map containing values to populate the fields with
     * @param fieldList the forms list of fields to be populated
     */
    private static void copyMapToFields(Map<String, Object> map, List<Field> fieldList) {

        LogService logService = ClickUtils.getLogService();

        String objectClassname = map.getClass().getName();
        objectClassname = objectClassname.substring(objectClassname.lastIndexOf(".") + 1);

        for (Field field : fieldList) {
            String fieldName = field.getName();

            // Check if the fieldName is contained in the map. For
            // example if a field has the name 'user.address', check if
            // 'user.address' is contained in the map.
            if (map.containsKey(fieldName)) {

                Object result = map.get(fieldName);

                field.setValueObject(result);

                if (logService.isDebugEnabled()) {
                    String msg = "   Form <- " + objectClassname + "." + fieldName + " : " + result;
                    logService.debug(msg);
                }
            }
        }
    }

    /**
     * Add buttons for the given Container to the specified buttons list,
     * recursively including any Fields contained in child containers. The list
     * of returned buttons will exclude any <tt>Button</tt> or <tt>Label</tt>
     * fields.
     *
     * @param container the container to obtain the fields from
     * @param buttons the list of contained fields
     */
    private static void addButtons(final Container container, final List<Button> buttons) {
        for (Control control : container.getControls()) {

            if (control instanceof Container) {
                // Include buttons that are containers
                if (control instanceof Button) {
                    buttons.add((Button) control);
                }
                Container childContainer = (Container) control;
                addButtons(childContainer, buttons);

            } else if (control instanceof Button) {
                buttons.add((Button) control);
            }
        }
    }

    /**
     * Add fields for the given Container to the specified field list,
     * recursively including any Fields contained in child containers.
     *
     * @param container the container to obtain the fields from
     * @param fields the list of contained fields
     */
    private static void addFields(final Container container, final List<Field> fields) {
        for (Control control : container.getControls()) {

            if (control instanceof Container) {
                // Include fields that are containers
                if (control instanceof Field) {
                    fields.add((Field) control);
                }
                Container childContainer = (Container) control;
                addFields(childContainer, fields);

            } else if (control instanceof Field) {
                fields.add((Field) control);
            }
        }
    }

    /**
     * Add input fields (TextField, TextArea, Select, Radio, Checkbox etc.) for
     * the given Container to the specified field list, recursively including
     * any Fields contained in child containers. The list of returned fields
     * will exclude any <tt>Button</tt>, <tt>FieldSet</tt> and <tt>Label</tt>
     * fields.
     *
     * @param container the container to obtain the fields from
     * @param fields the list of contained fields
     */
    private static void addInputFields(final Container container, final List<Field> fields) {
        for (Control control : container.getControls()) {

            if (control instanceof Label || control instanceof Button) {
                // Skip buttons and labels
                continue;

            } else if (control instanceof Container) {
                // Include fields but skip fieldSets
                if (control instanceof Field && !(control instanceof FieldSet)) {
                    fields.add((Field) control);
                }
                Container childContainer = (Container) control;
                addInputFields(childContainer, fields);

            } else if (control instanceof Field) {
                fields.add((Field) control);
            }
        }
    }

    /**
     * Add hidden fields for the given Container to the specified field list,
     * recursively including any Fields contained in child containers. The list
     * of returned fields will exclude any <tt>Button</tt>, <tt>FieldSet</tt>
     * and <tt>Label</tt> fields.
     *
     * @param container the container to obtain the hidden fields from
     * @param fields the list of contained fields
     */
    private static void addHiddenFields(final Container container, final List<Field> fields) {
        for (Control control : container.getControls()) {

            if (control instanceof Label || control instanceof Button) {
                // Skip buttons and labels
                continue;

            } else if (control instanceof Container) {
                // Include fields but skip fieldSets
                if (control instanceof Field && !(control instanceof FieldSet)) {
                    Field field = (Field) control;
                    if (field.isHidden()) {
                        fields.add((Field) control);
                    }
                }

                Container childContainer = (Container) control;
                addHiddenFields(childContainer, fields);

            } else if (control instanceof Field) {
                Field field = (Field) control;
                if (field.isHidden()) {
                    fields.add((Field) control);
                }
            }
        }
    }

    /**
     * Add fields for the container to the specified field list, recursively
     * including any Fields contained in child containers. The list
     * of returned fields will exclude any <tt>Button</tt> and <tt>FieldSet</tt>
     * fields.
     *
     * @param container the container to obtain the fields from
     * @param fields the list of contained fields
     */
    private static void addFieldsAndLabels(final Container container, final List<Field> fields) {
        for (Control control : container.getControls()) {

            if (control instanceof Button) {
                // Skip buttons
                continue;

            } else if (control instanceof Container) {
                // Include fields but skip fieldSets
                if (control instanceof Field && !(control instanceof FieldSet)) {
                    fields.add((Field) control);
                }
                Container childContainer = (Container) control;
                addFieldsAndLabels(childContainer, fields);

            } else if (control instanceof Field) {
                fields.add((Field) control);
            }
        }
    }

    /**
     * Add all the Fields for the given Container to the specified map,
     * recursively including any Fields contained in child containers.
     * <p/>
     * The map's key / value pair will consist of the control name and instance.
     *
     * @param container the container to obtain the fields from
     * @param fields the map of contained fields
     */
    private static void addFields(final Container container, final Map<String, Field> fields) {
        for (Control control : container.getControls()) {

            if (control instanceof Container) {
                // Include fields that are containers
                if (control instanceof Field) {
                    fields.put(control.getName(), (Field) control);
                }
                Container childContainer = (Container) control;
                addFields(childContainer, fields);

            } else if (control instanceof Field) {
                fields.put(control.getName(), (Field) control);
            }
        }
    }

    /**
     * Add the list of container fields to the specified list of fields, which
     * are not valid, not hidden and not disabled.
     * <p/>
     * The list of returned invalid fields will exclude any <tt>Button</tt>
     * fields.
     *
     * @param container the container to obtain the fields from
     * @param fields the map of contained fields
     */
    private static void addErrorFields(final Container container, final List<Field> fields) {
        for (Control control : container.getControls()) {

            if (control instanceof Button) {
                // Skip buttons
                continue;

            } else if (control instanceof Container) {
                if (control instanceof Field) {
                    Field field = (Field) control;
                    if (!field.isValid() && !field.isHidden() && !field.isDisabled()) {

                        fields.add((Field) control);
                    }
                }
                Container childContainer = (Container) control;
                addErrorFields(childContainer, fields);

            } else if (control instanceof Field) {
                Field field = (Field) control;
                if (!field.isValid() && !field.isHidden() && !field.isDisabled()) {

                    fields.add((Field) control);
                }
            }
        }
    }

    /**
     * Log a warning that the parent of the given control will be set to
     * the specified container.
     *
     * @param container the parent container
     * @param control the control which parent is being reset
     * @param currentParent the control current parent
     */
    private static void logParentReset(Container container, Control control, Object currentParent) {
        HtmlStringBuffer message = new HtmlStringBuffer();

        message.append("Changed ");
        message.append(ClassUtils.getShortClassName(control.getClass()));
        String controlId = control.getId();
        if (controlId != null) {
            message.append("[");
            message.append(controlId);
            message.append("]");
        } else {
            message.append("#");
            message.append(control.hashCode());
        }
        message.append(" parent from ");

        if (currentParent instanceof Page) {
            message.append(ClassUtils.getShortClassName(currentParent.getClass()));

        } else if (currentParent instanceof Container) {
            Container parentContainer = (Container) currentParent;

            message.append(ClassUtils.getShortClassName(parentContainer.getClass()));
            String parentId = parentContainer.getId();
            if (parentId != null) {
                message.append("[");
                message.append(parentId);
                message.append("]");
            } else {
                message.append("#");
                message.append(parentContainer.hashCode());
            }
        }

        message.append(" to ");
        message.append(ClassUtils.getShortClassName(container.getClass()));
        String id = container.getId();
        if (id != null) {
            message.append("[");
            message.append(id);
            message.append("]");
        } else {
            message.append("#");
            message.append(container.hashCode());
        }

        ClickUtils.getLogService().warn(message);
    }

    /**
     * This class fix an error in ognl's conversion of double->BigDecimal. The
     * default conversion uses BigDecimal(double), the fix is to use
     * BigDecimal.valueOf(double)
     *
     */
    private static class FixBigDecimalTypeConverter extends DefaultTypeConverter {
        @SuppressWarnings("unchecked")
        @Override
        public Object convertValue(Map context, Object value, Class toType) {
            if (value != null && toType == BigDecimal.class) {
                return bigDecValue(value);
            }
            return OgnlOps.convertValue(value, toType);
        }

        /**
        * Convert the given value into a BigDecimal.
        *
        * @param value the object to convert into a BigDecimal
        * @return the converted BigDecimal value
        */
        private BigDecimal bigDecValue(Object value) {
            if (value == null) {
                return BigDecimal.valueOf(0L);
            }
            Class<?> c = value.getClass();
            if (c == BigDecimal.class) {
                return (BigDecimal) value;
            }
            if (c == BigInteger.class) {
                return new BigDecimal((BigInteger) value);
            }
            if (c == Boolean.class) {
                return BigDecimal.valueOf(((Boolean) value).booleanValue() ? 1 : 0);
            }
            if (c == Character.class) {
                return BigDecimal.valueOf(((Character) value).charValue());
            }
            return new BigDecimal(value.toString().trim());
        }
    }
}