001    package echopoint.util;
002    /* 
003     * This file is part of the Echo Point Project.  This project is a collection
004     * of Components that have extended the Echo Web Application Framework.
005     *
006     * Version: MPL 1.1/GPL 2.0/LGPL 2.1
007     *
008     * The contents of this file are subject to the Mozilla Public License Version
009     * 1.1 (the "License"); you may not use this file except in compliance with
010     * the License. You may obtain a copy of the License at
011     * http://www.mozilla.org/MPL/
012     *
013     * Software distributed under the License is distributed on an "AS IS" basis,
014     * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
015     * for the specific language governing rights and limitations under the
016     * License.
017     *
018     * Alternatively, the contents of this file may be used under the terms of
019     * either the GNU General Public License Version 2 or later (the "GPL"), or
020     * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
021     * in which case the provisions of the GPL or the LGPL are applicable instead
022     * of those above. If you wish to allow use of your version of this file only
023     * under the terms of either the GPL or the LGPL, and not to allow others to
024     * use your version of this file under the terms of the MPL, indicate your
025     * decision by deleting the provisions above and replace them with the notice
026     * and other provisions required by the GPL or the LGPL. If you do not delete
027     * the provisions above, a recipient may use your version of this file under
028     * the terms of any one of the MPL, the GPL or the LGPL.
029     */
030    import java.lang.reflect.Method;
031    import java.util.ArrayList;
032    import java.util.Collections;
033    import java.util.List;
034    import java.util.Stack;
035    
036    import nextapp.echo.app.Component;
037    import nextapp.echo.app.ApplicationInstance;
038    import nextapp.echo.app.Window;
039    
040    /**
041     * A utility to class to help with Component manipulation
042     *
043     * @author Brad Baker 
044     */
045    public class ComponentKit {
046            
047            /**
048             * <code>ComponentOperationCallBack</code> is an interface
049             * used during the traverseComponent() method.  The onComponent()
050             * method is called for each component in the component 
051             * hierarchy.
052             */
053            public static interface ComponentTraversalCallBack {
054                    /**
055                     * Called for each component in a component hierarchy.
056                     *  
057                     * @param c - the component in a hierarchy
058                     */             
059                    public void onComponent(Component c); 
060            }
061    
062            /** not instantiable */
063            private ComponentKit() {
064            }
065    
066            /**
067             * This method will remove a child component from anywhere in the
068             * component hierarchy from parent downwards.
069             *  
070             * @param parent - an ancestor of child
071             * @param child - the component to remove
072             * @return true if the component was actually removed, or false if
073             *        the parent is not an ancestor of child.
074             */
075            public static boolean remove(Component parent, Component child) {
076                    if (parent == null || child == null)
077                            return false;
078                    boolean isAncestor = parent.isAncestorOf(child);
079                    if (isAncestor) {
080                            parent = child.getParent();
081                            parent.remove(child);
082                            return true;
083                    }
084                    return false;
085            }
086            
087            /**
088             * This method will return an array of Components that indicates the path from
089             * parent to child.  The parent Component (ie the root of the path) is the first 
090             * element in the array, while the child will be the last.  If the parent is not
091             * an ancestor of the child, then a zero length array will be returned.
092             * 
093             * @param parent the parent component to be searched
094             * @param child - the child component to find.
095             * @return an array of Components between parent and child or a zero length
096             *         array if there is no path.
097             */
098            public static Component[] getComponentPath(Component parent, Component child) {
099                    if (parent == null || child == null)
100                            return new Component[0];
101                    if (! parent.isAncestorOf(child))
102                            return new Component[0];
103    
104                    List compList = new ArrayList();
105                    while (child.getParent() != parent) {
106                            compList.add(child);
107                            child = child.getParent();
108                    }
109                    compList.add(child);
110                    compList.add(parent);
111                    Collections.reverse(compList);
112                    
113                    return (Component[]) compList.toArray(new Component[compList.size()]);
114            }
115            
116            /**
117             * Returns the highest Component parent starting from child.  This is in
118             * fact ths parent that has no parent.
119             *  
120             * @param child - the component to start searching at
121             * @return - the higest parent Component
122             */
123            public static Component getComponentRoot(Component child) {
124                    Component parent = null;
125                    do {
126                            parent = child.getParent();
127                            if (parent != null)
128                                    child = parent;
129                    }
130                    while (parent != null);
131                    return child;
132            }
133    
134            /**
135             * Returns the Window contaning the child or null if the child
136             * is not contained within a Window.
137             *  
138             * @param child - the component to start searching at
139             * @return - the Window contaning the child or null if the child
140             * is not contained within a Window.
141             */
142            public static Window getComponentWindow(Component child) {
143                    Component parent = getComponentRoot(child);
144                    if (parent instanceof Window)
145                            return (Window) parent;
146                    else
147                            return null;
148            }
149            
150            /**
151             * This method makes the component, and all its children, visible or invisible.
152             *  
153             * @param component - the component inquestion
154             * @param visible - true to make all components visible or 
155             *                                      false to make them invisible
156             */     
157            public static void setVisible(Component component, boolean visible) {
158                    if (component == null)
159                            return;
160                    component.setVisible(visible);
161                    Component[] children = component.getComponents();
162                    for (int i = 0; i < children.length; i++) {
163                            setVisible(children[i],visible);
164                    }       
165            }
166            
167            /**
168             * This method makes the component, and all its children, enabled or disabled.
169             *  
170             * @param component - the component inquestion
171             * @param enabled - true to make all components enabled or 
172             *                                      false to make them disabled
173             */     
174            public static void setEnabled(Component component, boolean enabled) {
175                    if (component == null)
176                            return;
177                    component.setEnabled(enabled);
178                    Component[] children = component.getComponents();
179                    for (int i = 0; i < children.length; i++) {
180                            setEnabled(children[i],enabled);
181                    }       
182            }
183            
184            /**
185             * This method will traverse the current component and all its children,
186             * in a depth first manner, and call the ComponentTraversalCallBack
187             * object for each component found. 
188             *  
189             * @param startComponent - the component to start at.
190             * @param traverseCallBack - the ComponentTraversalCallBack to call when a component is found
191             */
192            public static void traverseComponent(Component startComponent, ComponentTraversalCallBack traverseCallBack) {
193                    if (startComponent != null) {
194                            Stack stack = new Stack();
195                            stack.push(startComponent);
196                            while (! stack.isEmpty()) {
197                                    Component c = (Component) stack.pop();
198                                    traverseCallBack.onComponent(c);
199                                    Component[] children = c.getComponents();
200                                    for (int i = 0; i < children.length; i++) {
201                                            stack.push(children[i]);
202                                    }
203                            }
204                    }
205            }
206    
207            /**
208             * This method will traverse the EchoInstance and all its child
209             * components, in a depth first manner, and call the ComponentTraversalCallBack
210             * object for each component found. 
211             *  
212             * @param instance - the ApplicationInstance to traverse.
213             * @param traverseCallBack - the ComponentTraversalCallBack to call when a component is found
214             */
215            public static void traverseInstance(ApplicationInstance instance, ComponentTraversalCallBack traverseCallBack) {
216                    if (instance != null) {
217    //                      for (int i = 0; i < instance.getWindows().length; i++) {
218    //                              traverseComponent(instance.getWindows()[i],traverseCallBack);
219    //                      }
220                            if (instance.getDefaultWindow() != null) {
221                                    traverseComponent(instance.getDefaultWindow(),traverseCallBack);
222                            }
223                    }
224            }
225    
226    
227            /**
228             * This method will use reflection to set a property on a given component, and 
229             * all of its children.  For example you could set the component's font via :
230             * <p>
231             * <blockquote><pre>
232             *              ComponentKit.setProperty(myComponent, "font", new Font(Font.VERDANA,Font.BOLD,8));
233             * </pre></blockquote>
234             * <p>
235             * This method uses JavaBean rules for setting properties, that is it will look
236             * for a method called setXxx that takes one parameter, where Xxx is the setter method of the property to
237             * be set.  Failing that it will look for setXX and finally just XX.
238             * <p>
239             * This method guarantees not to throw any exceptions, but rather return false if
240             * it cannot set the properties on an eligble object.
241             *  
242             * @param component - the component in question
243             * @param propertyName - then name of the property to set
244             * @param propertyValue - the new value
245             * @return true if the property could be set.
246             */
247            public static boolean setProperty(Component component, String propertyName, Object propertyValue) {
248                    return setProperty(component, propertyName, propertyValue, null);
249            }
250                            
251            /**
252             * This method will use reflection to set a property on a given component, and 
253             * all of its children.  For example you could set the component's font via :
254             * <p>
255             * <blockquote><pre>
256             *              ComponentKit.setProperty(myComponent, "font", new Font(Font.VERDANA,Font.BOLD,8), RadionButton.class);
257             * </pre></blockquote>
258             * <p>
259             * This method uses JavaBean rules for setting properties, that is it will look
260             * for a method called setXxx that takes one parameter, where Xxx is the setter method of the property to
261             * be set.  Failing that it will look for setXX and finally just XX.
262             * <p>
263             * If a non null <code>filterClass</code> is provided then it will only set properties on components
264             * that are assignable from the filterClass.  If the filterClass is null, then all
265             * components will be matched.
266             * <p>
267             * This method guarantees not to throw any exceptions, but rather return false if
268             * it cannot set the properties on an eligble object.
269             *  
270             * @param component -           the component in question
271             * @param propertyName -        then name of the property to set
272             * @param propertyValue -       the new value
273             * @param filterClass -         only sets component derived from the filter class or matches
274             *                                                      all components if its null.
275             * @return true if the property could be set.
276             */
277            public static boolean setProperty(Component component, String propertyName, Object propertyValue, Class filterClass) {
278                    if (component == null || propertyName == null || propertyName.length() <= 0)
279                            return false;
280    
281                    boolean setOK = true;                   
282                    Class componentClass = component.getClass();
283                    if (filterClass == null || filterClass.isAssignableFrom(componentClass)) {
284                            Method setter = _getSetter(componentClass,propertyName,propertyValue);
285                            if (setter != null) {
286                                    try {
287                                            setter.invoke(component,new Object[] { propertyValue });
288                                            setOK = true;
289                                    } catch (Exception e) {
290                                            setOK = false;
291                                    }
292                            }
293                    }
294                    Component[] children = component.getComponents();
295                    for (int i = 0; i < children.length; i++) {
296                            boolean wasOK = setProperty(children[i],propertyName, propertyValue, filterClass);
297                            if (! wasOK)
298                                    setOK = false;
299                    }       
300                    return setOK;
301            }
302            
303            /**
304             * Private method to find the setter of a given propertyName. You must
305             * provide a 1 length string or better.  Trys setXxx, then setXXX and finally
306             * just XXXX.
307             */
308            private static Method _getSetter(Class clazz, String propertyName, Object propertyValue) {
309                    StringBuffer sb;
310                    String methodName;
311                    Class[] params = (propertyValue == null) ? null : new Class[] { propertyValue.getClass()};
312                    Method setter = null;
313    
314                    // try setXxxxXxxx
315                    sb  = new StringBuffer("set");
316                    sb.append(propertyName.substring(0,1).toUpperCase());
317                    sb.append(propertyName.substring(1));
318                    methodName = sb.toString();
319                    try {
320                            setter = _findSetterMethod(clazz,methodName,params);
321                            return setter;
322                    } catch (Exception e) {
323                            // no good
324                    }
325                    // try setXxxxxx
326                    sb  = new StringBuffer("set");
327                    sb.append(propertyName.substring(0,1).toUpperCase());
328                    if (propertyName.length() >= 2) {
329                            sb.append(propertyName.substring(1).toLowerCase());
330                    }
331                    methodName = sb.toString();
332                    try {
333                            setter = _findSetterMethod(clazz,methodName,params);
334                    } catch (Exception e) {
335                            // no good
336                    }
337                    // try setXXXXX
338                    sb  = new StringBuffer("set");
339                    sb.append(propertyName);
340                    methodName = sb.toString();
341                    try {
342                            setter = _findSetterMethod(clazz,methodName,params);
343                    } catch (Exception e) {
344                            // no good
345                    }
346                    try {
347                            setter = _findSetterMethod(clazz,propertyName,params);
348                    } catch (Exception e) {
349                            // no good
350                    }
351                    return setter;                  
352            }
353    
354            /** Find a matching setter method */    
355            private static Method _findSetterMethod(Class clazz, String methodName, Class[] searchParams) {
356                    Method[] methods = clazz.getMethods();
357                    for (int i=0; i< methods.length; i++) {
358                            Class returnType = methods[i].getReturnType();
359                            Class[] paramTypes = methods[i].getParameterTypes();
360                            Method method = methods[i]; 
361                            if (method.getName().equals(methodName) && paramTypes.length == 1) {
362                                    if (returnType.equals(Void.TYPE)) {
363                                            if (searchParams == null || searchParams.length == 0) {
364                                                    //in this case we dont know the class of the parameters 
365                                                    // probably because the param to be set is null and hence
366                                                    // we take the first one
367                                                    return method;
368                                            }
369                                            Class searchParam = searchParams[0];
370                                            if (paramTypes[0].isAssignableFrom(searchParam))        
371                                                    return method;
372                                            if (searchParam == null)
373                                                    return method;
374                                    }
375                            }
376                    }
377                    return null;    
378            }
379            
380            
381    }