001    /*
002     * $Id: ReflectionHelper.java,v 1.12 2012/02/09 17:56:39 oboehm Exp $
003     *
004     * Copyright (c) 2009 by Oliver Boehm
005     *
006     * Licensed under the Apache License, Version 2.0 (the "License");
007     * you may not use this file except in compliance with the License.
008     * You may obtain a copy of the License at
009     *
010     *   http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express orimplied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     *
018     * (c)reated 09.03.2009 by oliver (ob@aosd.de)
019     */
020    package patterntesting.runtime.util;
021    
022    import java.lang.reflect.*;
023    import java.util.*;
024    
025    import org.slf4j.*;
026    
027    /**
028     * This class is a helper class to access some fields via reflection. Normally
029     * you should avoid reflection. Handle with care if you use it.
030     *
031     * @author <a href="boehm@javatux.de">oliver</a>
032     * @since 09.03.2009
033     * @version $Revision: 1.12 $
034     */
035    public class ReflectionHelper {
036    
037         private static final Logger log = LoggerFactory.getLogger(ReflectionHelper.class);
038    
039         /** Utility class - no need to instantiate it */
040         private ReflectionHelper() {}
041    
042        /**
043         * Tries to get the wanted field. Before it is returned the acc
044         *
045         * @param cl the cl
046         * @param name the name
047         *
048         * @return the wanted field
049         *
050         * @throws NoSuchFieldException the no such field exception
051         */
052        public static Field getField(final Class<?> cl, final String name)
053                throws NoSuchFieldException {
054            try {
055                Field field = cl.getDeclaredField(name);
056                field.setAccessible(true);
057                return field;
058            } catch (NoSuchFieldException e) {
059                Class<?> superclass = cl.getSuperclass();
060                if (superclass == null) {
061                    throw e;
062                } else {
063                    if (log.isTraceEnabled()) {
064                        log.trace("using " + superclass + " to get " + cl.getName()
065                                + "." + name + "...");
066                    }
067                    return ReflectionHelper.getField(superclass, name);
068                }
069            }
070        }
071    
072        /**
073         * If it can it returns the value of the given field. If not it throws an
074         * exception.
075         *
076         * @param obj the obj
077         * @param name the name
078         * @return the field value
079         * @throws NoSuchFieldException the no such field exception
080         * @throws IllegalArgumentException the illegal argument exception
081         * @throws IllegalAccessException the illegal access exception
082         */
083        public static Object getFieldValue(final Object obj, final String name)
084                throws NoSuchFieldException, IllegalArgumentException,
085                IllegalAccessException {
086            Field field = ReflectionHelper.getField(obj.getClass(), name);
087            return field.get(obj);
088        }
089    
090        /**
091         * To get all uninitialized field you can call this method.
092         *
093         * @param obj the object where to get the fields from
094         *
095         * @return a collecion of unitialized fields (can be empty)
096         */
097        public static Collection<Field> getUninitializedNonStaticFields(final Object obj) {
098            Collection<Field> unitializedFields = new ArrayList<Field>();
099            Field[] fields = obj.getClass().getDeclaredFields();
100            for (int i = 0; i < fields.length; i++) {
101                try {
102                    fields[i].setAccessible(true);
103                    if ((fields[i].get(obj) == null) && !isStatic(fields[i])) {
104                        unitializedFields.add(fields[i]);
105                    }
106                } catch (IllegalArgumentException e) {
107                    log.info(e + " => " + fields[i] + " ignored");
108                } catch (IllegalAccessException e) {
109                    log.debug("can't access " + fields[i] + " => ignored");
110                }
111            }
112            return unitializedFields;
113        }
114    
115        /**
116         * Checks if the given field is static.
117         *
118         * @param field the field
119         * @return true, if is static
120         */
121        public static boolean isStatic(final Field field) {
122            int m = field.getModifiers();
123            return Modifier.isStatic(m);
124        }
125    
126        /**
127         * To short string.
128         *
129         * @param field the field
130         *
131         * @return the string
132         */
133        public static String toShortString(final Field field) {
134            return field.getType().getSimpleName() + " " + field.getName();
135        }
136    
137        /**
138         * To short string.
139         *
140         * @param fields the fields
141         *
142         * @return the string
143         */
144        public static String toShortString(final Collection<Field> fields) {
145            StringBuffer sbuf = new StringBuffer();
146            for (Iterator<Field> iterator = fields.iterator(); iterator.hasNext();) {
147                Field field = iterator.next();
148                sbuf.append(", ");
149                sbuf.append(toShortString(field));
150            }
151            return sbuf.substring(2);
152        }
153    
154        /**
155         * Gets the method.
156         *
157         * @param cl the cl
158         * @param name the name
159         * @param args the args
160         * @return the method
161         * @throws NoSuchMethodException the no such method exception
162         */
163        public static Method getMethod(final Class<?> cl, final String name, final Object... args)
164                throws NoSuchMethodException {
165            Class<?> parameterTypes[] = new Class<?>[args.length];
166            for (int i = 0; i < args.length; i++) {
167                parameterTypes[i] = args[i].getClass();
168            }
169            return getMethod(cl, name, parameterTypes);
170        }
171    
172        /**
173         * Gets the method.
174         *
175         * @param cl e.g. class java.lang.ClassLoader
176         * @param name e.g. "getPackages"
177         * @param parameterTypes the parameter types
178         * @return a Method object
179         * @throws NoSuchMethodException the no such method exception
180         */
181        public static Method getMethod(final Class<?> cl, final String name,
182                final Class<?>... parameterTypes) throws NoSuchMethodException {
183                try {
184                    return cl.getDeclaredMethod(name, parameterTypes);
185                } catch (NoSuchMethodException origException) {
186                    try {
187                    try {
188                        return findMethod(cl, name, parameterTypes);
189                    } catch (NoSuchMethodException nsme) {
190                            Class<?> superclass = cl.getSuperclass();
191                            if (superclass == null) {
192                                throw origException;
193                            } else {
194                                return getMethod(superclass, name, parameterTypes);
195                            }
196                    }
197                } catch (NoSuchMethodException derived) {
198                    throw origException;
199                }
200                }
201            }
202        
203        private static Method findMethod(final Class<?> cl, final String name,
204                final Class<?>... parameterTypes) throws NoSuchMethodException {
205            Method[] methods = cl.getDeclaredMethods();
206            for (int i = 0; i < methods.length; i++) {
207                if (name.equals(methods[i].getName()) && matchesParameters(methods[i], parameterTypes)) {
208                    return methods[i];
209                }
210            }
211            throw new NoSuchMethodException(cl.getName() + "." + name + "("
212                    + Converter.toShortString(parameterTypes) + ")");
213        }
214            
215        private static boolean matchesParameters(final Method method, final Class<?>[] matchingTypes) {
216            Class<?>[] parameterTypes = method.getParameterTypes();
217            if (parameterTypes.length != matchingTypes.length) {
218                return false;
219            }
220            for (int i = 0; i < parameterTypes.length; i++) {
221                if (!matches(parameterTypes[i], matchingTypes[i])) {
222                    return false;
223                }
224            }
225            return true;
226        }
227        
228        private static boolean matches(final Class<?> parameterType, final Class<?> matchingType) {
229            if (parameterType.isAssignableFrom(matchingType)) {
230                return true;
231            }
232            if (parameterType.isPrimitive()) {
233                if (parameterType.equals(boolean.class)) {
234                    return matchingType.equals(Boolean.class);
235                } else if (parameterType.equals(byte.class)) {
236                    return matchingType.equals(Byte.class);
237                } else if (parameterType.equals(char.class)) {
238                    return matchingType.equals(Character.class);
239                } else if (parameterType.equals(short.class)) {
240                    return matchingType.equals(Short.class);
241                } else if (parameterType.equals(int.class)) {
242                    return matchingType.equals(Integer.class);
243                } else if (parameterType.equals(long.class)) {
244                    return matchingType.equals(Long.class);
245                } else if (parameterType.equals(float.class)) {
246                    return matchingType.equals(Float.class);
247                } else if (parameterType.equals(double.class)) {
248                    return matchingType.equals(Double.class);
249                } else {
250                    log.warn("unknown primitive type \"" + parameterType + "\" not yet supported - sorry!");
251                    return false;
252                }
253            }
254            return false;
255        }
256    
257        /**
258             * Invoke method. This could be also a protected or private method.
259             *
260             * @param target the target
261             * @param name the method name
262             * @param args the args for the method
263             * @return the result of the method.
264             */
265        public static Object invokeMethod(final Object target, final String name, final Object... args) {
266                try {
267                Method method = getMethod(target.getClass(), name, args);
268                method.setAccessible(true);
269                return method.invoke(target, args);
270            } catch (NoSuchMethodException e) {
271                throw new IllegalArgumentException(
272                        "no method \"" + name + "(" + toParamString(args) + ")\" in " + target.getClass(), e);
273            } catch (IllegalAccessException iae) {
274                throw new RuntimeException("can't access method \"" + name + "\" in "
275                        + target.getClass(), iae);
276            } catch (InvocationTargetException ite) {
277                throw new RuntimeException("exception in method \"" + name + "\" of "
278                        + target.getClass(), ite.getTargetException());
279            }
280            }
281    
282        private static String toParamString(final Object[] args) {
283            Class<?> paramTypes[] = new Class<?>[args.length];
284            for (int i = 0; i < args.length; i++) {
285                paramTypes[i] = args[i].getClass();
286            }
287            return Converter.toShortString(paramTypes);
288        }
289    
290    }