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    @FunctionalInterface
039    public interface FieldCallback {
040
041        /**
042         * Perform an operation using the given field.
043         *
044         * @param field the field to operate on
045         */
046        void doWith(Field field) throws IllegalArgumentException, IllegalAccessException;
047    }
048
049    /**
050     * Action to take on each method.
051     */
052    @FunctionalInterface
053    public interface MethodCallback {
054
055        /**
056         * Perform an operation using the given method.
057         *
058         * @param method the method to operate on
059         */
060        void doWith(Method method) throws IllegalArgumentException, IllegalAccessException;
061    }
062
063    /**
064     * Action to take on each class.
065     */
066    @FunctionalInterface
067    public interface ClassCallback {
068
069        /**
070         * Perform an operation using the given class.
071         *
072         * @param clazz the class to operate on
073         */
074        void doWith(Class<?> clazz) throws IllegalArgumentException, IllegalAccessException;
075    }
076
077    /**
078     * Perform the given callback operation on the nested (inner) classes.
079     *
080     * @param clazz class to start looking at
081     * @param cc    the callback to invoke for each inner class (excluding the class itself)
082     */
083    public static void doWithClasses(Class<?> clazz, ClassCallback cc) throws IllegalArgumentException {
084        // and then nested classes
085        Class<?>[] classes = clazz.getDeclaredClasses();
086        for (Class<?> aClazz : classes) {
087            try {
088                cc.doWith(aClazz);
089            } catch (IllegalAccessException ex) {
090                throw new IllegalStateException("Shouldn't be illegal to access class '" + aClazz.getName() + "': " + ex);
091            }
092        }
093    }
094
095    /**
096     * Invoke the given callback on all fields in the target class, going up the class hierarchy to get all declared
097     * fields.
098     *
099     * @param clazz the target class to analyze
100     * @param fc    the callback to invoke for each field
101     */
102    public static void doWithFields(Class<?> clazz, FieldCallback fc) throws IllegalArgumentException {
103        // Keep backing up the inheritance hierarchy.
104        Class<?> targetClass = clazz;
105        do {
106            Field[] fields = targetClass.getDeclaredFields();
107            for (Field field : fields) {
108                try {
109                    fc.doWith(field);
110                } catch (IllegalAccessException ex) {
111                    throw new IllegalStateException("Shouldn't be illegal to access field '" + field.getName() + "': " + ex);
112                }
113            }
114            targetClass = targetClass.getSuperclass();
115        } while (targetClass != null && targetClass != Object.class);
116    }
117
118    /**
119     * Perform the given callback operation on all matching methods of the given class and superclasses (or given
120     * interface and super-interfaces).
121     * <p/>
122     * <b>Important:</b> This method does not take the {@link java.lang.reflect.Method#isBridge() bridge methods} into
123     * account.
124     *
125     * @param clazz class to start looking at
126     * @param mc    the callback to invoke for each method
127     */
128    public static void doWithMethods(Class<?> clazz, MethodCallback mc) throws IllegalArgumentException {
129        // Keep backing up the inheritance hierarchy.
130        Method[] methods = clazz.getDeclaredMethods();
131        for (Method method : methods) {
132            if (method.isBridge()) {
133                // skip the bridge methods which in Java 8 leads to problems with inheritance
134                // see https://bugs.openjdk.java.net/browse/JDK-6695379
135                continue;
136            }
137            try {
138                mc.doWith(method);
139            } catch (IllegalAccessException ex) {
140                throw new IllegalStateException("Shouldn't be illegal to access method '" + method.getName() + "': " + ex);
141            }
142        }
143        if (clazz.getSuperclass() != null) {
144            doWithMethods(clazz.getSuperclass(), mc);
145        } else if (clazz.isInterface()) {
146            for (Class<?> superIfc : clazz.getInterfaces()) {
147                doWithMethods(superIfc, mc);
148            }
149        }
150    }
151
152    /**
153     * Attempt to find a {@link Method} on the supplied class with the supplied name and parameter types. Searches all
154     * superclasses up to {@code Object}.
155     * <p>
156     * Returns {@code null} if no {@link Method} can be found.
157     *
158     * @param  clazz      the class to introspect
159     * @param  name       the name of the method
160     * @param  paramTypes the parameter types of the method (may be {@code null} to indicate any signature)
161     * @return            the Method object, or {@code null} if none found
162     */
163    public static Method findMethod(Class<?> clazz, String name, Class<?>... paramTypes) {
164        ObjectHelper.notNull(clazz, "Class must not be null");
165        ObjectHelper.notNull(name, "Method name must not be null");
166        Class<?> searchType = clazz;
167        while (searchType != null) {
168            Method[] methods = searchType.isInterface() ? searchType.getMethods() : searchType.getDeclaredMethods();
169            for (Method method : methods) {
170                if (name.equals(method.getName())
171                        && (paramTypes == null || Arrays.equals(paramTypes, method.getParameterTypes()))) {
172                    return method;
173                }
174            }
175            searchType = searchType.getSuperclass();
176        }
177        return null;
178    }
179
180    public static void setField(Field f, Object instance, Object value) {
181        try {
182            if (!Modifier.isPublic(f.getModifiers()) && !f.canAccess(instance)) {
183                f.setAccessible(true);
184            }
185            // must use fine-grained for the correct type when setting a field value via reflection
186            Class<?> type = f.getType();
187            if (boolean.class == type) {
188                boolean val;
189                if (value instanceof Boolean) {
190                    val = (boolean) value;
191                } else {
192                    val = Boolean.parseBoolean(value.toString());
193                }
194                f.setBoolean(instance, val);
195            } else if (byte.class == type) {
196                byte val;
197                if (value instanceof Byte) {
198                    val = (byte) value;
199                } else {
200                    val = Byte.parseByte(value.toString());
201                }
202                f.setByte(instance, val);
203            } else if (int.class == type) {
204                int val;
205                if (value instanceof Integer) {
206                    val = (int) value;
207                } else {
208                    val = Integer.parseInt(value.toString());
209                }
210                f.setInt(instance, val);
211            } else if (long.class == type) {
212                long val;
213                if (value instanceof Long) {
214                    val = (long) value;
215                } else {
216                    val = Long.parseLong(value.toString());
217                }
218                f.setLong(instance, val);
219            } else if (float.class == type) {
220                float val;
221                if (value instanceof Float) {
222                    val = (float) value;
223                } else {
224                    val = Float.parseFloat(value.toString());
225                }
226                f.setFloat(instance, val);
227            } else if (double.class == type) {
228                double val;
229                if (value instanceof Double) {
230                    val = (double) value;
231                } else {
232                    val = Double.parseDouble(value.toString());
233                }
234                f.setDouble(instance, val);
235            } else {
236                f.set(instance, value);
237            }
238        } catch (Exception ex) {
239            throw new UnsupportedOperationException("Cannot inject value of class: " + value.getClass() + " into: " + f);
240        }
241    }
242
243    public static Object getField(Field f, Object instance) {
244        try {
245            if (!Modifier.isPublic(f.getModifiers()) && !f.canAccess(instance)) {
246                f.setAccessible(true);
247            }
248            return f.get(instance);
249        } catch (Exception ex) {
250            // ignore
251        }
252        return null;
253    }
254
255}