View Javadoc

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