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