blob: e09b033384caa0663a2135e52cf8bbfb8182e6cf [file] [log] [blame]
/*
* 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() );
}
}
}