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.modifier; 017 018import java.util.ArrayList; 019import java.util.HashSet; 020import java.util.LinkedList; 021import java.util.List; 022import java.util.Queue; 023import java.util.Set; 024 025import org.kuali.rice.krad.datadictionary.parse.BeanTag; 026import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute; 027import org.kuali.rice.krad.datadictionary.parse.BeanTags; 028import org.kuali.rice.krad.uif.component.Component; 029import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleUtils; 030import org.kuali.rice.krad.uif.util.ComponentUtils; 031import org.kuali.rice.krad.uif.util.LifecycleElement; 032import org.kuali.rice.krad.uif.util.ObjectPropertyUtils; 033import org.kuali.rice.krad.uif.util.RecycleUtils; 034 035/** 036 * For a given <code>Component</code> instance converts all component properties 037 * of a certain type to instances of another configured <code>Component</code>. 038 * The conversion is performed recursively down all the component children 039 * 040 * <p> 041 * Some example uses of this are converting all checkbox controls to radio group 042 * controls within a group and replacement of a widget with another 043 * </p> 044 * 045 * @author Kuali Rice Team (rice.collab@kuali.org) 046 */ 047@BeanTags({@BeanTag(name = "componentConverterModifier", parent = "Uif-ComponentConverter-Modifier"), 048 @BeanTag(name = "checkboxToRadioConverterModifier", parent = "Uif-CheckboxToRadioConverter-Modifier")}) 049public class ComponentConvertModifier extends ComponentModifierBase { 050 private static final long serialVersionUID = -7566547737669924605L; 051 052 private Class<? extends Component> componentTypeToReplace; 053 054 private Component componentReplacementPrototype; 055 056 public ComponentConvertModifier() { 057 super(); 058 } 059 060 /** 061 * {@inheritDoc} 062 */ 063 @Override 064 public void performModification(Object model, Component component) { 065 if (component == null) { 066 return; 067 } 068 069 int idSuffix = 0; 070 convertToReplacement(component, idSuffix); 071 } 072 073 /** 074 * Reads the component properties and looks for types that match the 075 * configured type to replace. If a match is found, a new instance of the 076 * replacement component prototype is created and set as the property value. 077 * The method is then called for each of the component's children 078 * 079 * @param component component instance to inspect properties for 080 * @param idSuffix suffix string to use for any generated component 081 * replacements 082 */ 083 protected void convertToReplacement(Component component, int idSuffix) { 084 if (component == null) { 085 return; 086 } 087 088 @SuppressWarnings("unchecked") 089 Queue<LifecycleElement> elementQueue = RecycleUtils.getInstance(LinkedList.class); 090 elementQueue.offer(component); 091 092 while (elementQueue.isEmpty()) { 093 LifecycleElement element = elementQueue.poll(); 094 095 elementQueue.addAll(ViewLifecycleUtils.getElementsForLifecycle(element).values()); 096 097 if (!(element instanceof Component)) { 098 continue; 099 } 100 101 // check all component properties for the type to replace 102 Set<String> componentProperties = 103 ObjectPropertyUtils.getReadablePropertyNames(component.getClass()); 104 for (String propertyPath : componentProperties) { 105 Object propValue = ObjectPropertyUtils.getPropertyValue(component, propertyPath); 106 107 if (propValue != null) { 108 if (getComponentTypeToReplace().isAssignableFrom(propValue.getClass())) { 109 // types match, convert the component 110 performConversion(component, propertyPath, idSuffix++); 111 } 112 } 113 } 114 } 115 116 elementQueue.clear(); 117 RecycleUtils.recycle(elementQueue); 118 } 119 120 /** 121 * Creates a new instance of the replacement component prototype and sets a 122 * the property value for the given property name and component instance 123 * 124 * @param component component instance to set property on 125 * @param componentProperty property name to set 126 * @param idSuffix suffix string to use for the generated component 127 */ 128 protected void performConversion(Component component, String componentProperty, int idSuffix) { 129 // create new instance of replacement component 130 Component componentReplacement = ComponentUtils.copy(getComponentReplacementPrototype(), Integer.toString( 131 idSuffix)); 132 133 ObjectPropertyUtils.setPropertyValue(component, componentProperty, componentReplacement); 134 } 135 136 /** 137 * {@inheritDoc} 138 */ 139 @Override 140 public Set<Class<? extends Component>> getSupportedComponents() { 141 Set<Class<? extends Component>> components = new HashSet<Class<? extends Component>>(); 142 components.add(Component.class); 143 144 return components; 145 } 146 147 /** 148 * @see org.kuali.rice.krad.uif.modifier.ComponentModifierBase#getComponentPrototypes() 149 */ 150 public List<Component> getComponentPrototypes() { 151 List<Component> components = new ArrayList<Component>(); 152 153 components.add(componentReplacementPrototype); 154 155 return components; 156 } 157 158 /** 159 * Type of component that should be replaced with an instance of the 160 * component prototype 161 * 162 * @return component type to replace 163 */ 164 @BeanTagAttribute 165 public Class<? extends Component> getComponentTypeToReplace() { 166 return this.componentTypeToReplace; 167 } 168 169 /** 170 * Setter for the component type to replace 171 * 172 * @param componentTypeToReplace 173 */ 174 public void setComponentTypeToReplace(Class<? extends Component> componentTypeToReplace) { 175 this.componentTypeToReplace = componentTypeToReplace; 176 } 177 178 /** 179 * Prototype for the component replacement 180 * 181 * <p> 182 * Each time the type to replace if found a new instance of the component 183 * prototype will be created and set as the new property value 184 * </p> 185 * 186 * @return Component 187 */ 188 @BeanTagAttribute 189 public Component getComponentReplacementPrototype() { 190 return this.componentReplacementPrototype; 191 } 192 193 /** 194 * Setter for the replacement component prototype 195 * 196 * @param componentReplacementPrototype 197 */ 198 public void setComponentReplacementPrototype(Component componentReplacementPrototype) { 199 this.componentReplacementPrototype = componentReplacementPrototype; 200 } 201 202}