View Javadoc

1   /**
2    * Copyright 2005-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.uif.util;
17  
18  import java.beans.PropertyDescriptor;
19  import java.util.ArrayList;
20  import java.util.Collections;
21  import java.util.HashMap;
22  import java.util.HashSet;
23  import java.util.LinkedList;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Queue;
27  import java.util.Set;
28  
29  import org.apache.commons.lang.StringUtils;
30  import org.kuali.rice.krad.uif.UifConstants;
31  import org.kuali.rice.krad.uif.component.Component;
32  import org.kuali.rice.krad.uif.component.DataBinding;
33  import org.kuali.rice.krad.uif.component.Ordered;
34  import org.kuali.rice.krad.uif.container.CollectionGroup;
35  import org.kuali.rice.krad.uif.container.Container;
36  import org.kuali.rice.krad.uif.field.Field;
37  import org.kuali.rice.krad.uif.field.FieldGroup;
38  import org.kuali.rice.krad.uif.field.InputField;
39  import org.kuali.rice.krad.uif.layout.LayoutManager;
40  import org.kuali.rice.krad.uif.layout.TableLayoutManager;
41  import org.springframework.beans.BeanUtils;
42  import org.springframework.core.OrderComparator;
43  
44  /**
45   * ComponentUtils is a utility class providing methods to help create and modify <code>Component</code> instances
46   *
47   * @author Kuali Rice Team (rice.collab@kuali.org)
48   */
49  public class ComponentUtils {
50  
51      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ComponentUtils.class);
52  
53      public static <T extends Component> T copy(T component) {
54          return copy(component, null);
55      }
56  
57      public static <T extends Component> T copy(T component, String idSuffix) {
58          T copy = component.copy();
59  
60          if (StringUtils.isNotBlank(idSuffix)) {
61              updateIdsWithSuffixNested(copy, idSuffix);
62          }
63  
64          return copy;
65      }
66  
67      @SuppressWarnings("unchecked")
68      protected static <T extends Object> T getNewInstance(T object) {
69          T copy = null;
70          try {
71              copy = (T) object.getClass().newInstance();
72          } catch (Exception e) {
73              throw new RuntimeException("Unable to create new instance of class: " + object.getClass());
74          }
75  
76          return copy;
77      }
78  
79      /**
80       * Equivalent to {@link #copyFieldList(java.util.List, String, String)} but does not copy the given list of fields
81       * first.
82       */
83      public static <T extends Field> void bindAndIdFieldList(List<T> fields, String addBindingPrefix, String idSuffix) {
84          updateIdsWithSuffixNested(fields, idSuffix);
85          prefixBindingPath(fields, addBindingPrefix);
86      }
87  
88      public static <T extends Field> List<T> copyFieldList(List<T> fields, String addBindingPrefix, String idSuffix) {
89          List<T> copiedFieldList = copyFieldList(fields, idSuffix);
90  
91          prefixBindingPath(copiedFieldList, addBindingPrefix);
92  
93          return copiedFieldList;
94      }
95  
96      public static <T extends Field> List<T> copyFieldList(List<T> fields, String idSuffix) {
97          if (fields == null || fields.isEmpty()) {
98              return Collections.emptyList();
99          }
100         
101         List<T> copiedFieldList = new ArrayList<T>(fields.size());
102 
103         for (T field : fields) {
104             T copiedField = copy(field, idSuffix);
105             copiedFieldList.add(copiedField);
106         }
107 
108         return copiedFieldList;
109     }
110 
111     public static <T extends Component> T copyComponent(T component, String addBindingPrefix, String idSuffix) {
112         T copy = copy(component, idSuffix);
113 
114         prefixBindingPathNested(copy, addBindingPrefix);
115 
116         return copy;
117     }
118 
119     public static <T extends Component> List<T> copyComponentList(List<T> components, String idSuffix) {
120         if (components == null || components.isEmpty()) {
121             return Collections.emptyList();
122         }
123         
124         List<T> copiedComponentList = new ArrayList<T>(components.size());
125 
126         for (T field : components) {
127             T copiedComponent = copy(field, idSuffix);
128             copiedComponentList.add(copiedComponent);
129         }
130 
131         return copiedComponentList;
132     }
133 
134     public static <T extends Component> List<T> getComponentsOfType(List<? extends Component> items,
135             Class<T> componentType) {
136         if (items == null || items.isEmpty()) {
137             return Collections.emptyList();
138         }
139         
140         List<T> typeComponents = Collections.emptyList();
141         
142         for (Component component : items) {
143             
144             if (!componentType.isInstance(component)) {
145                 continue;
146             }
147 
148             if (typeComponents.isEmpty()) {
149                 typeComponents = new ArrayList<T>(items.size());
150             }
151 
152             typeComponents.add(componentType.cast(component));
153         }
154 
155         return typeComponents;
156     }
157 
158     /**
159      * Return the components of the specified type from the given component list
160      *
161      * <p>
162      * Components that match, implement or are extended from the specified {@code componentType} are returned in
163      * the result.  If a component is a parent to other components then these child components are searched for
164      * matching component types as well.
165      * </p>
166      *
167      * @param items list of components from which to search
168      * @param componentType the class or interface of the component type to return
169      * @param <T> the type of the components that are returned
170      * @return List of matching components
171      */
172     public static <T extends Component> List<T> getComponentsOfTypeDeep(List<? extends Component> items,
173             Class<T> componentType) {
174         if (items == null) {
175             return Collections.emptyList();
176         }
177         
178         List<T> components = Collections.emptyList();
179         
180         Queue<Component> componentQueue = new LinkedList<Component>();
181         componentQueue.addAll(items);
182 
183         while (!componentQueue.isEmpty()) {
184             Component currentComponent = componentQueue.poll();
185             if (currentComponent == null) {
186                 continue;
187             }
188 
189             if (componentType.isInstance(currentComponent)) {
190                 if (components.isEmpty()) {
191                     components = new ArrayList<T>();
192                 }
193 
194                 components.add(componentType.cast(currentComponent));
195             }
196             
197             componentQueue.addAll(currentComponent.getComponentsForLifecycle());
198         }
199 
200         return components;
201     }
202 
203     /**
204      * Return the components of the specified type from the given component list
205      *
206      * <p>
207      * Components that match, implement or are extended from the specified {@code componentType} are returned in
208      * the result.  If a component is a parent to other components then these child components are searched for
209      * matching component types as well.
210      * </p>
211      *
212      * @param component The components to search
213      * @param componentType the class or interface of the component type to return
214      * @param <T> the type of the components that are returned
215      * @return List of matching components
216      */
217     public static <T extends Component> List<T> getComponentsOfTypeDeep(Component component, Class<T> componentType) {
218         return getComponentsOfTypeDeep(Collections.singletonList(component), componentType);
219     }
220 
221     /**
222      * Returns components of the given type that are direct children of the given component (only checks
223      * one level) including itself
224      *
225      * @param component instance to get children for
226      * @param componentType type for component to return
227      * @param <T> type of component that will be returned
228      * @return list of child components with the given type
229      */
230     public static <T extends Component> List<T> getComponentsOfTypeShallow(Component component,
231             Class<T> componentType) {
232         if (component == null) {
233             return Collections.emptyList();
234         }
235         
236         List<T> typeComponents = getNestedComponentsOfTypeShallow(component, componentType);
237         
238         if (componentType.isInstance(component)) {
239             if (typeComponents.isEmpty()) {
240                 typeComponents = Collections.singletonList(componentType.cast(component));
241             } else {
242                 typeComponents.add(0, componentType.cast(component));
243             }
244         }
245 
246         return typeComponents;
247     }
248 
249     /**
250      * Get nested components of the type specified one layer deep; this defers from getComponentsOfTypeShallow
251      * because it does NOT include itself as a match if it also matches the type being requested
252      *
253      * @param component instance to get children for
254      * @param componentType type for component to return
255      * @param <T> type of component that will be returned
256      * @return list of child components with the given type
257      */
258     public static <T extends Component> List<T> getNestedComponentsOfTypeShallow(Component component,
259             Class<T> componentType) {
260         if (component == null) {
261             return Collections.emptyList();
262         }
263 
264         List<T> typeComponents = Collections.emptyList();
265         List<Component> nestedComponents = component.getComponentsForLifecycle();
266 
267         for (Component nested : nestedComponents) {
268             
269             if (!componentType.isInstance(nested)) {
270                 continue;
271             }
272 
273             if (typeComponents.isEmpty()) {
274                 typeComponents = new ArrayList<T>();
275             }
276 
277             typeComponents.add(componentType.cast(nested));
278         }
279 
280         return typeComponents;
281     }
282 
283     /**
284      * Get all nested children of a given component.
285      * 
286      * @param component The component to search.
287      * @return All nested children of the component.
288      * @see Component#getComponentsForLifecycle()
289      */
290     public static List<Component> getAllNestedComponents(Component component) {
291         if (component == null) {
292             return Collections.emptyList();
293         }
294         
295         List<Component> components = Collections.emptyList();
296         Queue<Component> componentQueue = new LinkedList<Component>();
297         componentQueue.offer(component);
298         
299         while (!componentQueue.isEmpty()) {
300             Component currentComponent = componentQueue.poll();
301             
302             if (currentComponent == null) {
303                 continue;
304             }
305 
306             if (currentComponent != component) {
307                 if (components.isEmpty()) {
308                     components = new ArrayList<Component>();
309                 }
310 
311                 components.add(currentComponent);
312             }
313 
314             componentQueue.addAll(currentComponent.getComponentsForLifecycle());
315         }
316 
317         return components;
318     }
319 
320     /**
321      * Searches for the component with the given id within the given list of components
322      *
323      * @param components list of components to search through
324      * @param componentId id for the component to find
325      * @return component found in the list or null
326      */
327     public static Component findComponentInList(List<Component> components, String componentId) {
328         for (Component component : components) {
329             if (component != null && StringUtils.equals(component.getId(), componentId)) {
330                 return component;
331             }
332         }
333 
334         return null;
335     }
336 
337     /**
338      * Finds the child component of the given parent component that has the required id
339      *
340      * @param parent parent component for component to find
341      * @param nestedId id of the component to find
342      * @return Component instance for child (if found) or null
343      */
344     public static Component findNestedComponentById(Component parent, String nestedId) {
345         if (parent == null) {
346             return null;
347         }
348         
349         Queue<Component> componentQueue = new LinkedList<Component>();
350         componentQueue.offer(parent);
351         
352         while (!componentQueue.isEmpty()) {
353             Component child = componentQueue.poll();
354             
355             if (child == null) {
356                 continue;
357             }
358 
359             if (child != parent && StringUtils.equals(nestedId, child.getId())) {
360                 return child;
361             }
362 
363             componentQueue.addAll(child.getComponentsForLifecycle());
364         }
365         
366         return null;
367     }
368 
369     public static void prefixBindingPath(List<? extends Field> fields, String addBindingPrefix) {
370         for (Field field : fields) {
371             if (field instanceof DataBinding) {
372                 prefixBindingPath((DataBinding) field, addBindingPrefix);
373             } else if ((field instanceof FieldGroup) && (((FieldGroup) field).getItems() != null)) {
374                 List<Field> groupFields = getComponentsOfTypeDeep(((FieldGroup) field).getItems(), Field.class);
375                 prefixBindingPath(groupFields, addBindingPrefix);
376             }
377         }
378     }
379 
380     public static void prefixBindingPathNested(Component component, String addBindingPrefix) {
381         if (component instanceof DataBinding) {
382             if (LOG.isDebugEnabled()) {
383                 LOG.info("setting nested binding prefix '" + addBindingPrefix + "' on " + component);
384             }
385             prefixBindingPath((DataBinding) component, addBindingPrefix);
386         }
387 
388         for (Component nested : component.getComponentsForLifecycle()) {
389             if (nested != null) {
390                 prefixBindingPathNested(nested, addBindingPrefix);
391             }
392         }
393     }
394 
395     public static void prefixBindingPath(DataBinding field, String addBindingPrefix) {
396         String bindingPrefix = addBindingPrefix;
397         if (StringUtils.isNotBlank(field.getBindingInfo().getBindByNamePrefix())) {
398             bindingPrefix += "." + field.getBindingInfo().getBindByNamePrefix();
399         }
400         field.getBindingInfo().setBindByNamePrefix(bindingPrefix);
401     }
402 
403     public static void updateIdsWithSuffixNested(List<? extends Component> components, String idSuffix) {
404         for (Component component : components) {
405             updateIdsWithSuffixNested(component, idSuffix);
406         }
407     }
408 
409     public static void updateIdsWithSuffixNested(Component component, String idSuffix) {
410         updateIdWithSuffix(component, idSuffix);
411 
412         updateChildIdsWithSuffixNested(component, idSuffix);
413     }
414 
415     public static void updateChildIdsWithSuffixNested(Component component, String idSuffix) {
416         for (Component nested : component.getComponentsForLifecycle()) {
417             if (nested != null) {
418                 updateIdsWithSuffixNested(nested, idSuffix);
419             }
420         }
421 
422         List<Component> propertyReplacerComponents = component.getPropertyReplacerComponents();
423         if (propertyReplacerComponents != null) {
424             for (Component nested : propertyReplacerComponents) {
425                 if (nested != null) {
426                     updateIdsWithSuffixNested(nested, idSuffix);
427                 }
428             }
429         }
430     }
431 
432     /**
433      * Clear all ids from a component and its children.  If there are features that depend on a static id of this
434      * component, this call may cause errors.
435      *
436      * @param component the component to clear all ids from
437      */
438     public static void clearIds(Component component) {
439         component.setId(null);
440 
441         if (Container.class.isAssignableFrom(component.getClass())) {
442             LayoutManager layoutManager = ((Container) component).getLayoutManager();
443             layoutManager.setId(null);
444         }
445 
446         for (Component nested : component.getComponentsForLifecycle()) {
447             if (nested != null) {
448                 clearIds(nested);
449             }
450         }
451 
452         List<Component> propertyReplacerComponents = component.getPropertyReplacerComponents();
453         if (propertyReplacerComponents != null) {
454             for (Component nested : propertyReplacerComponents) {
455                 if (nested != null) {
456                     clearIds(nested);
457                 }
458             }
459         }
460     }
461 
462     public static void clearIds(List<? extends Component> components) {
463         for (Component component : components) {
464             if (component != null) {
465                 clearIds(component);
466             }
467         }
468     }
469 
470     /**
471      * add a suffix to the id
472      *
473      * @param component the component instance whose id will be changed
474      * @param idSuffix the suffix to be appended
475      */
476     public static void updateIdWithSuffix(Component component, String idSuffix) {
477         if (component != null && !StringUtils.isEmpty(idSuffix)) {
478             component.setId(component.getId() + idSuffix);
479         }
480 
481         if (component instanceof Container) {
482             LayoutManager manager = ((Container) component).getLayoutManager();
483             if (manager != null) {
484                 manager.setId(manager.getId() + idSuffix);
485             }
486         }
487     }
488 
489     public static void setComponentsPropertyDeep(List<? extends Component> components, String propertyPath,
490             Object propertyValue) {
491         for (Component component : components) {
492             setComponentPropertyDeep(component, propertyPath, propertyValue);
493         }
494     }
495 
496     public static void setComponentPropertyDeep(Component component, String propertyPath, Object propertyValue) {
497         ObjectPropertyUtils.setPropertyValue(component, propertyPath, propertyValue, true);
498 
499         for (Component nested : component.getComponentsForLifecycle()) {
500             if (nested != null) {
501                 setComponentPropertyDeep(nested, propertyPath, propertyValue);
502             }
503         }
504     }
505 
506     public static List<String> getComponentPropertyNames(Class<? extends Component> componentClass) {
507         List<String> componentProperties = new ArrayList<String>();
508 
509         PropertyDescriptor[] properties = BeanUtils.getPropertyDescriptors(componentClass);
510         for (int i = 0; i < properties.length; i++) {
511             PropertyDescriptor descriptor = properties[i];
512             if (descriptor.getReadMethod() != null) {
513                 componentProperties.add(descriptor.getName());
514             }
515         }
516 
517         return componentProperties;
518     }
519 
520     /**
521      * Sets a property on the given component and removes any expressions for that property so the value is not
522      * overridden
523      *
524      * @param component component instance to set property on
525      * @param propertyName name of property to set
526      * @param propertyValue value to set property to
527      */
528     public static void setComponentPropertyFinal(Component component, String propertyName, Object propertyValue) {
529         if (component == null) {
530             return;
531         }
532 
533         ObjectPropertyUtils.setPropertyValue(component, propertyName, propertyValue);
534 
535         if ((component.getPropertyExpressions() != null) && component.getPropertyExpressions().containsKey(
536                 propertyName)) {
537             component.getPropertyExpressions().remove(propertyName);
538         }
539     }
540 
541     /**
542      * places a key, value pair in each context map of a list of components
543      *
544      * @param components the list components
545      * @param contextName a value to be used as a key to retrieve the object
546      * @param contextValue the value to be placed in the context
547      */
548     public static void pushObjectToContext(List<? extends Component> components, String contextName,
549             Object contextValue) {
550         if (components == null || components.isEmpty()) {
551             return;
552         }
553         
554         Queue<Component> componentQueue = new LinkedList<Component>();
555         componentQueue.addAll(components);
556         
557         while (!componentQueue.isEmpty()) {
558             Component currentComponent = componentQueue.poll();
559             
560             if (currentComponent == null) {
561                 continue;
562             }
563             
564             currentComponent.pushObjectToContext(contextName, contextValue);
565 
566             if (currentComponent instanceof Container) {
567                 LayoutManager layoutManager = ((Container) currentComponent).getLayoutManager();
568                 if (layoutManager != null) {
569                     layoutManager.pushObjectToContext(contextName, contextValue);
570                 }
571             }
572             
573             componentQueue.addAll(currentComponent.getComponentsForLifecycle());
574         }
575     }
576 
577     /**
578      * pushes object to a component's context so that it is available from {@link Component#getContext()}
579      *
580      * <p>The component's nested components that are available via {@code Component#getComponentsForLifecycle}
581      * are also updated recursively</p>
582      *
583      * @param component the component whose context is to be updated
584      * @param contextName a value to be used as a key to retrieve the object
585      * @param contextValue the value to be placed in the context
586      */
587     public static void pushObjectToContext(Component component, String contextName, Object contextValue) {
588         if (component == null) {
589             return;
590         }
591 
592         pushObjectToContext(Collections.singletonList(component), contextName, contextValue);
593     }
594 
595     /**
596      * places a all entries from a map into each context map of a list of components
597      *
598      * @param components The list components.
599      * @param sourceContext The source context map.
600      */
601     public static void pushAllToContext(List<? extends Component> components, Map<String, Object> sourceContext) {
602         if (components == null || components.isEmpty()) {
603             return;
604         }
605         
606         Queue<Component> componentQueue = new LinkedList<Component>();
607         componentQueue.addAll(components);
608         
609         while (!componentQueue.isEmpty()) {
610             Component currentComponent = componentQueue.poll();
611             
612             if (currentComponent == null) {
613                 continue;
614             }
615             
616             currentComponent.pushAllToContext(sourceContext);
617 
618             if (currentComponent instanceof Container) {
619                 LayoutManager layoutManager = ((Container) currentComponent).getLayoutManager();
620                 if (layoutManager != null) {
621                     layoutManager.pushAllToContext(sourceContext);
622                 }
623             }
624             
625             componentQueue.addAll(currentComponent.getComponentsForLifecycle());
626         }
627     }
628 
629     /**
630      * pushes object to a component's context so that it is available from {@link Component#getContext()}
631      *
632      * <p>The component's nested components that are available via {@code Component#getComponentsForLifecycle}
633      * are also updated recursively</p>
634      *
635      * @param component the component whose context is to be updated
636      * @param sourceContext The source context map.
637      */
638     public static void pushAllToContext(Component component, Map<String, Object> sourceContext) {
639         if (component == null) {
640             return;
641         }
642 
643         pushAllToContext(Collections.singletonList(component), sourceContext);
644     }
645 
646     /**
647      * update the contexts of the given components
648      *
649      * <p>calls {@link #updateContextForLine(org.kuali.rice.krad.uif.component.Component, Object, int, String)}
650      * for each component</p>
651      *
652      * @param components the components whose components to update
653      * @param collectionLine an instance of the data object for the line
654      * @param lineIndex the line index
655      * @param lineSuffix id suffix for components in the line to make them unique
656      */
657     public static void updateContextsForLine(List<? extends Component> components, Object collectionLine, int lineIndex,
658             String lineSuffix) {
659         for (Component component : components) {
660             updateContextForLine(component, collectionLine, lineIndex, lineSuffix);
661         }
662     }
663 
664     /**
665      * update the context map for the given component
666      *
667      * <p>The values of {@code UifConstants.ContextVariableNames.LINE} and {@code UifConstants.ContextVariableNames.INDEX}
668      * are set to {@code collectionLine} and {@code lineIndex} respectively.</p>
669      *
670      * @param component the component whose context is to be updated
671      * @param collectionLine an instance of the data object for the line
672      * @param lineIndex the line index
673      * @param lineSuffix id suffix for components in the line to make them unique
674      */
675     public static void updateContextForLine(Component component, Object collectionLine, int lineIndex,
676             String lineSuffix) {
677         Map<String, Object> toUpdate = new HashMap<String,Object>(4);
678         toUpdate.put(UifConstants.ContextVariableNames.LINE, collectionLine);
679         toUpdate.put(UifConstants.ContextVariableNames.INDEX, Integer.valueOf(lineIndex));
680         toUpdate.put(UifConstants.ContextVariableNames.LINE_SUFFIX, lineSuffix);
681 
682         boolean isAddLine = (lineIndex == -1);
683         toUpdate.put(UifConstants.ContextVariableNames.IS_ADD_LINE, isAddLine);
684         pushAllToContext(component, toUpdate);
685     }
686 
687     /**
688      * Performs sorting logic of the given list of <code>Ordered</code>
689      * instances by its order property
690      *
691      * <p>
692      * Items list is sorted based on its order property. Lower order values are
693      * placed higher in the list. If a item does not have a value assigned for
694      * the order (or is equal to the default order of 0), it will be assigned
695      * the a value based on the given order sequence integer. If two or more
696      * items share the same order value, all but the last item found in the list
697      * will be removed.
698      * </p>
699      *
700      * @param items
701      * @param defaultOrderSequence
702      * @return List<Ordered> sorted items
703      * @see org.kuali.rice.krad.uif.component.Component#getOrder()
704      * @see @see org.springframework.core.Ordered
705      */
706     public static List<? extends Ordered> sort(List<? extends Ordered> items, int defaultOrderSequence) {
707         if (items == null) {
708             return null;
709         }
710         
711         List<Ordered> orderedItems = new ArrayList<Ordered>(items.size());
712 
713         // do replacement for items with the same order property value
714         Set<Integer> foundOrders = new HashSet<Integer>();
715 
716         // reverse the list, so items later in the list win
717         for (int i = items.size()-1; i >= 0; i--) {
718             Ordered component = items.get(i);
719             int order = component.getOrder();
720 
721             // if order not set just add to list
722             if (order == 0) {
723                 orderedItems.add(component);
724             }
725             // check if the order value has been used already
726             else if (!foundOrders.contains(Integer.valueOf(order))) {
727                 orderedItems.add(component);
728                 foundOrders.add(Integer.valueOf(order));
729             }
730         }
731 
732         // now reverse the list back so we can assign defaults for items without
733         // an order value
734         for (int i = 0; i < items.size(); i++) {
735             Ordered component = items.get(i);
736             int order = component.getOrder();
737 
738             // if order property not set assign default
739             if (order == 0) {
740                 defaultOrderSequence++;
741                 while (foundOrders.contains(Integer.valueOf(defaultOrderSequence))) {
742                     defaultOrderSequence++;
743                 }
744                 component.setOrder(defaultOrderSequence);
745             }
746         }
747 
748         // now sort the list by its order property
749         Collections.sort(orderedItems, new OrderComparator());
750 
751         return orderedItems;
752     }
753 
754     /**
755      * Gets all the input fields contained in this container, but also in
756      * every sub-container that is a child of this container.  When called from the top level
757      * View this will be every InputField across all pages.
758      *
759      * @return every InputField that is a child at any level of this container
760      */
761     public static List<InputField> getAllInputFieldsWithinContainer(Container container) {
762         List<InputField> inputFields = new ArrayList<InputField>();
763 
764         for (Component c : container.getComponentsForLifecycle()) {
765             if (c instanceof InputField) {
766                 inputFields.add((InputField) c);
767             } else if (c instanceof Container) {
768                 inputFields.addAll(getAllInputFieldsWithinContainer((Container) c));
769             } else if (c instanceof FieldGroup) {
770                 Container cb = ((FieldGroup) c).getGroup();
771 
772                 inputFields.addAll(getAllInputFieldsWithinContainer(cb));
773             }
774         }
775 
776         return inputFields;
777     }
778 
779     /**
780      * Determines whether the given component contains an expression for the given property name
781      *
782      * @param component component instance to check for expressions
783      * @param propertyName name of the property to determine if there is an expression for
784      * @param collectionMatch if set to true will find an expressions for properties that start with the given
785      * property name (for matching expressions on collections like prop[index] or prop['key'])
786      * @return true if the component has an expression for the property name, false if not
787      */
788     public static boolean containsPropertyExpression(Component component, String propertyName,
789             boolean collectionMatch) {
790         boolean hasExpression = false;
791 
792         Map<String, String> propertyExpressions = component.getPropertyExpressions();
793 
794         if (collectionMatch) {
795             for (String expressionPropertyName : propertyExpressions.keySet()) {
796                 if (expressionPropertyName.startsWith(propertyName)) {
797                     hasExpression = true;
798                 }
799             }
800         } else if (propertyExpressions.containsKey(propertyName)) {
801             hasExpression = true;
802         }
803 
804         return hasExpression;
805     }
806 
807     /**
808      * Adjust nestingLevel properties for collections which use RichTable with forceLocalJsonData on and for all of its
809      * potentially additional nested subcollections
810      *
811      * @param container container to traverse and update nested levels in
812      * @param currentLevel the current nesting level, the initial call to this method should be 0
813      */
814     public static void adjustNestedLevelsForTableCollections(Container container, int currentLevel) {
815         if (container != null
816                 && container instanceof CollectionGroup
817                 && container.getLayoutManager() != null
818                 && container.getLayoutManager() instanceof TableLayoutManager
819                 && ((TableLayoutManager) container.getLayoutManager()).getRichTable() != null
820                 && ((TableLayoutManager) container.getLayoutManager()).getRichTable().isRender()
821                 && ((TableLayoutManager) container.getLayoutManager()).getRichTable().isForceLocalJsonData()) {
822             ((TableLayoutManager) container.getLayoutManager()).getRichTable().setNestedLevel(currentLevel);
823             currentLevel++;
824         }
825 
826         if (container != null) {
827             List<Container> subContainers = ComponentUtils.getNestedComponentsOfTypeShallow(container, Container.class);
828             for (Container subContainer : subContainers) {
829                 adjustNestedLevelsForTableCollections(subContainer, currentLevel);
830             }
831 
832             List<FieldGroup> subFieldGroups = ComponentUtils.getNestedComponentsOfTypeShallow(container,
833                     FieldGroup.class);
834             for (FieldGroup fieldGroup : subFieldGroups) {
835                 if (fieldGroup != null) {
836                     adjustNestedLevelsForTableCollections(fieldGroup.getGroup(), currentLevel);
837                 }
838             }
839         }
840     }
841 
842 }