org.kuali.rice.krad.uif.util.ComponentUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.kuali.rice.krad.uif.util.ComponentUtils.java

Source

/**
 * Copyright 2005-2014 The Kuali Foundation
 *
 * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php
 *
 * 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.kuali.rice.krad.uif.util;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.kuali.rice.core.framework.util.ReflectionUtils;
import org.kuali.rice.krad.uif.UifConstants;
import org.kuali.rice.krad.uif.component.Component;
import org.kuali.rice.krad.uif.component.DataBinding;
import org.kuali.rice.krad.uif.component.Ordered;
import org.kuali.rice.krad.uif.container.CollectionGroup;
import org.kuali.rice.krad.uif.container.Container;
import org.kuali.rice.krad.uif.container.Group;
import org.kuali.rice.krad.uif.field.Field;
import org.kuali.rice.krad.uif.field.FieldGroup;
import org.kuali.rice.krad.uif.field.InputField;
import org.kuali.rice.krad.uif.layout.LayoutManager;
import org.kuali.rice.krad.uif.layout.TableLayoutManager;
import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleUtils;
import org.kuali.rice.krad.uif.lifecycle.initialize.AssignIdsTask;
import org.springframework.core.OrderComparator;

/**
 * ComponentUtils is a utility class providing methods to help create and modify {@link Component} instances.
 *
 * @author Kuali Rice Team (rice.collab@kuali.org)
 */
