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}