Package org.apache.click.util

Source Code of org.apache.click.util.ContainerUtils

/*
* 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.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.click.control.Container;
import org.apache.click.Control;
import org.apache.click.Page;
import org.apache.click.control.Button;
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 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 properties = getObjectPropertyNames(object);
        Map ognlContext = new HashMap();

        for (int i = 0,  size = fieldList.size(); i < size; i++) {
            Field field = (Field) fieldList.get(i);

            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) {
        final List 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 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 properties = getObjectPropertyNames(object);

        LogService logService = ClickUtils.getLogService();

        for (int i = 0,  size = fieldList.size(); i < size; i++) {
            Field field = (Field) fieldList.get(i);

            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) {

        final List 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 = (Control) container.getControl(name);

        if (control != null) {
            return control;

        } else {
            for (int i = 0; i < container.getControls().size(); i++) {
                Control childControl = (Control) container.getControls().get(i);

                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 getButtons(final Container container) {
        if (container == null) {
            throw new IllegalArgumentException("Null container parameter");
        }

        final List buttons = new ArrayList();
        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 getErrorFields(final Container container) {
        if (container == null) {
            throw new IllegalArgumentException("Null container parameter");
        }

        final List fields = new ArrayList();
        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 getFieldMap(final Container container) {
        if (container == null) {
            throw new IllegalArgumentException("Null container parameter");
        }

        final Map fields = new HashMap();
        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 getFields(final Container container) {
        if (container == null) {
            throw new IllegalArgumentException("Null container parameter");
        }

        final List fields = new ArrayList();
        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 getFieldsAndLabels(final Container container) {
        if (container == null) {
            throw new IllegalArgumentException("Null container parameter");
        }

        final List fields = new ArrayList();
        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 getHiddenFields(final Container container) {
        if (container == null) {
            throw new IllegalArgumentException("Null container parameter");
        }

        final List fields = new ArrayList();
        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 getInputFields(final Container container) {
        if (container == null) {
            throw new IllegalArgumentException("Null container parameter");
        }

        final List fields = new ArrayList();
        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> if the specified control already has a parent assigned,
     * it will automatically be removed from that parent and inserted into the
     * container.
     * <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 or the container already contains
     * a control with the same name or if a Field name is not defined
     *
     * @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 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;
    }

    /**
     * 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 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 getObjectPropertyNames(Object object) {
        if (object instanceof Map) {
            return ((Map) object).keySet();
        }

        HashSet hashSet = new HashSet();

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

        for (int i = 0; i < methods.length; i++) {
            String methodName = methods[i].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 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 navigatable 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 constrcutor.");
                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 final 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 final 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 final 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 final 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
    * @param debug log debug statements when populating the map
    */
    private static void copyFieldsToMap(List fieldList, Map map) {

        LogService logService = ClickUtils.getLogService();

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

        for (int i = 0, size = fieldList.size(); i < size; i++) {
            Field field = (Field) fieldList.get(i);

            // 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
     * @param debug log debug statements when populating the object
     */
    private static void copyMapToFields(Map map, List fieldList) {

        LogService logService = ClickUtils.getLogService();

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

        for (int i = 0, size = fieldList.size(); i < size; i++) {
            Field field = (Field) fieldList.get(i);
            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 buttons) {
        for (int i = 0; i < container.getControls().size(); i++) {
            Control control = (Control) container.getControls().get(i);
            if (control instanceof Container) {
                // Include buttons that are containers
                if (control instanceof Button) {
                    buttons.add(control);
                }
                Container childContainer = (Container) control;
                addButtons(childContainer, buttons);
            } else if (control instanceof Button) {
                buttons.add(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 the list of contained fields
     */
    private static void addFields(final Container container, final List fields) {
        for (int i = 0; i < container.getControls().size(); i++) {
            Control control = (Control) container.getControls().get(i);
            if (control instanceof Container) {
                // Include fields that are containers
                if (control instanceof Field) {
                    fields.add(control);
                }
                Container childContainer = (Container) control;
                addFields(childContainer, fields);
            } else if (control instanceof Field) {
                fields.add(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 the list of contained fields
     */
    private static void addInputFields(final Container container, final List fields) {
        for (int i = 0; i < container.getControls().size(); i++) {
            Control control = (Control) container.getControls().get(i);
            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(control);
                }
                Container childContainer = (Container) control;
                addInputFields(childContainer, fields);

            } else if (control instanceof Field) {
                fields.add(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 the list of contained fields
     */
    private static void addHiddenFields(final Container container, final List fields) {
        for (int i = 0; i < container.getControls().size(); i++) {
            Control control = (Control) container.getControls().get(i);
            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(control);
                    }
                }

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

            } else if (control instanceof Field) {
                Field field = (Field) control;
                if (field.isHidden()) {
                    fields.add(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 the list of contained fields
     */
    private static void addFieldsAndLabels(final Container container, final List fields) {
        for (int i = 0; i < container.getControls().size(); i++) {
            Control control = (Control) container.getControls().get(i);
            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(control);
                }
                Container childContainer = (Container) control;
                addFieldsAndLabels(childContainer, fields);
            } else if (control instanceof Field) {
                fields.add(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 the map of contained fields
     */
    private static void addFields(final Container container, final Map fields) {
        for (int i = 0; i < container.getControls().size(); i++) {
            Control control = (Control) container.getControls().get(i);
            if (control instanceof Container) {
                // Include fields that are containers
                if (control instanceof Field) {
                    fields.put(control.getName(), control);
                }
                Container childContainer = (Container) control;
                addFields(childContainer, fields);
            } else if (control instanceof Field) {
                fields.put(control.getName(), 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
     * @return list of form fields which are not valid, not hidden and not
     *  disabled
     */
    private static void addErrorFields(final Container container, final List fields) {
        for (int i = 0; i < container.getControls().size(); i++) {
            Control control = (Control) container.getControls().get(i);
            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(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(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);
    }
}
TOP

Related Classes of org.apache.click.util.ContainerUtils

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.