001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad.uif.util;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.rice.core.api.util.type.TypeUtils;
020import org.kuali.rice.krad.uif.UifConstants;
021import org.kuali.rice.krad.uif.container.Container;
022import org.kuali.rice.krad.uif.component.Component;
023import org.kuali.rice.krad.uif.component.DataBinding;
024import org.kuali.rice.krad.uif.component.Ordered;
025import org.kuali.rice.krad.uif.field.Field;
026import org.kuali.rice.krad.uif.field.FieldGroup;
027import org.kuali.rice.krad.uif.layout.LayoutManager;
028import org.kuali.rice.krad.util.ObjectUtils;
029import org.springframework.beans.BeanUtils;
030import org.springframework.core.OrderComparator;
031
032import java.beans.PropertyDescriptor;
033import java.io.Serializable;
034import java.util.ArrayList;
035import java.util.Collections;
036import java.util.HashSet;
037import java.util.List;
038import java.util.Set;
039
040/**
041 * Utility class providing methods to help create and modify
042 * <code>Component</code> instances
043 * 
044 * @author Kuali Rice Team (rice.collab@kuali.org)
045 */
046public class ComponentUtils {
047    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ComponentUtils.class);
048
049
050    public static <T extends Component> T copy(T component) {
051        return copy(component, null);
052    }
053
054    public static <T extends Component> T copy(T component, String idSuffix) {
055        T copy = copyObject(component);
056
057        if (StringUtils.isNotBlank(idSuffix)) {
058            updateIdsWithSuffixNested(copy, idSuffix);
059        }
060
061        return copy;
062    }
063
064    public static <T extends Object> T copyObject(T object) {
065        if (object == null) {
066            return null;
067        }
068
069        T copy = null;
070        try {
071            copy = CloneUtils.deepClone(object);
072        }
073        catch (Exception e) {
074            throw new RuntimeException(e);
075        }
076
077        return copy;
078    }
079
080    protected static Object getCopyPropertyValue(Set<String> propertiesForReferenceCopy, String propertyName,
081            Object propertyValue) {
082        if (propertyValue == null) {
083            return null;
084        }
085
086        Object copyValue = propertyValue;
087
088        Class<?> valuePropertyType = propertyValue.getClass();
089        if (propertiesForReferenceCopy.contains(propertyName) || TypeUtils.isSimpleType(valuePropertyType)
090                || TypeUtils.isClassClass(valuePropertyType)) {
091            return copyValue;
092        }
093
094        if (Component.class.isAssignableFrom(valuePropertyType)
095                || LayoutManager.class.isAssignableFrom(valuePropertyType)) {
096            copyValue = copyObject(propertyValue);
097        }
098        else {
099            copyValue = ObjectUtils.deepCopy((Serializable) propertyValue);
100        }
101
102        return copyValue;
103    }
104
105    @SuppressWarnings("unchecked")
106    protected static <T extends Object> T getNewInstance(T object) {
107        T copy = null;
108        try {
109            copy = (T) object.getClass().newInstance();
110        }
111        catch (Exception e) {
112            throw new RuntimeException("Unable to create new instance of class: " + object.getClass());
113        }
114
115        return copy;
116    }
117
118    public static <T extends Field> List<T> copyFieldList(List<T> fields, String addBindingPrefix, String idSuffix) {
119        List<T> copiedFieldList = copyFieldList(fields, idSuffix);
120
121        prefixBindingPath(copiedFieldList, addBindingPrefix);
122
123        return copiedFieldList;
124    }
125
126    public static <T extends Field> List<T> copyFieldList(List<T> fields, String idSuffix) {
127        List<T> copiedFieldList = new ArrayList<T>();
128
129        for (T field : fields) {
130            T copiedField = copy(field, idSuffix);
131            copiedFieldList.add(copiedField);
132        }
133
134        return copiedFieldList;
135    }
136
137    public static <T extends Component> T copyComponent(T component, String addBindingPrefix, String idSuffix) {
138        T copy = copy(component, idSuffix);
139
140        prefixBindingPathNested(copy, addBindingPrefix);
141
142        return copy;
143    }
144
145    public static <T extends Component> List<T> copyComponentList(List<T> components, String idSuffix) {
146        List<T> copiedComponentList = new ArrayList<T>();
147
148        for (T field : components) {
149            T copiedComponent = copy(field, idSuffix);
150            copiedComponentList.add(copiedComponent);
151        }
152
153        return copiedComponentList;
154    }
155
156    @SuppressWarnings("unchecked")
157    public static <T extends Component> List<T> getComponentsOfType(List<? extends Component> items,
158            Class<T> componentType) {
159        List<T> typeComponents = new ArrayList<T>();
160
161        for (Component component : items) {
162            if (componentType.isAssignableFrom(component.getClass())) {
163                typeComponents.add((T) component);
164            }
165        }
166
167        return typeComponents;
168    }
169    
170    public static <T extends Component> List<T> getComponentsOfTypeDeep(List<? extends Component> items,
171            Class<T> componentType) {
172        List<T> typeComponents = new ArrayList<T>();
173
174        for (Component component : items) {
175            typeComponents.addAll(getComponentsOfTypeDeep(component, componentType));
176        }
177
178        return typeComponents;
179    }
180    
181    @SuppressWarnings("unchecked")
182    public static <T extends Component> List<T> getComponentsOfTypeDeep(Component component, Class<T> componentType) {
183        List<T> typeComponents = new ArrayList<T>();
184
185        if (component == null) {
186            return typeComponents;
187        }
188
189        if (componentType.isAssignableFrom(component.getClass())) {
190            typeComponents.add((T) component);
191        }
192
193        for (Component nested : component.getComponentsForLifecycle()) {
194            typeComponents.addAll(getComponentsOfTypeDeep(nested, componentType));
195        }
196
197        return typeComponents;
198    }
199
200    public static List<Component> getAllNestedComponents(Component component) {
201        List<Component> components = new ArrayList<Component>();
202
203        if (component == null) {
204            return components;
205        }
206
207        for (Component nested : component.getComponentsForLifecycle()) {
208            if (nested != null) {
209                components.add(nested);
210                components.addAll(getAllNestedComponents(nested));
211            }
212        }
213
214        return components;
215    }
216
217    public static void prefixBindingPath(List<? extends Field> fields, String addBindingPrefix) {
218        for (Field field : fields) {
219            if (field instanceof DataBinding) {
220                prefixBindingPath((DataBinding) field, addBindingPrefix);
221            }
222            else if ((field instanceof FieldGroup) && (((FieldGroup) field).getItems() != null) ) {
223                List<Field> groupFields = getComponentsOfType(((FieldGroup) field).getItems(), Field.class);
224                prefixBindingPath(groupFields, addBindingPrefix);
225            }
226        }
227    }
228
229    public static void prefixBindingPathNested(Component component, String addBindingPrefix) {
230        if (component instanceof DataBinding) {
231            if (LOG.isDebugEnabled()) {
232                LOG.info("setting nested binding prefix '"+ addBindingPrefix  +"' on " + component);
233            }
234            prefixBindingPath((DataBinding) component, addBindingPrefix);
235        }
236
237        for (Component nested : component.getComponentsForLifecycle()) {
238           if (nested != null) {
239              prefixBindingPathNested(nested, addBindingPrefix);
240           }
241        }
242    }
243
244    public static void prefixBindingPath(DataBinding field, String addBindingPrefix) {
245        String bindingPrefix = addBindingPrefix;
246        if (StringUtils.isNotBlank(field.getBindingInfo().getBindByNamePrefix())) {
247            bindingPrefix += "." + field.getBindingInfo().getBindByNamePrefix();
248        }
249        field.getBindingInfo().setBindByNamePrefix(bindingPrefix);
250    }
251
252    public static void updateIdsWithSuffixNested(List<? extends Component> components, String idSuffix) {
253        for (Component component : components) {
254            updateIdsWithSuffixNested(component, idSuffix);
255        }
256    }
257
258    public static void updateIdsWithSuffixNested(Component component, String idSuffix) {
259        updateIdWithSuffix(component, idSuffix);
260
261        if (Container.class.isAssignableFrom(component.getClass())) {
262            LayoutManager layoutManager = ((Container) component).getLayoutManager();
263            layoutManager.setId(layoutManager.getId() + idSuffix);
264        }
265
266        for (Component nested : component.getComponentsForLifecycle()) {
267            if (nested != null) {
268                updateIdsWithSuffixNested(nested, idSuffix);
269            }
270        }
271        
272        for (Component nested : component.getPropertyReplacerComponents()) {
273            if (nested != null) {
274                updateIdsWithSuffixNested(nested, idSuffix);
275            }
276        }        
277    }
278
279    public static void updateIdWithSuffix(Component component, String idSuffix) {
280        component.setId(component.getId() + idSuffix);
281    }
282
283    public static void setComponentsPropertyDeep(List<? extends Component> components, String propertyPath,
284            Object propertyValue) {
285        for (Component component : components) {
286            setComponentPropertyDeep(component, propertyPath, propertyValue);
287        }
288    }
289
290    public static void setComponentPropertyDeep(Component component, String propertyPath, Object propertyValue) {
291        ObjectPropertyUtils.setPropertyValue(component, propertyPath, propertyValue, true);
292
293        for (Component nested : component.getComponentsForLifecycle()) {
294            if (nested != null) {
295                setComponentPropertyDeep(nested, propertyPath, propertyValue);
296            }
297        }
298    }
299
300    public static List<String> getComponentPropertyNames(Class<? extends Component> componentClass) {
301        List<String> componentProperties = new ArrayList<String>();
302
303        PropertyDescriptor[] properties = BeanUtils.getPropertyDescriptors(componentClass);
304        for (int i = 0; i < properties.length; i++) {
305            PropertyDescriptor descriptor = properties[i];
306            if (descriptor.getReadMethod() != null) {
307                componentProperties.add(descriptor.getName());
308            }
309        }
310
311        return componentProperties;
312    }
313
314    public static void pushObjectToContext(List<? extends Component> components, String contextName, Object contextValue) {
315        for (Component component : components) {
316            pushObjectToContext(component, contextName, contextValue);
317        }
318    }
319
320    public static void pushObjectToContext(Component component, String contextName, Object contextValue) {
321        if (component == null) {
322            return;
323        }
324
325        component.pushObjectToContext(contextName, contextValue);
326
327        // special container check so we pick up the layout manager
328        if (Container.class.isAssignableFrom(component.getClass())) {
329            LayoutManager layoutManager = ((Container) component).getLayoutManager();
330            if (layoutManager != null) {
331                layoutManager.pushObjectToContext(contextName, contextValue);
332
333                for (Component nestedComponent : layoutManager.getComponentsForLifecycle()) {
334                    pushObjectToContext(nestedComponent, contextName, contextValue);
335                }
336            }
337        }
338
339        for (Component nestedComponent : component.getComponentsForLifecycle()) {
340            pushObjectToContext(nestedComponent, contextName, contextValue);
341        }
342    }
343
344    public static void updateContextsForLine(List<? extends Component> components, Object collectionLine,
345            int lineIndex) {
346        for (Component component : components) {
347            updateContextForLine(component, collectionLine, lineIndex);
348        }
349    }
350
351    public static void updateContextForLine(Component component, Object collectionLine, int lineIndex) {
352        pushObjectToContext(component, UifConstants.ContextVariableNames.LINE, collectionLine);
353        pushObjectToContext(component, UifConstants.ContextVariableNames.INDEX, new Integer(lineIndex));
354        
355        boolean isAddLine = (lineIndex == -1);
356        pushObjectToContext(component, UifConstants.ContextVariableNames.IS_ADD_LINE, isAddLine);
357    }
358
359    /**
360     * Performs sorting logic of the given list of <code>Ordered</code>
361     * instances by its order property
362     *
363     * <p>
364     * Items list is sorted based on its order property. Lower order values are
365     * placed higher in the list. If a item does not have a value assigned for
366     * the order (or is equal to the default order of 0), it will be assigned
367     * the a value based on the given order sequence integer. If two or more
368     * items share the same order value, all but the last item found in the list
369     * will be removed.
370     * </p>
371     * 
372     * @param items
373     * @param defaultOrderSequence
374     * @return List<Ordered> sorted items
375     * @see org.kuali.rice.krad.uif.component.Component#getOrder()
376     * @see @see org.springframework.core.Ordered
377     */
378    public static List<? extends Ordered> sort(List<? extends Ordered> items, int defaultOrderSequence) {
379        List<Ordered> orderedItems = new ArrayList<Ordered>();
380
381        // do replacement for items with the same order property value
382        Set<Integer> foundOrders = new HashSet<Integer>();
383
384        // reverse the list, so items later in the list win
385        Collections.reverse(items);
386        for (Ordered component : items) {
387            int order = component.getOrder();
388
389            // if order not set just add to list
390            if (order == 0) {
391                orderedItems.add(component);
392            }
393            // check if the order value has been used already
394            else if (!foundOrders.contains(new Integer(order))) {
395                orderedItems.add(component);
396                foundOrders.add(new Integer(order));
397            }
398        }
399
400        // now reverse the list back so we can assign defaults for items without
401        // an order value
402        Collections.reverse(items);
403        for (Ordered component : items) {
404            int order = component.getOrder();
405
406            // if order property not set assign default
407            if (order == 0) {
408                defaultOrderSequence++;
409                while (foundOrders.contains(new Integer(defaultOrderSequence))) {
410                    defaultOrderSequence++;
411                }
412                component.setOrder(defaultOrderSequence);
413            }
414        }
415
416        // now sort the list by its order property
417        Collections.sort(orderedItems, new OrderComparator());
418
419        return orderedItems;
420    }
421
422}