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