View Javadoc

1   /**
2    * Copyright 2005-2012 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 org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.core.api.util.type.TypeUtils;
20  import org.kuali.rice.krad.uif.UifConstants;
21  import org.kuali.rice.krad.uif.container.Container;
22  import org.kuali.rice.krad.uif.component.Component;
23  import org.kuali.rice.krad.uif.component.DataBinding;
24  import org.kuali.rice.krad.uif.component.Ordered;
25  import org.kuali.rice.krad.uif.container.ContainerBase;
26  import org.kuali.rice.krad.uif.field.Field;
27  import org.kuali.rice.krad.uif.field.FieldGroup;
28  import org.kuali.rice.krad.uif.field.InputField;
29  import org.kuali.rice.krad.uif.layout.LayoutManager;
30  import org.kuali.rice.krad.util.ObjectUtils;
31  import org.springframework.beans.BeanUtils;
32  import org.springframework.core.OrderComparator;
33  
34  import java.beans.PropertyDescriptor;
35  import java.io.Serializable;
36  import java.util.ArrayList;
37  import java.util.Collections;
38  import java.util.HashSet;
39  import java.util.List;
40  import java.util.Set;
41  
42  /**
43   * ComponentUtils is a utility class providing methods to help create and modify <code>Component</code> instances
44   * 
45   * @author Kuali Rice Team (rice.collab@kuali.org)
46   */
47  public class ComponentUtils {
48      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ComponentUtils.class);
49  
50  
51      public static <T extends Component> T copy(T component) {
52          return copy(component, null);
53      }
54  
55      public static <T extends Component> T copy(T component, String idSuffix) {
56          T copy = copyObject(component);
57  
58          if (StringUtils.isNotBlank(idSuffix)) {
59              updateIdsWithSuffixNested(copy, idSuffix);
60          }
61  
62          return copy;
63      }
64  
65      public static <T extends Object> T copyObject(T object) {
66          if (object == null) {
67              return null;
68          }
69  
70          T copy = null;
71          try {
72              copy = CloneUtils.deepClone(object);
73          }
74          catch (Exception e) {
75              throw new RuntimeException(e);
76          }
77  
78          return copy;
79      }
80  
81      protected static Object getCopyPropertyValue(Set<String> propertiesForReferenceCopy, String propertyName,
82              Object propertyValue) {
83          if (propertyValue == null) {
84              return null;
85          }
86  
87          Object copyValue = propertyValue;
88  
89          Class<?> valuePropertyType = propertyValue.getClass();
90          if (propertiesForReferenceCopy.contains(propertyName) || TypeUtils.isSimpleType(valuePropertyType)
91                  || TypeUtils.isClassClass(valuePropertyType)) {
92              return copyValue;
93          }
94  
95          if (Component.class.isAssignableFrom(valuePropertyType)
96                  || LayoutManager.class.isAssignableFrom(valuePropertyType)) {
97              copyValue = copyObject(propertyValue);
98          }
99          else {
100             copyValue = ObjectUtils.deepCopy((Serializable) propertyValue);
101         }
102 
103         return copyValue;
104     }
105 
106     @SuppressWarnings("unchecked")
107     protected static <T extends Object> T getNewInstance(T object) {
108         T copy = null;
109         try {
110             copy = (T) object.getClass().newInstance();
111         }
112         catch (Exception e) {
113             throw new RuntimeException("Unable to create new instance of class: " + object.getClass());
114         }
115 
116         return copy;
117     }
118 
119     public static <T extends Field> List<T> copyFieldList(List<T> fields, String addBindingPrefix, String idSuffix) {
120         List<T> copiedFieldList = copyFieldList(fields, idSuffix);
121 
122         prefixBindingPath(copiedFieldList, addBindingPrefix);
123 
124         return copiedFieldList;
125     }
126 
127     public static <T extends Field> List<T> copyFieldList(List<T> fields, String idSuffix) {
128         List<T> copiedFieldList = new ArrayList<T>();
129 
130         for (T field : fields) {
131             T copiedField = copy(field, idSuffix);
132             copiedFieldList.add(copiedField);
133         }
134 
135         return copiedFieldList;
136     }
137 
138     public static <T extends Component> T copyComponent(T component, String addBindingPrefix, String idSuffix) {
139         T copy = copy(component, idSuffix);
140 
141         prefixBindingPathNested(copy, addBindingPrefix);
142 
143         return copy;
144     }
145 
146     public static <T extends Component> List<T> copyComponentList(List<T> components, String idSuffix) {
147         List<T> copiedComponentList = new ArrayList<T>();
148 
149         for (T field : components) {
150             T copiedComponent = copy(field, idSuffix);
151             copiedComponentList.add(copiedComponent);
152         }
153 
154         return copiedComponentList;
155     }
156 
157     @SuppressWarnings("unchecked")
158     public static <T extends Component> List<T> getComponentsOfType(List<? extends Component> items,
159             Class<T> componentType) {
160         List<T> typeComponents = new ArrayList<T>();
161 
162         for (Component component : items) {
163             if (componentType.isAssignableFrom(component.getClass())) {
164                 typeComponents.add((T) component);
165             }
166         }
167 
168         return typeComponents;
169     }
170 
171     /**
172      * Return the components of the specified type from the given component list
173      *
174      * <p>
175      * Components that match, implement or are extended from the specified {@code componentType} are returned in
176      * the result.  If a component is a parent to other components then these child components are searched for
177      * matching component types as well.
178      * </p>
179      *
180      * @param items list of components from which to search
181      * @param componentType the class or interface of the component type to return
182      * @param <T> the type of the components that are returned
183      * @return List of matching components
184      */
185     public static <T extends Component> List<T> getComponentsOfTypeDeep(List<? extends Component> items,
186             Class<T> componentType) {
187         List<T> typeComponents = new ArrayList<T>();
188 
189         for (Component component : items) {
190             typeComponents.addAll(getComponentsOfTypeDeep(component, componentType));
191         }
192 
193         return typeComponents;
194     }
195     
196     @SuppressWarnings("unchecked")
197     public static <T extends Component> List<T> getComponentsOfTypeDeep(Component component, Class<T> componentType) {
198         List<T> typeComponents = new ArrayList<T>();
199 
200         if (component == null) {
201             return typeComponents;
202         }
203 
204         if (componentType.isAssignableFrom(component.getClass())) {
205             typeComponents.add((T) component);
206         }
207 
208         for (Component nested : component.getComponentsForLifecycle()) {
209             typeComponents.addAll(getComponentsOfTypeDeep(nested, componentType));
210         }
211 
212         return typeComponents;
213     }
214 
215     public static List<Component> getAllNestedComponents(Component component) {
216         List<Component> components = new ArrayList<Component>();
217 
218         if (component == null) {
219             return components;
220         }
221 
222         for (Component nested : component.getComponentsForLifecycle()) {
223             if (nested != null) {
224                 components.add(nested);
225                 components.addAll(getAllNestedComponents(nested));
226             }
227         }
228 
229         return components;
230     }
231 
232     /**
233      * Finds the child component of the given parent component that has the required id
234      * 
235      * @param parent - parent component for component to find
236      * @param nestedId - id of the component to find
237      * @return Component instance for child (if found) or null
238      */
239     public static Component findNestedComponentById(Component parent, String nestedId) {
240         Component childComponent = null;
241 
242         List<Component> children = getAllNestedComponents(parent);
243         for (Component child : children) {
244             if (StringUtils.equals(nestedId, child.getId())) {
245                 childComponent = child;
246                 break;
247             }
248         }
249 
250         return childComponent;
251     }
252 
253     public static void prefixBindingPath(List<? extends Field> fields, String addBindingPrefix) {
254         for (Field field : fields) {
255             if (field instanceof DataBinding) {
256                 prefixBindingPath((DataBinding) field, addBindingPrefix);
257             }
258             else if ((field instanceof FieldGroup) && (((FieldGroup) field).getItems() != null) ) {
259                 List<Field> groupFields = getComponentsOfTypeDeep(((FieldGroup) field).getItems(), Field.class);
260                 prefixBindingPath(groupFields, addBindingPrefix);
261             }
262         }
263     }
264 
265     public static void prefixBindingPathNested(Component component, String addBindingPrefix) {
266         if (component instanceof DataBinding) {
267             if (LOG.isDebugEnabled()) {
268                 LOG.info("setting nested binding prefix '"+ addBindingPrefix  +"' on " + component);
269             }
270             prefixBindingPath((DataBinding) component, addBindingPrefix);
271         }
272 
273         for (Component nested : component.getComponentsForLifecycle()) {
274            if (nested != null) {
275               prefixBindingPathNested(nested, addBindingPrefix);
276            }
277         }
278     }
279 
280     public static void prefixBindingPath(DataBinding field, String addBindingPrefix) {
281         String bindingPrefix = addBindingPrefix;
282         if (StringUtils.isNotBlank(field.getBindingInfo().getBindByNamePrefix())) {
283             bindingPrefix += "." + field.getBindingInfo().getBindByNamePrefix();
284         }
285         field.getBindingInfo().setBindByNamePrefix(bindingPrefix);
286     }
287 
288     public static void updateIdsWithSuffixNested(List<? extends Component> components, String idSuffix) {
289         for (Component component : components) {
290             updateIdsWithSuffixNested(component, idSuffix);
291         }
292     }
293 
294     public static void updateIdsWithSuffixNested(Component component, String idSuffix) {
295         updateIdWithSuffix(component, idSuffix);
296        // updateFactoryIdWithSuffix(component, idSuffix);
297 
298         if (Container.class.isAssignableFrom(component.getClass())) {
299             LayoutManager layoutManager = ((Container) component).getLayoutManager();
300             layoutManager.setId(layoutManager.getId() + idSuffix);
301         }
302 
303         for (Component nested : component.getComponentsForLifecycle()) {
304             if (nested != null) {
305                 updateIdsWithSuffixNested(nested, idSuffix);
306             }
307         }
308         
309         for (Component nested : component.getPropertyReplacerComponents()) {
310             if (nested != null) {
311                 updateIdsWithSuffixNested(nested, idSuffix);
312             }
313         }        
314     }
315 
316     /**
317      * add a suffix to the id
318      *
319      * @param component - the component instance whose id will be changed
320      * @param idSuffix - the suffix to be appended
321      */
322     public static void updateIdWithSuffix(Component component, String idSuffix) {
323         if (component != null && !StringUtils.isEmpty(idSuffix)) {
324             component.setId(component.getId() + idSuffix);
325         }
326     }
327 
328     public static void setComponentsPropertyDeep(List<? extends Component> components, String propertyPath,
329             Object propertyValue) {
330         for (Component component : components) {
331             setComponentPropertyDeep(component, propertyPath, propertyValue);
332         }
333     }
334 
335     public static void setComponentPropertyDeep(Component component, String propertyPath, Object propertyValue) {
336         ObjectPropertyUtils.setPropertyValue(component, propertyPath, propertyValue, true);
337 
338         for (Component nested : component.getComponentsForLifecycle()) {
339             if (nested != null) {
340                 setComponentPropertyDeep(nested, propertyPath, propertyValue);
341             }
342         }
343     }
344 
345     public static List<String> getComponentPropertyNames(Class<? extends Component> componentClass) {
346         List<String> componentProperties = new ArrayList<String>();
347 
348         PropertyDescriptor[] properties = BeanUtils.getPropertyDescriptors(componentClass);
349         for (int i = 0; i < properties.length; i++) {
350             PropertyDescriptor descriptor = properties[i];
351             if (descriptor.getReadMethod() != null) {
352                 componentProperties.add(descriptor.getName());
353             }
354         }
355 
356         return componentProperties;
357     }
358 
359     /**
360      * places a key, value pair in each context map of a list of components
361      *
362      * @param components - the list components
363      * @param contextName - a value to be used as a key to retrieve the object
364      * @param contextValue - the value to be placed in the context
365      */
366     public static void pushObjectToContext(List<? extends Component> components, String contextName, Object contextValue) {
367         for (Component component : components) {
368             pushObjectToContext(component, contextName, contextValue);
369         }
370     }
371 
372     /**
373      * pushes object to a component's context so that it is available from {@link Component#getContext()}
374      *
375      * <p>The component's nested components that are available via {@code Component#getComponentsForLifecycle}
376      * are also updated recursively</p>
377      *
378      * @param component - the component whose context is to be updated
379      * @param contextName - a value to be used as a key to retrieve the object
380      * @param contextValue - the value to be placed in the context
381      */
382     public static void pushObjectToContext(Component component, String contextName, Object contextValue) {
383         if (component == null) {
384             return;
385         }
386 
387         component.pushObjectToContext(contextName, contextValue);
388 
389         // special container check so we pick up the layout manager
390         if (Container.class.isAssignableFrom(component.getClass())) {
391             LayoutManager layoutManager = ((Container) component).getLayoutManager();
392             if (layoutManager != null) {
393                 // add to layout manager context only if not present
394                 if (layoutManager.getContext().get(contextName) != contextValue) {
395                     layoutManager.pushObjectToContext(contextName, contextValue);
396 
397                     for (Component nestedComponent : layoutManager.getComponentsForLifecycle()) {
398                         pushObjectToContext(nestedComponent, contextName, contextValue);
399                     }
400                 }
401             }
402         }
403 
404         for (Component nestedComponent : component.getComponentsForLifecycle()) {
405             pushObjectToContext(nestedComponent, contextName, contextValue);
406         }
407     }
408 
409     /**
410      * update the contexts of the given components
411      *
412      * <p>calls {@link #updateContextForLine(org.kuali.rice.krad.uif.component.Component, Object, int)} for each component</p>
413      *
414      * @param components - the components whose components to update
415      * @param collectionLine - an instance of the data object for the line
416      * @param lineIndex - the line index
417      */
418     public static void updateContextsForLine(List<? extends Component> components, Object collectionLine,
419             int lineIndex) {
420         for (Component component : components) {
421             updateContextForLine(component, collectionLine, lineIndex);
422         }
423     }
424 
425     /**
426      * update the context map for the given component
427      *
428      * <p>The values of {@code UifConstants.ContextVariableNames.LINE} and {@code UifConstants.ContextVariableNames.INDEX}
429      * are set to {@code collectionLine} and {@code lineIndex} respectively.</p>
430      *
431      * @param component - the component whose context is to be updated
432      * @param collectionLine - an instance of the data object for the line
433      * @param lineIndex - the line index
434      */
435     public static void updateContextForLine(Component component, Object collectionLine, int lineIndex) {
436         pushObjectToContext(component, UifConstants.ContextVariableNames.LINE, collectionLine);
437         pushObjectToContext(component, UifConstants.ContextVariableNames.INDEX, Integer.valueOf(lineIndex));
438         
439         boolean isAddLine = (lineIndex == -1);
440         pushObjectToContext(component, UifConstants.ContextVariableNames.IS_ADD_LINE, isAddLine);
441     }
442 
443     /**
444      * Performs sorting logic of the given list of <code>Ordered</code>
445      * instances by its order property
446      *
447      * <p>
448      * Items list is sorted based on its order property. Lower order values are
449      * placed higher in the list. If a item does not have a value assigned for
450      * the order (or is equal to the default order of 0), it will be assigned
451      * the a value based on the given order sequence integer. If two or more
452      * items share the same order value, all but the last item found in the list
453      * will be removed.
454      * </p>
455      * 
456      * @param items
457      * @param defaultOrderSequence
458      * @return List<Ordered> sorted items
459      * @see org.kuali.rice.krad.uif.component.Component#getOrder()
460      * @see @see org.springframework.core.Ordered
461      */
462     public static List<? extends Ordered> sort(List<? extends Ordered> items, int defaultOrderSequence) {
463         List<Ordered> orderedItems = new ArrayList<Ordered>();
464 
465         // do replacement for items with the same order property value
466         Set<Integer> foundOrders = new HashSet<Integer>();
467 
468         // reverse the list, so items later in the list win
469         Collections.reverse(items);
470         for (Ordered component : items) {
471             int order = component.getOrder();
472 
473             // if order not set just add to list
474             if (order == 0) {
475                 orderedItems.add(component);
476             }
477             // check if the order value has been used already
478             else if (!foundOrders.contains(Integer.valueOf(order))) {
479                 orderedItems.add(component);
480                 foundOrders.add(Integer.valueOf(order));
481             }
482         }
483 
484         // now reverse the list back so we can assign defaults for items without
485         // an order value
486         Collections.reverse(items);
487         for (Ordered component : items) {
488             int order = component.getOrder();
489 
490             // if order property not set assign default
491             if (order == 0) {
492                 defaultOrderSequence++;
493                 while (foundOrders.contains(Integer.valueOf(defaultOrderSequence))) {
494                     defaultOrderSequence++;
495                 }
496                 component.setOrder(defaultOrderSequence);
497             }
498         }
499 
500         // now sort the list by its order property
501         Collections.sort(orderedItems, new OrderComparator());
502 
503         return orderedItems;
504     }
505 
506     /**
507      * Gets all the input fields contained in this container, but also in
508      * every sub-container that is a child of this container.  When called from the top level
509      * View this will be every InputField across all pages.
510      *
511      * @return every InputField that is a child at any level of this container
512      */
513     public static List<InputField> getAllInputFieldsWithinContainer(Container container) {
514         List<InputField> inputFields = new ArrayList<InputField>();
515 
516         for (Component c : container.getComponentsForLifecycle()) {
517             if (c instanceof InputField) {
518                 inputFields.add((InputField) c);
519             } else if (c instanceof Container) {
520                 inputFields.addAll(getAllInputFieldsWithinContainer((Container) c));
521             } else if (c instanceof FieldGroup) {
522                 Container cb = ((FieldGroup) c).getGroup();
523 
524                 inputFields.addAll(getAllInputFieldsWithinContainer(cb));
525             }
526         }
527 
528         return inputFields;
529     }
530 
531 }