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 }