public class ComponentUtils {
    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ComponentUtils.class);

    private ComponentUtils() {
    }

    public static <T extends Component> T copy(T component) {
        return copy(component, null);
    }

    public static <T extends Component> T copy(T component, String idSuffix) {
        if (component == null) {
            return null;
        }

        T copy = CopyUtils.copy(component);

        if (StringUtils.isNotBlank(idSuffix)) {
            updateIdsWithSuffixNested(copy, idSuffix);
        }

        return copy;
    }

    /**
     * Copy a list of components
     * 
     * @param <T> component type
     * @param <T> component type
     * @param components the list of components to copy
     * @return the copied list
     */
    public static <T extends Component> List<T> copy(List<T> components) {
        if (components != null) {
            List<T> componentsCopy = new ArrayList<T>();
            for (T component : components) {
                T copiedComponent = copy(component);
                componentsCopy.add(copiedComponent);
            }

            return componentsCopy;
        }

        return new ArrayList<T>();
    }

    /**
     * Adjusts the ids to contain the given suffix and adds the giving binding prefix for the list of fields.
     *
     * @param <T> component type
     * @param fields list of fields to bind and id
     * @param addBindingPrefix prefix to add to the binding path
     * @param idSuffix id suffix
     */
    public static <T extends Field> void bindAndIdFieldList(List<T> fields, String addBindingPrefix,
            String idSuffix) {
        updateIdsWithSuffixNested(fields, idSuffix);
        prefixBindingPath(fields, addBindingPrefix);
    }

    public static <T extends Component> T copyComponent(T component, String addBindingPrefix, String idSuffix) {
        T copy = copy(component, idSuffix);

        prefixBindingPathNested(copy, addBindingPrefix);

        return copy;
    }

    public static <T extends Component> List<T> copyComponentList(List<T> components, String idSuffix) {
        if (components == null || components.isEmpty()) {
            return Collections.emptyList();
        }

        List<T> copiedComponentList = new ArrayList<T>(components.size());

        for (T field : components) {
            T copiedComponent = copy(field, idSuffix);
            copiedComponentList.add(copiedComponent);
        }

        return copiedComponentList;
    }

    public static <T extends Object> List<T> getComponentsOfType(List<? extends Component> items,
            Class<T> componentType) {
        if (items == null || items.isEmpty()) {
            return Collections.emptyList();
        }

        List<T> typeComponents = Collections.emptyList();

        for (Component component : items) {

            if (!componentType.isInstance(component)) {
                continue;
            }

            if (typeComponents.isEmpty()) {
                typeComponents = new ArrayList<T>(items.size());
            }

            typeComponents.add(componentType.cast(component));
        }

        return typeComponents;
    }

    /**
     * Gets all components of the give type that are within the items list of the container, or within a nested
     * container or field group.
     *
     * @param container container instance to pull components from
     * @param componentType type for components to pull
     * @param <T> type for component to pull
     * @return List of nested components with the given type
     */
    public static <T extends Component> List<T> getNestedContainerComponents(Container container,
            Class<T> componentType) {
        List<T> typeComponents = new ArrayList<T>();

        if (container == null) {
            return typeComponents;
        }

        for (Component item : container.getItems()) {
            if (item == null) {
                continue;
            }

            if (item instanceof Container) {
                typeComponents.addAll(getNestedContainerComponents((Container) item, componentType));
            } else if (item instanceof FieldGroup) {
                typeComponents.addAll(getNestedContainerComponents(((FieldGroup) item).getGroup(), componentType));
            } else if (componentType.isAssignableFrom(item.getClass())) {
                typeComponents.add(componentType.cast(item));
            }
        }

        return typeComponents;
    }

    public static <T extends Component> List<T> getNestedNonCollectionComponents(List<Component> items,
            Class<T> componentType) {
        List<T> typeComponents = new ArrayList<T>();

        if (items == null) {
            return typeComponents;
        }

        for (Component item : items) {
            if (item == null) {
                continue;
            }

            if (item instanceof Container && !(item instanceof CollectionGroup)) {
                typeComponents.addAll(getNestedNonCollectionComponents((Container) item, componentType));
            } else if (item instanceof FieldGroup) {
                typeComponents
                        .addAll(getNestedNonCollectionComponents(((FieldGroup) item).getGroup(), componentType));
            } else if (componentType.isAssignableFrom(item.getClass())) {
                typeComponents.add(componentType.cast(item));
            }
        }

        return typeComponents;
    }

    public static <T extends Component> List<T> getNestedNonCollectionComponents(Container container,
            Class<T> componentType) {
        List<T> typeComponents = new ArrayList<T>();

        if (container == null) {
            return typeComponents;
        }

        for (Component item : container.getItems()) {
            if (item == null) {
                continue;
            }

            if (item instanceof Container && !(item instanceof CollectionGroup)) {
                typeComponents.addAll(getNestedNonCollectionComponents((Container) item, componentType));
            } else if (item instanceof FieldGroup) {
                typeComponents
                        .addAll(getNestedNonCollectionComponents(((FieldGroup) item).getGroup(), componentType));
            } else if (componentType.isAssignableFrom(item.getClass())) {
                typeComponents.add(componentType.cast(item));
            }
        }

        return typeComponents;
    }

    /**
     * Get all nested children of a given component.
     *
     * @param component The component to search.
     * @return All nested children of the component.
     * @see ViewLifecycleUtils#getElementsForLifecycle(LifecycleElement)
     */
    public static List<Component> getAllNestedComponents(Component component) {
        if (component == null) {
            return Collections.emptyList();
        }

        List<Component> components = Collections.emptyList();
        @SuppressWarnings("unchecked")
        Queue<LifecycleElement> elementQueue = RecycleUtils.getInstance(LinkedList.class);
        elementQueue.offer(component);

        try {
            while (!elementQueue.isEmpty()) {
                LifecycleElement currentElement = elementQueue.poll();

                if (currentElement == null) {
                    continue;
                }

                if (currentElement instanceof Component && currentElement != component) {
                    if (components.isEmpty()) {
                        components = new ArrayList<Component>();
                    }

                    components.add((Component) currentElement);
                }

                elementQueue.addAll(ViewLifecycleUtils.getElementsForLifecycle(currentElement).values());
            }
        } finally {
            elementQueue.clear();
            RecycleUtils.recycle(elementQueue);
        }

        return components;
    }

    /**
     * Searches for the component with the given id within the given list of components
     *
     * @param components list of components to search through
     * @param componentId id for the component to find
     * @return component found in the list or null
     */
    public static Component findComponentInList(List<Component> components, String componentId) {
        for (Component component : components) {
            if (component != null && StringUtils.equals(component.getId(), componentId)) {
                return component;
            }
        }

        return null;
    }

    public static void prefixBindingPath(List<? extends Component> components, String addBindingPrefix) {
        for (Component component : components) {
            if (component instanceof DataBinding) {
                prefixBindingPath((DataBinding) component, addBindingPrefix);
            } else if ((component instanceof FieldGroup) && (((FieldGroup) component).getItems() != null)) {
                List<? extends Component> fieldGroupItems = ((FieldGroup) component).getItems();
                prefixBindingPath(fieldGroupItems, addBindingPrefix);

                //                List<Field> groupFields = ViewLifecycleUtils
                //                        .getElementsOfTypeDeep(((FieldGroup) field).getItems(), Field.class);
                //                prefixBindingPath(groupFields, addBindingPrefix);
            } else if ((component instanceof Group) && (((Group) component).getItems() != null)
                    && !(component instanceof CollectionGroup)) {
                List<? extends Component> groupItems = ((Group) component).getItems();
                prefixBindingPath(groupItems, addBindingPrefix);
            }
        }
    }

    public static void prefixBindingPathNested(Component component, String addBindingPrefix) {
        @SuppressWarnings("unchecked")
        Queue<LifecycleElement> elementQueue = RecycleUtils.getInstance(LinkedList.class);
        elementQueue.offer(component);

        try {
            while (!elementQueue.isEmpty()) {
                LifecycleElement currentElement = elementQueue.poll();
                if (currentElement == null) {
                    continue;
                }

                if (currentElement instanceof DataBinding) {
                    if (LOG.isDebugEnabled()) {
                        LOG.info("setting nested binding prefix '" + addBindingPrefix + "' on " + currentElement);
                    }
                    prefixBindingPath((DataBinding) currentElement, addBindingPrefix);
                }

                elementQueue.addAll(ViewLifecycleUtils.getElementsForLifecycle(currentElement).values());
            }
        } finally {
            elementQueue.clear();
            RecycleUtils.recycle(elementQueue);
        }
    }

    public static void prefixBindingPath(DataBinding field, String addBindingPrefix) {
        String bindingPrefix = addBindingPrefix;
        if (StringUtils.isNotBlank(field.getBindingInfo().getBindByNamePrefix())) {
            bindingPrefix += "." + field.getBindingInfo().getBindByNamePrefix();
        }
        field.getBindingInfo().setBindByNamePrefix(bindingPrefix);
    }

    public static void updateIdsWithSuffixNested(List<? extends Component> components, String idSuffix) {
        for (Component component : components) {
            updateIdsWithSuffixNested(component, idSuffix);
        }
    }

    public static void updateIdsWithSuffixNested(Component component, String idSuffix) {
        updateIdWithSuffix(component, idSuffix);

        updateChildIdsWithSuffixNested(component, idSuffix);
    }

    public static void updateChildIdsWithSuffixNested(Component component, String idSuffix) {
        @SuppressWarnings("unchecked")
        Queue<LifecycleElement> elementQueue = RecycleUtils.getInstance(LinkedList.class);
        try {
            elementQueue.addAll(ViewLifecycleUtils.getElementsForLifecycle(component).values());

            while (!elementQueue.isEmpty()) {
                LifecycleElement currentElement = elementQueue.poll();
                if (currentElement == null) {
                    continue;
                }

                if (currentElement instanceof Component) {
                    updateIdWithSuffix((Component) currentElement, idSuffix);
                    elementQueue.addAll(((Component) currentElement).getPropertyReplacerComponents());
                }

                elementQueue.addAll(ViewLifecycleUtils.getElementsForLifecycle(currentElement).values());
            }
        } finally {
            elementQueue.clear();
            RecycleUtils.recycle(elementQueue);
        }
    }

    /**
     * Generate a hash code unique within the current view for a single lifecycle element. A unique
     * ID value will be assigned to the lifecycle element, replacing the current ID.
     *
     * <p>This method may only be called during the view lifecycle.</p>
     *
     * @param element The element to generate a hash code for.
     * @param seed A hash value to use as a seed for the new hash.
     * @return A hash code based on the provided element and seed value.
     * @see AssignIdsTask For a complete description of the algorithm. This method implements a
     * single step in the algorithm described in that task.
     */
    public static int generateId(LifecycleElement element, int seed) {
        if (element == null) {
            return seed;
        }

        final int prime = 6971;
        int hash = prime * seed + element.getClass().getName().hashCode();

        String id = element.getId();
        hash *= prime;
        if (id != null) {
            hash += id.hashCode();
        }

        do {
            hash *= 4507;
            id = Long.toString(((long) hash) - ((long) Integer.MIN_VALUE), 36);
        } while (!ViewLifecycle.getView().getViewIndex().observeAssignedId(id));

        element.setId(UifConstants.COMPONENT_ID_PREFIX + id);

        return hash;
    }

    /**
     * Replace all IDs from a component and its children with new generated ID values.
     *
     * <p>If there are features that depend on a static id of this
     * component, this call may cause errors.</p>
     *
     * @param components A list of component to clear all IDs from.
     * @see AssignIdsTask For a complete description of the algorithm.
     */
    public static void clearAndAssignIds(List<? extends Component> components) {
        if (components == null || components.isEmpty()) {
            return;
        }

        int hash = 1;
        @SuppressWarnings("unchecked")
        Queue<LifecycleElement> toClear = RecycleUtils.getInstance(LinkedList.class);
        toClear.addAll(components);
        try {
            while (!toClear.isEmpty()) {
                LifecycleElement element = toClear.poll();

                hash = generateId(element, hash);

                for (LifecycleElement nested : ViewLifecycleUtils.getElementsForLifecycle(element).values()) {
                    if (nested != null) {
                        toClear.add(nested);
                    }
                }

                if (element instanceof Component) {
                    List<Component> propertyReplacerComponents = ((Component) element)
                            .getPropertyReplacerComponents();
                    if (propertyReplacerComponents == null) {
                        continue;
                    }

                    for (Component nested : propertyReplacerComponents) {
                        if (nested != null) {
                            toClear.add(nested);
                        }
                    }
                }
            }
        } finally {
            toClear.clear();
            RecycleUtils.recycle(toClear);
        }
    }

    /**
     * add a suffix to the id
     *
     * @param element the component instance whose id will be changed
     * @param idSuffix the suffix to be appended
     */
    public static void updateIdWithSuffix(LifecycleElement element, String idSuffix) {
        if (element != null && !StringUtils.isEmpty(idSuffix)) {
            element.setId(element.getId() + idSuffix);
        }

        if (element instanceof Container) {
            LayoutManager manager = ((Container) element).getLayoutManager();
            if (manager != null) {
                manager.setId(manager.getId() + idSuffix);
            }
        }
    }

    /**
     * Traverse a component tree, setting a property on all components for which the property is writable.
     *
     * @param <T> component type
     * @param <T> component type
     * @param components The components to traverse.
     * @param propertyPath The property path to set.
     * @param propertyValue The property value to set.
     * @see ObjectPropertyUtils#isWritableProperty(Object, String)
     * @see ObjectPropertyUtils#setPropertyValue(Object, String, Object)
     */
    public static <T extends Component> void setComponentsPropertyDeep(List<T> components, String propertyPath,
            Object propertyValue) {
        if (components == null || components.isEmpty()) {
            return;
        }

        Set<Class<?>> skipTypes = null;
        @SuppressWarnings("unchecked")
        Queue<LifecycleElement> elementQueue = RecycleUtils.getInstance(LinkedList.class);
        elementQueue.addAll(components);

        try {
            while (!elementQueue.isEmpty()) {
                LifecycleElement currentElement = elementQueue.poll();
                if (currentElement == null) {
                    continue;
                }

                elementQueue.addAll(ViewLifecycleUtils.getElementsForLifecycle(currentElement).values());

                Class<?> componentClass = currentElement.getClass();
                if (skipTypes != null && skipTypes.contains(componentClass)) {
                    continue;
                }

                if (!ObjectPropertyUtils.isWritableProperty(currentElement, propertyPath)) {
                    if (skipTypes == null) {
                        skipTypes = new HashSet<Class<?>>();
                    }
                    skipTypes.add(componentClass);
                    continue;
                }

                ObjectPropertyUtils.setPropertyValue(currentElement, propertyPath, propertyValue, true);
            }
        } finally {
            elementQueue.clear();
            RecycleUtils.recycle(elementQueue);
        }
    }

    /**
     * Traverse a component tree, setting a property on all components for which the property is writable.
     *
     * @param component The component to traverse.
     * @param propertyPath The property path to set.
     * @param propertyValue The property value to set.
     * @see ObjectPropertyUtils#isWritableProperty(Object, String)
     * @see ObjectPropertyUtils#setPropertyValue(Object, String, Object)
     */
    public static void setComponentPropertyDeep(Component component, String propertyPath, Object propertyValue) {
        setComponentsPropertyDeep(Collections.singletonList(component), propertyPath, propertyValue);
    }

    /**
     * Sets a property on the given component and removes any expressions for that property so the value is not
     * overridden
     *
     * @param component component instance to set property on
     * @param propertyName name of property to set
     * @param propertyValue value to set property to
     */
    public static void setComponentPropertyFinal(Component component, String propertyName, Object propertyValue) {
        if (component == null) {
            return;
        }

        ObjectPropertyUtils.setPropertyValue(component, propertyName, propertyValue);

        if ((component.getPropertyExpressions() != null)
                && component.getPropertyExpressions().containsKey(propertyName)) {
            component.getPropertyExpressions().remove(propertyName);
        }
    }

    /**
     * Indicates if the given component has configuration that it allows it to be refreshed.
     *
     * @param component instance to check
     * @return true if component can be refreshed, false if not
     */
    public static boolean canBeRefreshed(Component component) {
        boolean hasRefreshCondition = StringUtils.isNotBlank(component.getProgressiveRender())
                || StringUtils.isNotBlank(component.getConditionalRefresh()) || (component.getRefreshTimer() > 0)
                || (component.getRefreshWhenChangedPropertyNames() != null
                        && !component.getRefreshWhenChangedPropertyNames().isEmpty());

        boolean isInlineEditField = component instanceof InputField
                && (((InputField) component).isInlineEdit() || ((InputField) component).isAjaxInlineEdit());

        return hasRefreshCondition || component.isRefreshedByAction() || component.isDisclosedByAction()
                || component.isRetrieveViaAjax() || isInlineEditField;
    }

    /**
     * places a key, value pair in each context map of a list of components
     *
     * @param elements the list of elements
     * @param contextName a value to be used as a key to retrieve the object
     * @param contextValue the value to be placed in the context
     */
    public static void pushObjectToContext(Collection<? extends LifecycleElement> elements, String contextName,
            Object contextValue) {
        if (elements == null || elements.isEmpty()) {
            return;
        }

        Queue<LifecycleElement> elementQueue = new LinkedList<LifecycleElement>();

        try {
            elementQueue.addAll(elements);
            while (!elementQueue.isEmpty()) {
                LifecycleElement currentElement = elementQueue.poll();

                if (currentElement == null) {
                    continue;
                }

                if (currentElement instanceof Component) {
                    ((Component) currentElement).pushObjectToContext(contextName, contextValue);
                }

                elementQueue.addAll(ViewLifecycleUtils.getElementsForLifecycle(currentElement).values());
            }
        } finally {
            elementQueue.clear();
            RecycleUtils.recycle(elementQueue);
        }
    }

    /**
     * pushes object to a component's context so that it is available from {@link Component#getContext()}
     *
     * <p>The component's nested components that are available via {@code Component#getComponentsForLifecycle}
     * are also updated recursively</p>
     *
     * @param component the component whose context is to be updated
     * @param contextName a value to be used as a key to retrieve the object
     * @param contextValue the value to be placed in the context
     */
    public static void pushObjectToContext(Component component, String contextName, Object contextValue) {
        if (component == null) {
            return;
        }

        pushObjectToContext(Collections.singletonList(component), contextName, contextValue);
    }

    /**
     * places a all entries from a map into each context map of a list of components
     *
     * @param components The list components.
     * @param sourceContext The source context map.
     */
    public static void pushAllToContext(List<? extends Component> components, Map<String, Object> sourceContext) {
        if (components == null || components.isEmpty()) {
            return;
        }

        @SuppressWarnings("unchecked")
        Queue<LifecycleElement> elementQueue = RecycleUtils.getInstance(LinkedList.class);
        try {
            elementQueue.addAll(components);
            while (!elementQueue.isEmpty()) {
                LifecycleElement currentElement = elementQueue.poll();

                if (currentElement == null) {
                    continue;
                }

                if (currentElement instanceof Component) {
                    ((Component) currentElement).pushAllToContext(sourceContext);
                }

                elementQueue.addAll(ViewLifecycleUtils.getElementsForLifecycle(currentElement).values());
            }
        } finally {
            elementQueue.clear();
            RecycleUtils.recycle(elementQueue);
        }
    }

    /**
     * pushes object to a component's context so that it is available from {@link Component#getContext()}
     *
     * <p>The component's nested components that are available via {@code Component#getComponentsForLifecycle}
     * are also updated recursively</p>
     *
     * @param component the component whose context is to be updated
     * @param sourceContext The source context map.
     */
    public static void pushAllToContext(Component component, Map<String, Object> sourceContext) {
        if (component == null) {
            return;
        }

        pushAllToContext(Collections.singletonList(component), sourceContext);
    }

    /**
     * Update the contexts of the given components.
     *
     * <p>Calls {@link ComponentUtils#updateContextForLine} for each component</p>
     *
     * @param components the components whose components to update
     * @param collectionGroup collection group the components are associated with
     * @param collectionLine an instance of the data object for the line
     * @param lineIndex the line index
     * @param lineSuffix id suffix for components in the line to make them unique
     */
    public static void updateContextsForLine(List<? extends Component> components, CollectionGroup collectionGroup,
            Object collectionLine, int lineIndex, String lineSuffix) {
        for (Component component : components) {
            updateContextForLine(component, collectionGroup, collectionLine, lineIndex, lineSuffix);
        }
    }

    /**
     * update the context map for the given component
     *
     * <p>The values of {@code UifConstants.ContextVariableNames.LINE} and {@code UifConstants.ContextVariableNames.INDEX}
     * are set to {@code collectionLine} and {@code lineIndex} respectively.</p>
     *
     * @param component the component whose context is to be updated
     * @param collectionGroup collection group the component is associated with
     * @param collectionLine an instance of the data object for the line
     * @param lineIndex the line index
     * @param lineSuffix id suffix for components in the line, not if the collection group has a container id suffix
     * it will be appended to the lineSuffix for the final exported context entry
     */
    public static void updateContextForLine(Component component, CollectionGroup collectionGroup,
            Object collectionLine, int lineIndex, String lineSuffix) {
        // line id suffix that we export as expression variable will contain the container suffix as well
        // so correct references to ids for line components can be configured in the XML
        if (StringUtils.isNotBlank(collectionGroup.getContainerIdSuffix())) {
            lineSuffix = lineSuffix + collectionGroup.getContainerIdSuffix();
        }

        Map<String, Object> toUpdate = new HashMap<String, Object>(5);
        toUpdate.put(UifConstants.ContextVariableNames.COLLECTION_GROUP, collectionGroup);
        toUpdate.put(UifConstants.ContextVariableNames.LINE, collectionLine);
        toUpdate.put(UifConstants.ContextVariableNames.INDEX, Integer.valueOf(lineIndex));
        toUpdate.put(UifConstants.ContextVariableNames.LINE_SUFFIX, lineSuffix);

        boolean isAddLine = (lineIndex == -1);
        toUpdate.put(UifConstants.ContextVariableNames.IS_ADD_LINE, isAddLine);
        pushAllToContext(component, toUpdate);
    }

    /**
     * Sets the context of the given lifecycle element to null, then using reflection recursively finds any
     * lifecycle element children and sets their context to null.
     *
     * @param lifecycleElement lifecycle element instance to clean
     */
    public static void cleanContextDeap(LifecycleElement lifecycleElement) {
        if (lifecycleElement == null) {
            return;
        }

        lifecycleElement.setContext(null);

        // find any children that are lifecycle elements and clean them as well
        Class<?> elementClass = lifecycleElement.getClass();

        List<java.lang.reflect.Field> fields = ReflectionUtils.getAllFields(elementClass);
        for (java.lang.reflect.Field field : fields) {
            // Check for lists that can contain lifecycle elements
            if (Collection.class.isAssignableFrom(field.getType())) {
                ReflectionUtils.makeAccessible(field);
                Collection<Object> elements = (Collection<Object>) ReflectionUtils.getField(field,
                        lifecycleElement);
                if (elements != null) {
                    for (Object element : elements) {
                        if (element != null && LifecycleElement.class.isAssignableFrom(element.getClass())) {
                            cleanContextDeap((LifecycleElement) element);
                        }
                    }
                }
                // Check for Maps that can contain lifecycle elements
            } else if (Map.class.isAssignableFrom(field.getType())) {
                ReflectionUtils.makeAccessible(field);
                Map<Object, Object> elements = (Map<Object, Object>) ReflectionUtils.getField(field,
                        lifecycleElement);
                if (elements != null) {
                    for (Object element : elements.entrySet()) {
                        if (element != null && LifecycleElement.class.isAssignableFrom(element.getClass())) {
                            cleanContextDeap((LifecycleElement) element);
                        }
                    }
                }
                // Check if field is a lifecycle element itself
            } else if (LifecycleElement.class.isAssignableFrom(field.getType())) {
                ReflectionUtils.makeAccessible(field);
                LifecycleElement nestedElement = (LifecycleElement) ReflectionUtils.getField(field,
                        lifecycleElement);

                cleanContextDeap(nestedElement);
            }
        }
    }

    /**
     * Performs sorting logic of the given list of <code>Ordered</code>
     * instances by its order property
     *
     * <p>
     * Items list is sorted based on its order property. Lower order values are
     * placed higher in the list. If a item does not have a value assigned for
     * the order (or is equal to the default order of 0), it will be assigned
     * the a value based on the given order sequence integer. If two or more
     * items share the same order value, all but the last item found in the list
     * will be removed.
     * </p>
     * 
     * @param <T> ordered type
     * @param <T> ordered type
     * @param items
     * @param defaultOrderSequence
     * @return List<Ordered> sorted items
     * @see org.kuali.rice.krad.uif.component.Component#getOrder()
     * @see org.springframework.core.Ordered
     */
    public static <T extends Ordered> List<T> sort(List<T> items, int defaultOrderSequence) {
        if (items == null) {
            return null;
        }

        List<T> orderedItems = new ArrayList<T>(items.size());

        // do replacement for items with the same order property value
        Set<Integer> foundOrders = new HashSet<Integer>();

        // reverse the list, so items later in the list win
        for (int i = items.size() - 1; i >= 0; i--) {
            T component = items.get(i);
            int order = component.getOrder();

            // if order not set just add to list
            if (order == 0) {
                orderedItems.add(component);
            }
            // check if the order value has been used already
            else if (!foundOrders.contains(Integer.valueOf(order))) {
                orderedItems.add(component);
                foundOrders.add(Integer.valueOf(order));
            }
        }

        // now reverse the list back so we can assign defaults for items without
        // an order value
        for (int i = 0; i < items.size(); i++) {
            Ordered component = items.get(i);
            int order = component.getOrder();

            // if order property not set assign default
            if (order == 0) {
                defaultOrderSequence++;
                while (foundOrders.contains(Integer.valueOf(defaultOrderSequence))) {
                    defaultOrderSequence++;
                }
                component.setOrder(defaultOrderSequence);
            }
        }

        // now sort the list by its order property
        Collections.sort(orderedItems, new OrderComparator());

        return orderedItems;
    }

    /**
     * Gets all the input fields contained in this container, but also in
     * every sub-container that is a child of this container.  When called from the top level
     * View this will be every InputField across all pages.
     *
     * @param container container to scan for input fields
     * @return every InputField that is a child at any level of this container
     */
    public static List<InputField> getAllInputFieldsWithinContainer(Container container) {
        List<InputField> inputFields = new ArrayList<InputField>();

        for (LifecycleElement c : ViewLifecycleUtils.getElementsForLifecycle(container).values()) {
            if (c instanceof InputField) {
                inputFields.add((InputField) c);
            } else if (c instanceof Container) {
                inputFields.addAll(getAllInputFieldsWithinContainer((Container) c));
            } else if (c instanceof FieldGroup) {
                Container cb = ((FieldGroup) c).getGroup();

                inputFields.addAll(getAllInputFieldsWithinContainer(cb));
            }
        }

        return inputFields;
    }

    /**
     * Determines whether the given component contains an expression for the given property name
     *
     * @param component component instance to check for expressions
     * @param propertyName name of the property to determine if there is an expression for
     * @param collectionMatch if set to true will find an expressions for properties that start with the given
     * property name (for matching expressions on collections like prop[index] or prop['key'])
     * @return true if the component has an expression for the property name, false if not
     */
    public static boolean containsPropertyExpression(Component component, String propertyName,
            boolean collectionMatch) {
        boolean hasExpression = false;

        Map<String, String> propertyExpressions = component.getPropertyExpressions();

        if (collectionMatch) {
            for (String expressionPropertyName : propertyExpressions.keySet()) {
                if (expressionPropertyName.startsWith(propertyName)) {
                    hasExpression = true;
                }
            }
        } else if (propertyExpressions.containsKey(propertyName)) {
            hasExpression = true;
        }

        return hasExpression;
    }

    /**
     * Adjust nestingLevel properties for collections which use RichTable with forceLocalJsonData on and for all of its
     * potentially additional nested subcollections
     *
     * @param container container to traverse and update nested levels in
     * @param currentLevel the current nesting level, the initial call to this method should be 0
     */
    public static void adjustNestedLevelsForTableCollections(Container container, int currentLevel) {
        if (container != null && container instanceof CollectionGroup && container.getLayoutManager() != null
                && container.getLayoutManager() instanceof TableLayoutManager
                && ((TableLayoutManager) container.getLayoutManager()).getRichTable() != null
                && ((TableLayoutManager) container.getLayoutManager()).getRichTable().isRender()
                && ((TableLayoutManager) container.getLayoutManager()).getRichTable().isForceLocalJsonData()) {
            ((TableLayoutManager) container.getLayoutManager()).getRichTable().setNestedLevel(currentLevel);
            currentLevel++;
        }

        if (container != null) {
            List<Container> subContainers = ViewLifecycleUtils.getNestedElementsOfTypeShallow(container,
                    Container.class);
            for (Container subContainer : subContainers) {
                adjustNestedLevelsForTableCollections(subContainer, currentLevel);
            }

            List<FieldGroup> subFieldGroups = ViewLifecycleUtils.getNestedElementsOfTypeShallow(container,
                    FieldGroup.class);
            for (FieldGroup fieldGroup : subFieldGroups) {
                if (fieldGroup != null) {
                    adjustNestedLevelsForTableCollections(fieldGroup.getGroup(), currentLevel);
                }
            }
        }
    }

}