001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.activemq.tool.properties;
018    
019    import java.lang.reflect.Constructor;
020    import java.lang.reflect.InvocationTargetException;
021    import java.lang.reflect.Method;
022    import java.util.ArrayList;
023    import java.util.Iterator;
024    import java.util.List;
025    import java.util.Properties;
026    import java.util.StringTokenizer;
027    
028    import org.slf4j.Logger;
029    import org.slf4j.LoggerFactory;
030    
031    public final class ReflectionUtil {
032        private static final Logger LOG = LoggerFactory.getLogger(ReflectionUtil.class);
033    
034        private ReflectionUtil() {
035        }
036    
037        public static void configureClass(Object obj, String key, String val) {
038            try {
039                String debugInfo;
040    
041                Object target = obj;
042                Class targetClass = obj.getClass();
043    
044                // DEBUG: Debugging Info
045                debugInfo = "Invoking: " + targetClass.getName();
046    
047                StringTokenizer tokenizer = new StringTokenizer(key, ".");
048                String keySubString = key;
049                int tokenCount = tokenizer.countTokens();
050    
051                // For nested settings, get the object first. -1, do not count the
052                // last token
053                for (int j = 0; j < tokenCount - 1; j++) {
054                    // Find getter method first
055                    String name = tokenizer.nextToken();
056    
057                    // Check if the target object will accept the settings
058                    if (target instanceof ReflectionConfigurable && !((ReflectionConfigurable)target).acceptConfig(keySubString, val)) {
059                        return;
060                    } else {
061                        // This will reduce the key, so that it will be recognize by
062                        // the next object. i.e.
063                        // Property name: factory.prefetchPolicy.queuePrefetch
064                        // Calling order:
065                        // this.getFactory().prefetchPolicy().queuePrefetch();
066                        // If factory does not accept the config, it should be given
067                        // prefetchPolicy.queuePrefetch as the key
068                        // +1 to account for the '.'
069                        keySubString = keySubString.substring(name.length() + 1);
070                    }
071    
072                    String getMethod = "get" + name.substring(0, 1).toUpperCase() + name.substring(1);
073                    Method method = targetClass.getMethod(getMethod, new Class[] {});
074                    target = method.invoke(target, null);
075                    targetClass = target.getClass();
076    
077                    debugInfo += "." + getMethod + "()";
078                }
079    
080                // Property name
081                String property = tokenizer.nextToken();
082                // Check if the target object will accept the settings
083                if (target instanceof ReflectionConfigurable && !((ReflectionConfigurable)target).acceptConfig(property, val)) {
084                    return;
085                }
086    
087                // Find setter method
088                Method setterMethod = findSetterMethod(targetClass, property);
089    
090                // Get the first parameter type. This assumes that there is only one
091                // parameter.
092                if (setterMethod == null) {
093                    throw new IllegalAccessException("Unable to find appropriate setter method signature for property: " + property);
094                }
095                Class paramType = setterMethod.getParameterTypes()[0];
096    
097                // Set primitive type
098                debugInfo += "." + setterMethod + "(" + paramType.getName() + ": " + val + ")";
099                if (paramType.isPrimitive()) {
100                    if (paramType == Boolean.TYPE) {
101                        setterMethod.invoke(target, new Object[] {
102                            Boolean.valueOf(val)
103                        });
104                    } else if (paramType == Integer.TYPE) {
105                        setterMethod.invoke(target, new Object[] {
106                            Integer.valueOf(val)
107                        });
108                    } else if (paramType == Long.TYPE) {
109                        setterMethod.invoke(target, new Object[] {
110                            Long.valueOf(val)
111                        });
112                    } else if (paramType == Double.TYPE) {
113                        setterMethod.invoke(target, new Object[] {
114                            Double.valueOf(val)
115                        });
116                    } else if (paramType == Float.TYPE) {
117                        setterMethod.invoke(target, new Object[] {
118                            Float.valueOf(val)
119                        });
120                    } else if (paramType == Short.TYPE) {
121                        setterMethod.invoke(target, new Object[] {
122                            Short.valueOf(val)
123                        });
124                    } else if (paramType == Byte.TYPE) {
125                        setterMethod.invoke(target, new Object[] {
126                            Byte.valueOf(val)
127                        });
128                    } else if (paramType == Character.TYPE) {
129                        setterMethod.invoke(target, new Object[] {
130                            new Character(val.charAt(0))
131                        });
132                    }
133                } else {
134                    // Set String type
135                    if (paramType == String.class) {
136                        setterMethod.invoke(target, new Object[] {
137                            val
138                        });
139    
140                        // For unknown object type, try to create an instance of the
141                        // object using a String constructor
142                    } else {
143                        Constructor c = paramType.getConstructor(new Class[] {
144                            String.class
145                        });
146                        Object paramObject = c.newInstance(new Object[] {
147                            val
148                        });
149    
150                        setterMethod.invoke(target, new Object[] {
151                            paramObject
152                        });
153                    }
154                }
155                LOG.debug(debugInfo);
156    
157            } catch (Exception e) {
158                LOG.warn(e.toString());
159            }
160        }
161    
162        public static void configureClass(Object obj, Properties props) {
163            for (Iterator i = props.keySet().iterator(); i.hasNext();) {
164                try {
165                    String key = (String)i.next();
166                    String val = props.getProperty(key);
167    
168                    configureClass(obj, key, val);
169                } catch (Throwable t) {
170                    // Let's catch any exception as this could be cause by the
171                    // foreign class
172                    t.printStackTrace();
173                }
174            }
175        }
176    
177        public static Properties retrieveObjectProperties(Object obj) {
178            Properties props = new Properties();
179            try {
180                props.putAll(retrieveClassProperties("", obj.getClass(), obj));
181            } catch (Exception e) {
182                LOG.warn(e.toString());
183            }
184            return props;
185        }
186    
187        protected static Properties retrieveClassProperties(String prefix, Class targetClass, Object targetObject) {
188            if (targetClass == null || targetObject == null) {
189                return new Properties();
190            } else {
191                Properties props = new Properties();
192                Method[] getterMethods = findAllGetterMethods(targetClass);
193                for (int i = 0; i < getterMethods.length; i++) {
194                    try {
195                        String propertyName = getPropertyName(getterMethods[i].getName());
196                        Class retType = getterMethods[i].getReturnType();
197    
198                        // If primitive or string type, return it
199                        if (retType.isPrimitive() || retType == String.class) {
200                            // Check for an appropriate setter method to consider it
201                            // as a property
202                            if (findSetterMethod(targetClass, propertyName) != null) {
203                                Object val = null;
204                                try {
205                                    val = getterMethods[i].invoke(targetObject, null);
206                                } catch (InvocationTargetException e) {
207                                    e.printStackTrace();
208                                } catch (IllegalAccessException e) {
209                                    e.printStackTrace();
210                                }
211                                props.setProperty(prefix + propertyName, val + "");
212                            }
213                        } else {
214                            try {
215                                Object val = getterMethods[i].invoke(targetObject, null);
216                                if (val != null && val != targetObject) {
217                                    props.putAll(retrieveClassProperties(propertyName + ".", val.getClass(), val));
218                                }
219                            } catch (InvocationTargetException e) {
220                                e.printStackTrace();
221                            } catch (IllegalAccessException e) {
222                                e.printStackTrace();
223                            }
224                        }
225                    } catch (Throwable t) {
226                        // Let's catch any exception, cause this could be cause by
227                        // the foreign class
228                        t.printStackTrace();
229                    }
230                }
231                return props;
232            }
233        }
234    
235        private static Method findSetterMethod(Class targetClass, String propertyName) {
236            String methodName = "set" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
237    
238            Method[] methods = targetClass.getMethods();
239            for (int i = 0; i < methods.length; i++) {
240                if (methods[i].getName().equals(methodName) && isSetterMethod(methods[i])) {
241                    return methods[i];
242                }
243            }
244            return null;
245        }
246    
247        private static Method findGetterMethod(Class targetClass, String propertyName) {
248            String methodName1 = "get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
249            String methodName2 = "is" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
250    
251            Method[] methods = targetClass.getMethods();
252            for (int i = 0; i < methods.length; i++) {
253                if ((methods[i].getName().equals(methodName1) || methods[i].getName().equals(methodName2)) && isGetterMethod(methods[i])) {
254                    return methods[i];
255                }
256            }
257            return null;
258        }
259    
260        private static Method[] findAllGetterMethods(Class targetClass) {
261            List getterMethods = new ArrayList();
262            Method[] methods = targetClass.getMethods();
263    
264            for (int i = 0; i < methods.length; i++) {
265                if (isGetterMethod(methods[i])) {
266                    getterMethods.add(methods[i]);
267                }
268            }
269    
270            return (Method[])getterMethods.toArray(new Method[] {});
271        }
272    
273        private static boolean isGetterMethod(Method method) {
274            // Check method signature first
275            // If 'get' method, must return a non-void value
276            // If 'is' method, must return a boolean value
277            // Both must have no parameters
278            // Method must not belong to the Object class to prevent infinite loop
279            return ((method.getName().startsWith("is") && method.getReturnType() == Boolean.TYPE) || (method.getName().startsWith("get") && method.getReturnType() != Void.TYPE))
280                   && (method.getParameterTypes().length == 0) && method.getDeclaringClass() != Object.class;
281        }
282    
283        private static boolean isSetterMethod(Method method) {
284            // Check method signature first
285            if (method.getName().startsWith("set") && method.getReturnType() == Void.TYPE) {
286                Class[] paramType = method.getParameterTypes();
287                // Check that it can only accept one parameter
288                if (paramType.length == 1) {
289                    // Check if parameter is a primitive or can accept a String
290                    // parameter
291                    if (paramType[0].isPrimitive() || paramType[0] == String.class) {
292                        return true;
293                    } else {
294                        // Check if object can accept a string as a constructor
295                        try {
296                            if (paramType[0].getConstructor(new Class[] {
297                                String.class
298                            }) != null) {
299                                return true;
300                            }
301                        } catch (NoSuchMethodException e) {
302                            // Do nothing
303                        }
304                    }
305                }
306            }
307            return false;
308        }
309    
310        private static String getPropertyName(String methodName) {
311            String name;
312            if (methodName.startsWith("get")) {
313                name = methodName.substring(3);
314            } else if (methodName.startsWith("set")) {
315                name = methodName.substring(3);
316            } else if (methodName.startsWith("is")) {
317                name = methodName.substring(2);
318            } else {
319                name = "";
320            }
321    
322            return name.substring(0, 1).toLowerCase() + name.substring(1);
323        }
324    }