| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.felix.scr.impl.inject.field; |
| |
| import java.lang.reflect.Field; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Modifier; |
| import java.security.AccessController; |
| import java.security.PrivilegedAction; |
| |
| import org.apache.felix.scr.impl.inject.internal.ClassUtils; |
| import org.apache.felix.scr.impl.logger.ComponentLogger; |
| import org.osgi.service.log.LogService; |
| |
| /** |
| * Utility methods for handling field injection. |
| */ |
| public class FieldUtils { |
| |
| /** |
| * Return type for {@link FieldUtils#searchField(Class, String, ComponentLogger)} |
| */ |
| public static final class FieldSearchResult |
| { |
| public final Field field; |
| public final boolean usable; |
| |
| public FieldSearchResult(final Field f, final boolean usable) |
| { |
| this.field = f; |
| this.usable = usable; |
| } |
| } |
| |
| /** |
| * Searches the field named {@code fieldName} in the given |
| * {@code targetClass}. If the target class has no acceptable field |
| * the class hierarchy is traversed until a field is found or the root |
| * of the class hierarchy is reached without finding a field. |
| * <p> |
| * If an unexpected error occurs while searching, {@code null} is |
| * returned. In all other cases a {@code FieldSearchResult} is |
| * returned. If no field is found, {@code FieldSearchResult#field} |
| * is set to {@code null}. If the field is found, but not usable |
| * (e.g. due to visibility restrictions), {@code FieldSearchResult#usable} |
| * is set to {@code false}. |
| * |
| * @param targetClass The class of the component |
| * @param fieldName The name of the field |
| * @param logger A logger to log errors / problems |
| * @return A field search result or {@code null} if an unexpected |
| * error occurred. |
| */ |
| public static FieldSearchResult searchField( final Class<?> componentClass, |
| final String fieldName, |
| final ComponentLogger logger ) |
| { |
| final ClassLoader targetClasslLoader = componentClass.getClassLoader(); |
| final String targetPackage = ClassUtils.getPackageName( componentClass ); |
| Class<?> theClass = componentClass; |
| boolean acceptPrivate = true; |
| boolean acceptPackage = true; |
| while (true) |
| { |
| |
| logger.log( LogService.LOG_DEBUG, |
| "Locating field {0} in class {1}", null, fieldName, theClass.getName() ); |
| |
| try |
| { |
| final FieldSearchResult result = getField( componentClass, theClass, fieldName, acceptPrivate, acceptPackage, logger ); |
| if ( result != null ) |
| { |
| return result; |
| } |
| } |
| catch ( final InvocationTargetException ex ) |
| { |
| logger.log( LogService.LOG_ERROR, "Field {0} cannot be found in component class {1}. The field will be ignored.", |
| ex.getTargetException(), |
| fieldName, componentClass.getName() ); |
| return null; |
| } |
| |
| // if we get here, we have no field, so check the super class |
| theClass = theClass.getSuperclass(); |
| if ( theClass == null ) |
| { |
| break; |
| } |
| |
| // super class field check ignores private fields and accepts |
| // package fields only if in the same package and package |
| // fields are (still) allowed |
| acceptPackage &= targetClasslLoader == theClass.getClassLoader() |
| && targetPackage.equals( ClassUtils.getPackageName( theClass ) ); |
| |
| // private fields will not be accepted any more in super classes |
| acceptPrivate = false; |
| } |
| |
| // nothing found |
| logger.log( LogService.LOG_ERROR, "Field {0} cannot be found in component class {1}. The field will be ignored.", null, |
| fieldName, componentClass.getName() ); |
| return new FieldSearchResult(null, false); |
| } |
| |
| /** |
| * Finds the field named {@code fieldName} field in the given |
| * {@code targetClass}. If the target class has no acceptable field |
| * the class hierarchy is traversed until a field is found or the root |
| * of the class hierarchy is reached without finding a field. |
| * |
| * @param componentClass The class of the component (for logging) |
| * @param targetClass The class in which to look for the method |
| * @param fieldName The name of the field |
| * @param acceptPrivate {@code true} if private fields should be |
| * considered. |
| * @param acceptPackage {@code true} if package private fields should |
| * be considered. |
| * @param logger For error logging |
| * @return If the field is found a {@code FieldSearchResult} is returned. |
| * If the field is not found, {@code null} is returned. |
| * @throws InvocationTargetException If an unexpected Throwable is caught |
| * trying to find the requested field. |
| */ |
| private static FieldSearchResult getField( final Class<?> componentClass, |
| final Class<?> targetClass, |
| final String fieldName, |
| final boolean acceptPrivate, |
| final boolean acceptPackage, |
| final ComponentLogger logger ) |
| throws InvocationTargetException |
| { |
| try |
| { |
| // find the declared field in this class |
| final Field field = targetClass.getDeclaredField( fieldName ); |
| |
| // accept public and protected fields only and ensure accessibility |
| return accept( componentClass, field, acceptPrivate, acceptPackage, logger ); |
| } |
| catch ( NoSuchFieldException nsfe ) |
| { |
| // thrown if no field is declared with the given name and |
| // parameters |
| if ( logger.isLogEnabled( LogService.LOG_DEBUG ) ) |
| { |
| logger.log( LogService.LOG_DEBUG, "Declared Field {0}.{1} not found", null, targetClass.getName(), fieldName ); |
| } |
| } |
| catch ( Throwable throwable ) |
| { |
| // unexpected problem accessing the field, don't let everything |
| // blow up in this situation, just throw a declared exception |
| throw new InvocationTargetException( throwable, "Unexpected problem trying to get field " + fieldName ); |
| } |
| |
| // caught and ignored exception, assume no field and continue search |
| return null; |
| } |
| |
| /** |
| * This method checks whether the found field is acceptable (= usable) |
| * for the component instance. |
| * It returns a {@code FieldSearchResult} with the usable flag set to |
| * {@code true} if the field is not static and |
| * <ul> |
| * <li>Is public or protected</li> |
| * <li>Is private and {@code acceptPrivate} is {@code true}</li> |
| * <li>Is package private and {@code acceptPackage} is {@code true}</li> |
| * </ul> |
| * <p> |
| * If the field is usable, this method makes the field accessible. |
| * <p> |
| * If the field is not usable, a {@code FieldSearchResult} with the usable |
| * flag set to {@code false} is returned and an error is logged with |
| * the provided logger. |
| * |
| * @param componentClass The class of the component,. |
| * @param field The field to check |
| * @param acceptPrivate Whether a private field is acceptable |
| * @param acceptPackage Whether a package private field is acceptable |
| * @param logger The logger for error logging |
| * @return A field search result, this is never {@code null} |
| */ |
| private static FieldSearchResult accept( final Class<?> componentClass, |
| final Field field, |
| final boolean acceptPrivate, |
| final boolean acceptPackage, |
| final ComponentLogger logger) |
| { |
| // check modifiers now |
| final int mod = field.getModifiers(); |
| |
| // static fields |
| if ( Modifier.isStatic( mod ) ) |
| { |
| logger.log( LogService.LOG_ERROR, "Field {0} must not be static", null, toString(componentClass, field) ); |
| return new FieldSearchResult(field, false); |
| } |
| |
| // accept public and protected fields |
| if ( Modifier.isPublic( mod ) || Modifier.isProtected( mod ) ) |
| { |
| setAccessible( field ); |
| return new FieldSearchResult(field, true); |
| } |
| |
| // accept private if accepted |
| if ( Modifier.isPrivate( mod ) ) |
| { |
| if ( acceptPrivate ) |
| { |
| setAccessible( field ); |
| return new FieldSearchResult(field, true); |
| } |
| |
| } else { |
| |
| // accept default (package) |
| if ( acceptPackage ) |
| { |
| setAccessible( field ); |
| return new FieldSearchResult(field, true); |
| } |
| } |
| |
| // else don't accept |
| // the method would fit the requirements but is not acceptable |
| logger.log( LogService.LOG_ERROR, |
| "findField: Suitable but non-accessible field {0}", null, toString(componentClass, field)); |
| return new FieldSearchResult(field, false); |
| } |
| |
| /** |
| * Return a string representation of the field |
| * @param componentClass The component class |
| * @param field The field |
| * @return A string representation of the field |
| */ |
| public static String toString(final Class<?> componentClass, |
| final Field field) |
| { |
| if ( componentClass.getName().equals(field.getDeclaringClass().getName()) ) |
| { |
| return field.getName() + " in component class " + componentClass.getName(); |
| } |
| return field.getName() + " in class " + field.getDeclaringClass().getName() + ", subclass of component class " + componentClass.getName(); |
| } |
| |
| /** |
| * Make the field accessible |
| * @param field The field |
| */ |
| private static void setAccessible(final Field field) |
| { |
| AccessController.doPrivileged( new PrivilegedAction<Object>() |
| { |
| @Override |
| public Object run() |
| { |
| field.setAccessible( true ); |
| return null; |
| } |
| } ); |
| } |
| |
| /** |
| * Set the field, type etc. |
| * @param f The field |
| * @param component The component instance |
| * @param value The value to set |
| * @param logger The logger |
| */ |
| public static void setField( final Field f, |
| final Object component, |
| final Object value, |
| final ComponentLogger logger ) |
| { |
| try |
| { |
| f.set(component, value); |
| } |
| catch ( final IllegalArgumentException iae ) |
| { |
| logger.log( LogService.LOG_ERROR, "Field {0} can't be set", iae, f.getName() ); |
| } |
| catch ( final IllegalAccessException iae ) |
| { |
| logger.log( LogService.LOG_ERROR, "Field {0} can't be set", iae, f.getName() ); |
| } |
| } |
| } |