001/** 002 * Copyright 2005-2015 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 java.util.ArrayList; 019import java.util.Collection; 020import java.util.Collections; 021import java.util.HashMap; 022import java.util.HashSet; 023import java.util.LinkedList; 024import java.util.List; 025import java.util.Map; 026import java.util.Queue; 027import java.util.Set; 028 029import org.apache.commons.lang.StringUtils; 030import org.kuali.rice.core.framework.util.ReflectionUtils; 031import org.kuali.rice.krad.uif.UifConstants; 032import org.kuali.rice.krad.uif.component.Component; 033import org.kuali.rice.krad.uif.component.DataBinding; 034import org.kuali.rice.krad.uif.component.Ordered; 035import org.kuali.rice.krad.uif.container.CollectionGroup; 036import org.kuali.rice.krad.uif.container.Container; 037import org.kuali.rice.krad.uif.container.Group; 038import org.kuali.rice.krad.uif.field.Field; 039import org.kuali.rice.krad.uif.field.FieldGroup; 040import org.kuali.rice.krad.uif.field.InputField; 041import org.kuali.rice.krad.uif.layout.LayoutManager; 042import org.kuali.rice.krad.uif.layout.TableLayoutManager; 043import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle; 044import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleUtils; 045import org.kuali.rice.krad.uif.lifecycle.initialize.AssignIdsTask; 046import org.springframework.core.OrderComparator; 047 048/** 049 * ComponentUtils is a utility class providing methods to help create and modify {@link Component} instances. 050 * 051 * @author Kuali Rice Team (rice.collab@kuali.org) 052 */ 053public class ComponentUtils { 054 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ComponentUtils.class); 055 056 private ComponentUtils() {} 057 058 public static <T extends Component> T copy(T component) { 059 return copy(component, null); 060 } 061 062 public static <T extends Component> T copy(T component, String idSuffix) { 063 if (component == null) { 064 return null; 065 } 066 067 T copy = CopyUtils.copy(component); 068 069 if (StringUtils.isNotBlank(idSuffix)) { 070 updateIdsWithSuffixNested(copy, idSuffix); 071 } 072 073 return copy; 074 } 075 076 /** 077 * Copy a list of components 078 * 079 * @param <T> component type 080 * @param <T> component type 081 * @param components the list of components to copy 082 * @return the copied list 083 */ 084 public static <T extends Component> List<T> copy(List<T> components) { 085 if (components != null) { 086 List<T> componentsCopy = new ArrayList<T>(); 087 for (T component : components) { 088 T copiedComponent = copy(component); 089 componentsCopy.add(copiedComponent); 090 } 091 092 return componentsCopy; 093 } 094 095 return new ArrayList<T>(); 096 } 097 098 /** 099 * Adjusts the ids to contain the given suffix and adds the giving binding prefix for the list of fields. 100 * 101 * @param <T> component type 102 * @param fields list of fields to bind and id 103 * @param addBindingPrefix prefix to add to the binding path 104 * @param idSuffix id suffix 105 */ 106 public static <T extends Field> void bindAndIdFieldList(List<T> fields, String addBindingPrefix, String idSuffix) { 107 updateIdsWithSuffixNested(fields, idSuffix); 108 prefixBindingPath(fields, addBindingPrefix); 109 } 110 111 public static <T extends Component> T copyComponent(T component, String addBindingPrefix, String idSuffix) { 112 T copy = copy(component, idSuffix); 113 114 prefixBindingPathNested(copy, addBindingPrefix); 115 116 return copy; 117 } 118 119 public static <T extends Component> List<T> copyComponentList(List<T> components, String idSuffix) { 120 if (components == null || components.isEmpty()) { 121 return Collections.emptyList(); 122 } 123 124 List<T> copiedComponentList = new ArrayList<T>(components.size()); 125 126 for (T field : components) { 127 T copiedComponent = copy(field, idSuffix); 128 copiedComponentList.add(copiedComponent); 129 } 130 131 return copiedComponentList; 132 } 133 134 public static <T extends Object> List<T> getComponentsOfType(List<? extends Component> items, 135 Class<T> componentType) { 136 if (items == null || items.isEmpty()) { 137 return Collections.emptyList(); 138 } 139 140 List<T> typeComponents = Collections.emptyList(); 141 142 for (Component component : items) { 143 144 if (!componentType.isInstance(component)) { 145 continue; 146 } 147 148 if (typeComponents.isEmpty()) { 149 typeComponents = new ArrayList<T>(items.size()); 150 } 151 152 typeComponents.add(componentType.cast(component)); 153 } 154 155 return typeComponents; 156 } 157 158 /** 159 * Gets all components of the give type that are within the items list of the container, or within a nested 160 * container or field group. 161 * 162 * @param container container instance to pull components from 163 * @param componentType type for components to pull 164 * @param <T> type for component to pull 165 * @return List of nested components with the given type 166 */ 167 public static <T extends Component> List<T> getNestedContainerComponents(Container container, 168 Class<T> componentType) { 169 List<T> typeComponents = new ArrayList<T>(); 170 171 if (container == null) { 172 return typeComponents; 173 } 174 175 for (Component item : container.getItems()) { 176 if (item == null) { 177 continue; 178 } 179 180 if (item instanceof Container) { 181 typeComponents.addAll(getNestedContainerComponents((Container) item, componentType)); 182 } else if (item instanceof FieldGroup) { 183 typeComponents.addAll(getNestedContainerComponents(((FieldGroup) item).getGroup(), componentType)); 184 } else if (componentType.isAssignableFrom(item.getClass())) { 185 typeComponents.add(componentType.cast(item)); 186 } 187 } 188 189 return typeComponents; 190 } 191 192 public static <T extends Component> List<T> getNestedNonCollectionComponents(List<Component> items, 193 Class<T> componentType) { 194 List<T> typeComponents = new ArrayList<T>(); 195 196 if (items == null) { 197 return typeComponents; 198 } 199 200 for (Component item : items) { 201 if (item == null) { 202 continue; 203 } 204 205 if (item instanceof Container && !(item instanceof CollectionGroup)) { 206 typeComponents.addAll(getNestedNonCollectionComponents((Container) item, componentType)); 207 } else if (item instanceof FieldGroup) { 208 typeComponents.addAll(getNestedNonCollectionComponents(((FieldGroup) item).getGroup(), componentType)); 209 } else if (componentType.isAssignableFrom(item.getClass())) { 210 typeComponents.add(componentType.cast(item)); 211 } 212 } 213 214 return typeComponents; 215 } 216 217 public static <T extends Component> List<T> getNestedNonCollectionComponents(Container container, 218 Class<T> componentType) { 219 List<T> typeComponents = new ArrayList<T>(); 220 221 if (container == null) { 222 return typeComponents; 223 } 224 225 for (Component item : container.getItems()) { 226 if (item == null) { 227 continue; 228 } 229 230 if (item instanceof Container && !(item instanceof CollectionGroup)) { 231 typeComponents.addAll(getNestedNonCollectionComponents((Container) item, componentType)); 232 } else if (item instanceof FieldGroup) { 233 typeComponents.addAll(getNestedNonCollectionComponents(((FieldGroup) item).getGroup(), componentType)); 234 } else if (componentType.isAssignableFrom(item.getClass())) { 235 typeComponents.add(componentType.cast(item)); 236 } 237 } 238 239 return typeComponents; 240 } 241 242 /** 243 * Get all nested children of a given component. 244 * 245 * @param component The component to search. 246 * @return All nested children of the component. 247 * @see ViewLifecycleUtils#getElementsForLifecycle(LifecycleElement) 248 */ 249 public static List<Component> getAllNestedComponents(Component component) { 250 if (component == null) { 251 return Collections.emptyList(); 252 } 253 254 List<Component> components = Collections.emptyList(); 255 @SuppressWarnings("unchecked") Queue<LifecycleElement> elementQueue = RecycleUtils.getInstance( 256 LinkedList.class); 257 elementQueue.offer(component); 258 259 try { 260 while (!elementQueue.isEmpty()) { 261 LifecycleElement currentElement = elementQueue.poll(); 262 263 if (currentElement == null) { 264 continue; 265 } 266 267 if (currentElement instanceof Component && currentElement != component) { 268 if (components.isEmpty()) { 269 components = new ArrayList<Component>(); 270 } 271 272 components.add((Component) currentElement); 273 } 274 275 elementQueue.addAll(ViewLifecycleUtils.getElementsForLifecycle(currentElement).values()); 276 } 277 } finally { 278 elementQueue.clear(); 279 RecycleUtils.recycle(elementQueue); 280 } 281 282 return components; 283 } 284 285 /** 286 * Searches for the component with the given id within the given list of components 287 * 288 * @param components list of components to search through 289 * @param componentId id for the component to find 290 * @return component found in the list or null 291 */ 292 public static Component findComponentInList(List<Component> components, String componentId) { 293 for (Component component : components) { 294 if (component != null && StringUtils.equals(component.getId(), componentId)) { 295 return component; 296 } 297 } 298 299 return null; 300 } 301 302 public static void prefixBindingPath(List<? extends Component> components, String addBindingPrefix) { 303 for (Component component : components) { 304 prefixBindingPath(component, addBindingPrefix); 305 } 306 } 307 308 public static void prefixBindingPath(Component component, String addBindingPrefix) { 309 if (component instanceof DataBinding) { 310 prefixBindingPath((DataBinding) component, addBindingPrefix); 311 } else if ((component instanceof FieldGroup) && (((FieldGroup) component).getItems() != null)) { 312 List<? extends Component> fieldGroupItems = ((FieldGroup) component).getItems(); 313 prefixBindingPath(fieldGroupItems, addBindingPrefix); 314 } else if ((component instanceof Group) && (((Group) component).getItems() != null) && 315 !(component instanceof CollectionGroup)) { 316 List<? extends Component> groupItems = ((Group) component).getItems(); 317 prefixBindingPath(groupItems, addBindingPrefix); 318 } 319 } 320 321 public static void prefixBindingPath(DataBinding field, String addBindingPrefix) { 322 String bindingPrefix = addBindingPrefix; 323 if (StringUtils.isNotBlank(field.getBindingInfo().getBindByNamePrefix())) { 324 bindingPrefix += "." + field.getBindingInfo().getBindByNamePrefix(); 325 } 326 field.getBindingInfo().setBindByNamePrefix(bindingPrefix); 327 } 328 329 public static void prefixBindingPathNested(Component component, String addBindingPrefix) { 330 @SuppressWarnings("unchecked") Queue<LifecycleElement> elementQueue = RecycleUtils.getInstance( 331 LinkedList.class); 332 elementQueue.offer(component); 333 334 try { 335 while (!elementQueue.isEmpty()) { 336 LifecycleElement currentElement = elementQueue.poll(); 337 if (currentElement == null) { 338 continue; 339 } 340 341 if (currentElement instanceof DataBinding) { 342 if (LOG.isDebugEnabled()) { 343 LOG.info("setting nested binding prefix '" + addBindingPrefix + "' on " + currentElement); 344 } 345 prefixBindingPath((DataBinding) currentElement, addBindingPrefix); 346 } 347 348 elementQueue.addAll(ViewLifecycleUtils.getElementsForLifecycle(currentElement).values()); 349 } 350 } finally { 351 elementQueue.clear(); 352 RecycleUtils.recycle(elementQueue); 353 } 354 } 355 356 public static void updateIdsWithSuffixNested(List<? extends Component> components, String idSuffix) { 357 for (Component component : components) { 358 updateIdsWithSuffixNested(component, idSuffix); 359 } 360 } 361 362 public static void updateIdsWithSuffixNested(Component component, String idSuffix) { 363 updateIdWithSuffix(component, idSuffix); 364 365 updateChildIdsWithSuffixNested(component, idSuffix); 366 } 367 368 /** 369 * add a suffix to the id 370 * 371 * @param element the component instance whose id will be changed 372 * @param idSuffix the suffix to be appended 373 */ 374 public static void updateIdWithSuffix(LifecycleElement element, String idSuffix) { 375 if (StringUtils.isBlank(idSuffix)) { 376 return; 377 } 378 379 if (element != null) { 380 element.setId(element.getId() + idSuffix); 381 } 382 383 if (element instanceof Container) { 384 LayoutManager manager = ((Container) element).getLayoutManager(); 385 386 if (manager != null) { 387 manager.setId(manager.getId() + idSuffix); 388 } 389 } 390 } 391 392 public static void updateChildIdsWithSuffixNested(Component component, String idSuffix) { 393 @SuppressWarnings("unchecked") Queue<LifecycleElement> elementQueue = RecycleUtils.getInstance( 394 LinkedList.class); 395 try { 396 elementQueue.addAll(ViewLifecycleUtils.getElementsForLifecycle(component).values()); 397 398 while (!elementQueue.isEmpty()) { 399 LifecycleElement currentElement = elementQueue.poll(); 400 if (currentElement == null) { 401 continue; 402 } 403 404 if (currentElement instanceof Component) { 405 updateIdWithSuffix((Component) currentElement, idSuffix); 406 elementQueue.addAll(((Component) currentElement).getPropertyReplacerComponents()); 407 } 408 409 elementQueue.addAll(ViewLifecycleUtils.getElementsForLifecycle(currentElement).values()); 410 } 411 } finally { 412 elementQueue.clear(); 413 RecycleUtils.recycle(elementQueue); 414 } 415 } 416 417 /** 418 * Generate a hash code unique within the current view for a single lifecycle element. A unique 419 * ID value will be assigned to the lifecycle element, replacing the current ID. 420 * 421 * <p>This method may only be called during the view lifecycle.</p> 422 * 423 * @param element The element to generate a hash code for. 424 * @param seed A hash value to use as a seed for the new hash. 425 * @return A hash code based on the provided element and seed value. 426 * @see AssignIdsTask For a complete description of the algorithm. This method implements a 427 * single step in the algorithm described in that task. 428 */ 429 public static int generateId(LifecycleElement element, int seed) { 430 if (element == null) { 431 return seed; 432 } 433 434 final int prime = 6971; 435 int hash = prime * seed + element.getClass().getName().hashCode(); 436 437 String id = element.getId(); 438 hash *= prime; 439 if (id != null) { 440 hash += id.hashCode(); 441 } 442 443 do { 444 hash *= 4507; 445 id = Long.toString(((long) hash) - ((long) Integer.MIN_VALUE), 36); 446 } while (!ViewLifecycle.getView().getViewIndex().observeAssignedId(id)); 447 448 element.setId(UifConstants.COMPONENT_ID_PREFIX + id); 449 450 return hash; 451 } 452 453 /** 454 * Replace all IDs from a component and its children with new generated ID values. 455 * 456 * <p>If there are features that depend on a static id of this 457 * component, this call may cause errors.</p> 458 * 459 * @param components A list of component to clear all IDs from. 460 * @see AssignIdsTask For a complete description of the algorithm. 461 */ 462 public static void clearAndAssignIds(List<? extends Component> components) { 463 if (components == null || components.isEmpty()) { 464 return; 465 } 466 467 int hash = 1; 468 @SuppressWarnings("unchecked") Queue<LifecycleElement> toClear = RecycleUtils.getInstance(LinkedList.class); 469 toClear.addAll(components); 470 try { 471 while (!toClear.isEmpty()) { 472 LifecycleElement element = toClear.poll(); 473 474 hash = generateId(element, hash); 475 476 for (LifecycleElement nested : ViewLifecycleUtils.getElementsForLifecycle(element).values()) { 477 if (nested != null) { 478 toClear.add(nested); 479 } 480 } 481 482 if (element instanceof Component) { 483 List<Component> propertyReplacerComponents = ((Component) element).getPropertyReplacerComponents(); 484 if (propertyReplacerComponents == null) { 485 continue; 486 } 487 488 for (Component nested : propertyReplacerComponents) { 489 if (nested != null) { 490 toClear.add(nested); 491 } 492 } 493 } 494 } 495 } finally { 496 toClear.clear(); 497 RecycleUtils.recycle(toClear); 498 } 499 } 500 501 /** 502 * Traverse a component tree, setting a property on all components for which the property is writable. 503 * 504 * @param <T> component type 505 * @param <T> component type 506 * @param components The components to traverse. 507 * @param propertyPath The property path to set. 508 * @param propertyValue The property value to set. 509 * @see ObjectPropertyUtils#isWritableProperty(Object, String) 510 * @see ObjectPropertyUtils#setPropertyValue(Object, String, Object) 511 */ 512 public static <T extends Component> void setComponentsPropertyDeep(List<T> components, String propertyPath, 513 Object propertyValue) { 514 if (components == null || components.isEmpty()) { 515 return; 516 } 517 518 Set<Class<?>> skipTypes = null; 519 @SuppressWarnings("unchecked") Queue<LifecycleElement> elementQueue = RecycleUtils.getInstance( 520 LinkedList.class); 521 elementQueue.addAll(components); 522 523 try { 524 while (!elementQueue.isEmpty()) { 525 LifecycleElement currentElement = elementQueue.poll(); 526 if (currentElement == null) { 527 continue; 528 } 529 530 elementQueue.addAll(ViewLifecycleUtils.getElementsForLifecycle(currentElement).values()); 531 532 Class<?> componentClass = currentElement.getClass(); 533 if (skipTypes != null && skipTypes.contains(componentClass)) { 534 continue; 535 } 536 537 if (!ObjectPropertyUtils.isWritableProperty(currentElement, propertyPath)) { 538 if (skipTypes == null) { 539 skipTypes = new HashSet<Class<?>>(); 540 } 541 skipTypes.add(componentClass); 542 continue; 543 } 544 545 ObjectPropertyUtils.setPropertyValue(currentElement, propertyPath, propertyValue, true); 546 } 547 } finally { 548 elementQueue.clear(); 549 RecycleUtils.recycle(elementQueue); 550 } 551 } 552 553 /** 554 * Traverse a component tree, setting a property on all components for which the property is writable. 555 * 556 * @param component The component to traverse. 557 * @param propertyPath The property path to set. 558 * @param propertyValue The property value to set. 559 * @see ObjectPropertyUtils#isWritableProperty(Object, String) 560 * @see ObjectPropertyUtils#setPropertyValue(Object, String, Object) 561 */ 562 public static void setComponentPropertyDeep(Component component, String propertyPath, Object propertyValue) { 563 setComponentsPropertyDeep(Collections.singletonList(component), propertyPath, propertyValue); 564 } 565 566 /** 567 * Sets a property on the given component and removes any expressions for that property so the value is not 568 * overridden 569 * 570 * @param component component instance to set property on 571 * @param propertyName name of property to set 572 * @param propertyValue value to set property to 573 */ 574 public static void setComponentPropertyFinal(Component component, String propertyName, Object propertyValue) { 575 if (component == null) { 576 return; 577 } 578 579 ObjectPropertyUtils.setPropertyValue(component, propertyName, propertyValue); 580 581 if ((component.getPropertyExpressions() != null) && component.getPropertyExpressions().containsKey( 582 propertyName)) { 583 component.getPropertyExpressions().remove(propertyName); 584 } 585 } 586 587 /** 588 * Indicates if the given component has configuration that it allows it to be refreshed. 589 * 590 * @param component instance to check 591 * @return true if component can be refreshed, false if not 592 */ 593 public static boolean canBeRefreshed(Component component) { 594 boolean hasRefreshCondition = StringUtils.isNotBlank(component.getProgressiveRender()) || 595 StringUtils.isNotBlank(component.getConditionalRefresh()) || (component.getRefreshTimer() > 0) || 596 (component.getRefreshWhenChangedPropertyNames() != null && !component 597 .getRefreshWhenChangedPropertyNames().isEmpty()); 598 599 boolean isInlineEditField = 600 component instanceof InputField && (((InputField) component).isInlineEdit() || ((InputField) component) 601 .isAjaxInlineEdit()); 602 603 return hasRefreshCondition || component.isRefreshedByAction() || component.isDisclosedByAction() || 604 component.isRetrieveViaAjax() || isInlineEditField; 605 } 606 607 /** 608 * places a key, value pair in each context map of a list of components 609 * 610 * @param elements the list of elements 611 * @param contextName a value to be used as a key to retrieve the object 612 * @param contextValue the value to be placed in the context 613 */ 614 public static void pushObjectToContext(Collection<? extends LifecycleElement> elements, String contextName, 615 Object contextValue) { 616 if (elements == null || elements.isEmpty()) { 617 return; 618 } 619 620 Queue<LifecycleElement> elementQueue = new LinkedList<LifecycleElement>(); 621 622 try { 623 elementQueue.addAll(elements); 624 while (!elementQueue.isEmpty()) { 625 LifecycleElement currentElement = elementQueue.poll(); 626 627 if (currentElement == null) { 628 continue; 629 } 630 631 if (currentElement instanceof Component) { 632 ((Component) currentElement).pushObjectToContext(contextName, contextValue); 633 } 634 635 elementQueue.addAll(ViewLifecycleUtils.getElementsForLifecycle(currentElement).values()); 636 } 637 } finally { 638 elementQueue.clear(); 639 RecycleUtils.recycle(elementQueue); 640 } 641 } 642 643 /** 644 * pushes object to a component's context so that it is available from {@link Component#getContext()} 645 * 646 * <p>The component's nested components that are available via {@code Component#getComponentsForLifecycle} 647 * are also updated recursively</p> 648 * 649 * @param component the component whose context is to be updated 650 * @param contextName a value to be used as a key to retrieve the object 651 * @param contextValue the value to be placed in the context 652 */ 653 public static void pushObjectToContext(Component component, String contextName, Object contextValue) { 654 if (component == null) { 655 return; 656 } 657 658 pushObjectToContext(Collections.singletonList(component), contextName, contextValue); 659 } 660 661 /** 662 * places a all entries from a map into each context map of a list of components 663 * 664 * @param components The list components. 665 * @param sourceContext The source context map. 666 */ 667 public static void pushAllToContext(List<? extends Component> components, Map<String, Object> sourceContext) { 668 if (components == null || components.isEmpty()) { 669 return; 670 } 671 672 @SuppressWarnings("unchecked") Queue<LifecycleElement> elementQueue = RecycleUtils.getInstance( 673 LinkedList.class); 674 try { 675 elementQueue.addAll(components); 676 while (!elementQueue.isEmpty()) { 677 LifecycleElement currentElement = elementQueue.poll(); 678 679 if (currentElement == null) { 680 continue; 681 } 682 683 if (currentElement instanceof Component) { 684 ((Component) currentElement).pushAllToContext(sourceContext); 685 } 686 687 elementQueue.addAll(ViewLifecycleUtils.getElementsForLifecycle(currentElement).values()); 688 } 689 } finally { 690 elementQueue.clear(); 691 RecycleUtils.recycle(elementQueue); 692 } 693 } 694 695 /** 696 * pushes object to a component's context so that it is available from {@link Component#getContext()} 697 * 698 * <p>The component's nested components that are available via {@code Component#getComponentsForLifecycle} 699 * are also updated recursively</p> 700 * 701 * @param component the component whose context is to be updated 702 * @param sourceContext The source context map. 703 */ 704 public static void pushAllToContext(Component component, Map<String, Object> sourceContext) { 705 if (component == null) { 706 return; 707 } 708 709 pushAllToContext(Collections.singletonList(component), sourceContext); 710 } 711 712 /** 713 * Update the contexts of the given components. 714 * 715 * <p>Calls {@link ComponentUtils#updateContextForLine} for each component</p> 716 * 717 * @param components the components whose components to update 718 * @param collectionGroup collection group the components are associated with 719 * @param collectionLine an instance of the data object for the line 720 * @param lineIndex the line index 721 * @param lineSuffix id suffix for components in the line to make them unique 722 */ 723 public static void updateContextsForLine(List<? extends Component> components, CollectionGroup collectionGroup, 724 Object collectionLine, int lineIndex, String lineSuffix) { 725 for (Component component : components) { 726 updateContextForLine(component, collectionGroup, collectionLine, lineIndex, lineSuffix); 727 } 728 } 729 730 /** 731 * update the context map for the given component 732 * 733 * <p>The values of {@code UifConstants.ContextVariableNames.LINE} and {@code UifConstants.ContextVariableNames.INDEX} 734 * are set to {@code collectionLine} and {@code lineIndex} respectively.</p> 735 * 736 * @param component the component whose context is to be updated 737 * @param collectionGroup collection group the component is associated with 738 * @param collectionLine an instance of the data object for the line 739 * @param lineIndex the line index 740 * @param lineSuffix id suffix for components in the line, not if the collection group has a container id suffix 741 * it will be appended to the lineSuffix for the final exported context entry 742 */ 743 public static void updateContextForLine(Component component, CollectionGroup collectionGroup, Object collectionLine, 744 int lineIndex, String lineSuffix) { 745 // line id suffix that we export as expression variable will contain the container suffix as well 746 // so correct references to ids for line components can be configured in the XML 747 if (StringUtils.isNotBlank(collectionGroup.getContainerIdSuffix())) { 748 lineSuffix = lineSuffix + collectionGroup.getContainerIdSuffix(); 749 } 750 751 Map<String, Object> toUpdate = new HashMap<String, Object>(5); 752 toUpdate.put(UifConstants.ContextVariableNames.COLLECTION_GROUP, collectionGroup); 753 toUpdate.put(UifConstants.ContextVariableNames.LINE, collectionLine); 754 toUpdate.put(UifConstants.ContextVariableNames.INDEX, Integer.valueOf(lineIndex)); 755 toUpdate.put(UifConstants.ContextVariableNames.LINE_SUFFIX, lineSuffix); 756 757 boolean isAddLine = (lineIndex == -1); 758 toUpdate.put(UifConstants.ContextVariableNames.IS_ADD_LINE, isAddLine); 759 pushAllToContext(component, toUpdate); 760 } 761 762 /** 763 * Sets the context of the given lifecycle element to null, then using reflection recursively finds any 764 * lifecycle element children and sets their context to null. 765 * 766 * @param lifecycleElement lifecycle element instance to clean 767 */ 768 public static void cleanContextDeap(LifecycleElement lifecycleElement) { 769 if (lifecycleElement == null) { 770 return; 771 } 772 773 lifecycleElement.setContext(null); 774 775 // find any children that are lifecycle elements and clean them as well 776 Class<?> elementClass = lifecycleElement.getClass(); 777 778 List<java.lang.reflect.Field> fields = ReflectionUtils.getAllFields(elementClass); 779 for (java.lang.reflect.Field field : fields) { 780 // Check for lists that can contain lifecycle elements 781 if (Collection.class.isAssignableFrom(field.getType())) { 782 ReflectionUtils.makeAccessible(field); 783 Collection<Object> elements = (Collection<Object>) ReflectionUtils.getField(field, lifecycleElement); 784 if (elements != null) { 785 for (Object element : elements) { 786 if (element != null && LifecycleElement.class.isAssignableFrom(element.getClass())) { 787 cleanContextDeap((LifecycleElement) element); 788 } 789 } 790 } 791 // Check for Maps that can contain lifecycle elements 792 } else if (Map.class.isAssignableFrom(field.getType())) { 793 ReflectionUtils.makeAccessible(field); 794 Map<Object, Object> elements = (Map<Object, Object>) ReflectionUtils.getField(field, lifecycleElement); 795 if (elements != null) { 796 for (Object element : elements.entrySet()) { 797 if (element != null && LifecycleElement.class.isAssignableFrom(element.getClass())) { 798 cleanContextDeap((LifecycleElement) element); 799 } 800 } 801 } 802 // Check if field is a lifecycle element itself 803 } else if (LifecycleElement.class.isAssignableFrom(field.getType())) { 804 ReflectionUtils.makeAccessible(field); 805 LifecycleElement nestedElement = (LifecycleElement) ReflectionUtils.getField(field, lifecycleElement); 806 807 cleanContextDeap(nestedElement); 808 } 809 } 810 } 811 812 /** 813 * Performs sorting logic of the given list of <code>Ordered</code> 814 * instances by its order property 815 * 816 * <p> 817 * Items list is sorted based on its order property. Lower order values are 818 * placed higher in the list. If a item does not have a value assigned for 819 * the order (or is equal to the default order of 0), it will be assigned 820 * the a value based on the given order sequence integer. If two or more 821 * items share the same order value, all but the last item found in the list 822 * will be removed. 823 * </p> 824 * 825 * @param <T> ordered type 826 * @param <T> ordered type 827 * @param items 828 * @param defaultOrderSequence 829 * @return List<Ordered> sorted items 830 * @see org.kuali.rice.krad.uif.component.Component#getOrder() 831 * @see org.springframework.core.Ordered 832 */ 833 public static <T extends Ordered> List<T> sort(List<T> items, int defaultOrderSequence) { 834 if (items == null) { 835 return null; 836 } 837 838 List<T> orderedItems = new ArrayList<T>(items.size()); 839 840 // do replacement for items with the same order property value 841 Set<Integer> foundOrders = new HashSet<Integer>(); 842 843 // reverse the list, so items later in the list win 844 for (int i = items.size() - 1; i >= 0; i--) { 845 T component = items.get(i); 846 int order = component.getOrder(); 847 848 // if order not set just add to list 849 if (order == 0) { 850 orderedItems.add(component); 851 } 852 // check if the order value has been used already 853 else if (!foundOrders.contains(Integer.valueOf(order))) { 854 orderedItems.add(component); 855 foundOrders.add(Integer.valueOf(order)); 856 } 857 } 858 859 // now reverse the list back so we can assign defaults for items without 860 // an order value 861 for (int i = 0; i < items.size(); i++) { 862 Ordered component = items.get(i); 863 int order = component.getOrder(); 864 865 // if order property not set assign default 866 if (order == 0) { 867 defaultOrderSequence++; 868 while (foundOrders.contains(Integer.valueOf(defaultOrderSequence))) { 869 defaultOrderSequence++; 870 } 871 component.setOrder(defaultOrderSequence); 872 } 873 } 874 875 // now sort the list by its order property 876 Collections.sort(orderedItems, new OrderComparator()); 877 878 return orderedItems; 879 } 880 881 /** 882 * Gets all the input fields contained in this container, but also in 883 * every sub-container that is a child of this container. When called from the top level 884 * View this will be every InputField across all pages. 885 * 886 * @param container container to scan for input fields 887 * @return every InputField that is a child at any level of this container 888 */ 889 public static List<InputField> getAllInputFieldsWithinContainer(Container container) { 890 List<InputField> inputFields = new ArrayList<InputField>(); 891 892 for (LifecycleElement c : ViewLifecycleUtils.getElementsForLifecycle(container).values()) { 893 if (c instanceof InputField) { 894 inputFields.add((InputField) c); 895 } else if (c instanceof Container) { 896 inputFields.addAll(getAllInputFieldsWithinContainer((Container) c)); 897 } else if (c instanceof FieldGroup) { 898 Container cb = ((FieldGroup) c).getGroup(); 899 900 inputFields.addAll(getAllInputFieldsWithinContainer(cb)); 901 } 902 } 903 904 return inputFields; 905 } 906 907 /** 908 * Determines whether the given component contains an expression for the given property name 909 * 910 * @param component component instance to check for expressions 911 * @param propertyName name of the property to determine if there is an expression for 912 * @param collectionMatch if set to true will find an expressions for properties that start with the given 913 * property name (for matching expressions on collections like prop[index] or prop['key']) 914 * @return true if the component has an expression for the property name, false if not 915 */ 916 public static boolean containsPropertyExpression(Component component, String propertyName, 917 boolean collectionMatch) { 918 boolean hasExpression = false; 919 920 Map<String, String> propertyExpressions = component.getPropertyExpressions(); 921 922 if (collectionMatch) { 923 for (String expressionPropertyName : propertyExpressions.keySet()) { 924 if (expressionPropertyName.startsWith(propertyName)) { 925 hasExpression = true; 926 } 927 } 928 } else if (propertyExpressions.containsKey(propertyName)) { 929 hasExpression = true; 930 } 931 932 return hasExpression; 933 } 934 935 /** 936 * Adjust nestingLevel properties for collections which use RichTable with forceLocalJsonData on and for all of its 937 * potentially additional nested subcollections 938 * 939 * @param container container to traverse and update nested levels in 940 * @param currentLevel the current nesting level, the initial call to this method should be 0 941 */ 942 public static void adjustNestedLevelsForTableCollections(Container container, int currentLevel) { 943 if (container != null 944 && container instanceof CollectionGroup 945 && container.getLayoutManager() != null 946 && container.getLayoutManager() instanceof TableLayoutManager 947 && ((TableLayoutManager) container.getLayoutManager()).getRichTable() != null 948 && ((TableLayoutManager) container.getLayoutManager()).getRichTable().isRender() 949 && ((TableLayoutManager) container.getLayoutManager()).getRichTable().isForceLocalJsonData()) { 950 ((TableLayoutManager) container.getLayoutManager()).getRichTable().setNestedLevel(currentLevel); 951 currentLevel++; 952 } 953 954 if (container != null) { 955 List<Container> subContainers = ViewLifecycleUtils.getNestedElementsOfTypeShallow(container, 956 Container.class); 957 for (Container subContainer : subContainers) { 958 adjustNestedLevelsForTableCollections(subContainer, currentLevel); 959 } 960 961 List<FieldGroup> subFieldGroups = ViewLifecycleUtils.getNestedElementsOfTypeShallow(container, 962 FieldGroup.class); 963 for (FieldGroup fieldGroup : subFieldGroups) { 964 if (fieldGroup != null) { 965 adjustNestedLevelsForTableCollections(fieldGroup.getGroup(), currentLevel); 966 } 967 } 968 } 969 } 970 971}