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.camel.util;
018
019import java.lang.reflect.Field;
020import java.lang.reflect.Method;
021import java.lang.reflect.Modifier;
022import java.util.Arrays;
023
024/**
025 * Helper for working with reflection on classes.
026 * <p/>
027 * This code is based on org.apache.camel.spring.util.ReflectionUtils class.
028 */
029public final class ReflectionHelper {
030
031    private ReflectionHelper() {
032        // utility class
033    }
034
035    /**
036     * Callback interface invoked on each field in the hierarchy.
037     */
038    public interface FieldCallback {
039
040        /**
041         * Perform an operation using the given field.
042         *
043         * @param field the field to operate on
044         */
045        void doWith(Field field) throws IllegalArgumentException, IllegalAccessException;
046    }
047
048    /**
049     * Action to take on each method.
050     */
051    public interface MethodCallback {
052
053        /**
054         * Perform an operation using the given method.
055         *
056         * @param method the method to operate on
057         */
058        void doWith(Method method) throws IllegalArgumentException, IllegalAccessException;
059    }
060
061    /**
062     * Action to take on each class.
063     */
064    public interface ClassCallback {
065
066        /**
067         * Perform an operation using the given class.
068         *
069         * @param clazz the class to operate on
070         */
071        void doWith(Class clazz) throws IllegalArgumentException, IllegalAccessException;
072    }
073
074    /**
075     * Perform the given callback operation on the nested (inner) classes.
076     *
077     * @param clazz class to start looking at
078     * @param cc the callback to invoke for each inner class (excluding the class itself)
079     */
080    public static void doWithClasses(Class<?> clazz, ClassCallback cc) throws IllegalArgumentException {
081        // and then nested classes
082        Class[] classes = clazz.getDeclaredClasses();
083        for (Class aClazz : classes) {
084            try {
085                cc.doWith(aClazz);
086            } catch (IllegalAccessException ex) {
087                throw new IllegalStateException("Shouldn't be illegal to access class '" + aClazz.getName() + "': " + ex);
088            }
089        }
090    }
091
092    /**
093     * Invoke the given callback on all fields in the target class, going up the
094     * class hierarchy to get all declared fields.
095     * @param clazz the target class to analyze
096     * @param fc the callback to invoke for each field
097     */
098    public static void doWithFields(Class<?> clazz, FieldCallback fc) throws IllegalArgumentException {
099        // Keep backing up the inheritance hierarchy.
100        Class<?> targetClass = clazz;
101        do {
102            Field[] fields = targetClass.getDeclaredFields();
103            for (Field field : fields) {
104                try {
105                    fc.doWith(field);
106                } catch (IllegalAccessException ex) {
107                    throw new IllegalStateException("Shouldn't be illegal to access field '" + field.getName() + "': " + ex);
108                }
109            }
110            targetClass = targetClass.getSuperclass();
111        }
112        while (targetClass != null && targetClass != Object.class);
113    }
114
115    /**
116     * Perform the given callback operation on all matching methods of the given
117     * class and superclasses (or given interface and super-interfaces).
118     * <p/>
119     * <b>Important:</b> This method does not take the
120     * {@link java.lang.reflect.Method#isBridge() bridge methods} into account.
121     *
122     * @param clazz class to start looking at
123     * @param mc the callback to invoke for each method
124     */
125    public static void doWithMethods(Class<?> clazz, MethodCallback mc) throws IllegalArgumentException {
126        // Keep backing up the inheritance hierarchy.
127        Method[] methods = clazz.getDeclaredMethods();
128        for (Method method : methods) {
129            if (method.isBridge()) {
130                // skip the bridge methods which in Java 8 leads to problems with inheritance
131                // see https://bugs.openjdk.java.net/browse/JDK-6695379
132                continue;
133            }
134            try {
135                mc.doWith(method);
136            } catch (IllegalAccessException ex) {
137                throw new IllegalStateException("Shouldn't be illegal to access method '" + method.getName() + "': " + ex);
138            }
139        }
140        if (clazz.getSuperclass() != null) {
141            doWithMethods(clazz.getSuperclass(), mc);
142        } else if (clazz.isInterface()) {
143            for (Class<?> superIfc : clazz.getInterfaces()) {
144                doWithMethods(superIfc, mc);
145            }
146        }
147    }
148    
149    /**
150     * Attempt to find a {@link Method} on the supplied class with the supplied name
151     * and parameter types. Searches all superclasses up to {@code Object}.
152     * <p>Returns {@code null} if no {@link Method} can be found.
153     * @param clazz the class to introspect
154     * @param name the name of the method
155     * @param paramTypes the parameter types of the method
156     * (may be {@code null} to indicate any signature)
157     * @return the Method object, or {@code null} if none found
158     */
159    public static Method findMethod(Class<?> clazz, String name, Class<?>... paramTypes) {
160        ObjectHelper.notNull(clazz, "Class must not be null");
161        ObjectHelper.notNull(name, "Method name must not be null");
162        Class<?> searchType = clazz;
163        while (searchType != null) {
164            Method[] methods = searchType.isInterface() ? searchType.getMethods() : searchType.getDeclaredMethods();
165            for (Method method : methods) {
166                if (name.equals(method.getName()) && (paramTypes == null || Arrays.equals(paramTypes, method.getParameterTypes()))) {
167                    return method;
168                }
169            }
170            searchType = searchType.getSuperclass();
171        }
172        return null;
173    }
174
175    public static void setField(Field f, Object instance, Object value) {
176        try {
177            boolean oldAccessible = f.isAccessible();
178            boolean shouldSetAccessible = !Modifier.isPublic(f.getModifiers()) && !oldAccessible;
179            if (shouldSetAccessible) {
180                f.setAccessible(true);
181            }
182            f.set(instance, value);
183            if (shouldSetAccessible) {
184                f.setAccessible(oldAccessible);
185            }
186        } catch (Exception ex) {
187            throw new UnsupportedOperationException("Cannot inject value of class: " + value.getClass() + " into: " + f);
188        }
189    }
190
191    public static Object getField(Field f, Object instance) {
192        try {
193            boolean oldAccessible = f.isAccessible();
194            boolean shouldSetAccessible = !Modifier.isPublic(f.getModifiers()) && !oldAccessible;
195            if (shouldSetAccessible) {
196                f.setAccessible(true);
197            }
198            Object answer = f.get(instance);
199            if (shouldSetAccessible) {
200                f.setAccessible(oldAccessible);
201            }
202            return answer;
203        } catch (Exception ex) {
204            // ignore
205        }
206        return null;
207    }
208
209}