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}