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 */
017package org.apache.activemq.tool.properties;
018
019import java.lang.reflect.Constructor;
020import java.lang.reflect.InvocationTargetException;
021import java.lang.reflect.Method;
022import java.util.ArrayList;
023import java.util.Iterator;
024import java.util.List;
025import java.util.Properties;
026import java.util.StringTokenizer;
027
028import org.slf4j.Logger;
029import org.slf4j.LoggerFactory;
030
031public 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<Object> 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}