FELIX-925 Allow activate and deactivate methods to have different signatures
* Refactored reflection use for finding and invoking methods
* Allow for private and package private methods
* Unit tests
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@785125 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/AbstractComponentManager.java b/scr/src/main/java/org/apache/felix/scr/impl/AbstractComponentManager.java
index bd6dc97..759014d 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/AbstractComponentManager.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/AbstractComponentManager.java
@@ -18,9 +18,6 @@
*/
package org.apache.felix.scr.impl;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Enumeration;
@@ -961,66 +958,4 @@
m_state = newState;
}
-
- /**
- * Finds the named public or protected method in the given class or any
- * super class. If such a method is found, its accessibility is enfored by
- * calling the <code>Method.setAccessible</code> method if required and
- * the method is returned. Enforcing accessibility is required to support
- * invocation of protected methods.
- *
- * @param clazz The <code>Class</code> which provides the method.
- * @param name The name of the method.
- * @param parameterTypes The parameters to the method. Passing
- * <code>null</code> is equivalent to using an empty array.
- * @param only Whether to only look at the declared methods of the given
- * class or also inspect the super classes.
- *
- * @return The named method with enforced accessibility
- *
- * @throws NoSuchMethodException If no public or protected method with
- * the given name can be found in the class or any of its super classes.
- * @throws InvocationTargetException If an unexpected Throwable is caught
- * trying to access the desired method.
- */
- static Method getMethod( Class clazz, String name, Class[] parameterTypes, boolean only )
- throws NoSuchMethodException, InvocationTargetException
- {
- for ( ; clazz != null; clazz = clazz.getSuperclass() )
- {
- try
- {
- // find the declared method in this class
- Method method = clazz.getDeclaredMethod( name, parameterTypes );
-
- // accept public and protected methods only and ensure accessibility
- if ( Modifier.isPublic( method.getModifiers() ) || Modifier.isProtected( method.getModifiers()) )
- {
- method.setAccessible( true );
- return method;
- }
-
- // if only the clazz is to be scanned terminate here
- if ( only )
- {
- break;
- }
- }
- catch ( NoSuchMethodException nsme )
- {
- // ignore for now
- }
- catch ( Throwable throwable )
- {
- // unexpected problem accessing the method, don't let everything
- // blow up in this situation, just throw a declared exception
- throw new InvocationTargetException( throwable,
- "Unexpected problem trying to get method " + name );
- }
- }
-
- // walked up the complete super class hierarchy and still not found
- // anything, sigh ...
- throw new NoSuchMethodException( name );
- }
}
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/DependencyManager.java b/scr/src/main/java/org/apache/felix/scr/impl/DependencyManager.java
index 90e90f3..2107196 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/DependencyManager.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/DependencyManager.java
@@ -72,7 +72,7 @@
// the object on which the bind/undind methods are to be called
private transient Object m_componentInstance;
-
+
// the bind method
private Method m_bind;
@@ -90,7 +90,7 @@
// the target service filter
private Filter m_targetFilter;
-
+
/**
* Constructor that receives several parameters.
@@ -129,7 +129,7 @@
case ServiceEvent.REGISTERED:
m_componentManager.log( LogService.LOG_DEBUG, "Dependency Manager: Adding " + serviceString,
m_componentManager.getComponentMetadata(), null );
-
+
// consider the service if the filter matches
if ( targetFilterMatch( ref ) )
{
@@ -147,10 +147,10 @@
case ServiceEvent.MODIFIED:
m_componentManager.log( LogService.LOG_DEBUG, "Dependency Manager: Updating " + serviceString,
m_componentManager.getComponentMetadata(), null );
-
+
// remove the service first
serviceRemoved( ref );
-
+
// recalculate the number of services matching the filter
// because we don't know whether this service previously matched
// or not
@@ -164,7 +164,7 @@
case ServiceEvent.UNREGISTERING:
m_componentManager.log( LogService.LOG_DEBUG, "Dependency Manager: Removing " + serviceString,
m_componentManager.getComponentMetadata(), null );
-
+
// manage the service counter if the filter matchs
if ( targetFilterMatch( ref ) )
{
@@ -181,7 +181,7 @@
// remove the service ignoring the filter match because if the
// service is bound, it has to be removed no matter what
serviceRemoved( ref );
-
+
break;
}
}
@@ -227,7 +227,7 @@
m_componentManager.log( LogService.LOG_DEBUG, "Dependency Manager: Service "
+ m_dependencyMetadata.getName() + " registered, reactivate component", m_componentManager
.getComponentMetadata(), null );
-
+
m_componentManager.reactivate();
}
else
@@ -246,7 +246,7 @@
}
}
}
-
+
// otherwise bind if we have a bind method and the service needs
// be bound
else if ( m_dependencyMetadata.getBind() != null )
@@ -267,15 +267,15 @@
{
// bind the service, getting it if required
invokeBindMethod( reference );
-
+
// unbind the old service reference
unbind( boundRefs );
}
}
}
}
-
- else
+
+ else
{
m_componentManager.log( LogService.LOG_DEBUG,
"Dependency Manager: Ignoring service addition, wrong state "
@@ -283,8 +283,8 @@
m_componentManager.getComponentMetadata(), null );
}
}
-
-
+
+
/**
* Called by the {@link #serviceChanged(ServiceEvent)} method if an existing
* service is unregistered from the system or if a registered service has
@@ -322,11 +322,11 @@
"Dependency Manager: Deactivating component due to mandatory dependency on "
+ m_dependencyMetadata.getName() + "/" + m_dependencyMetadata.getInterface()
+ " not satisfied", m_componentManager.getComponentMetadata(), null );
-
+
// deactivate the component now
m_componentManager.deactivateInternal();
}
-
+
// if the dependency is static, we have to reactivate the component
// to "remove" the dependency
else if ( m_dependencyMetadata.isStatic() )
@@ -345,11 +345,11 @@
m_componentManager.getComponentMetadata(), ex );
}
}
-
+
// dynamic dependency, multiple or single but this service is the bound one
else
{
-
+
// try to bind a replacement service first if this is a unary
// cardinality reference and a replacement is available.
if ( !m_dependencyMetadata.isMultiple() )
@@ -363,24 +363,24 @@
+ m_dependencyMetadata.getName() + "/" + m_dependencyMetadata.getInterface()
+ " not satisfied", m_componentManager.getComponentMetadata(), null );
m_componentManager.deactivateInternal();
-
+
// abort here we do not need to do more
return;
}
}
-
+
// call the unbind method if one is defined
if ( m_dependencyMetadata.getUnbind() != null )
{
invokeUnbindMethod( reference );
}
-
+
// make sure the service is returned
ungetService( reference );
}
}
-
- else
+
+ else
{
m_componentManager.log( LogService.LOG_DEBUG,
"Dependency Manager: Ignoring service removal, wrong state "
@@ -388,8 +388,8 @@
m_componentManager.getComponentMetadata(), null );
}
}
-
-
+
+
private boolean handleServiceEvent()
{
return ( m_componentManager.getState() & STATE_MASK ) != 0;
@@ -460,7 +460,7 @@
m_componentManager.log( LogService.LOG_DEBUG, "Registered for service events, currently " + m_size
+ " service(s) match the filter", m_componentManager.getComponentMetadata(), null );
}
-
+
/**
* Disposes off this dependency manager by removing as a service listener
* and ungetting all services, which are still kept in the list of our
@@ -635,12 +635,12 @@
* ranking as specified by the service.ranking property. If both services
* have the same service ranking, then the service with the lowest service
* ID as specified by the service.id property is chosen.
- *
+ *
* @param newReference The ServiceReference representing the newly added
* Service
* @param oldReference The ServiceReference representing the service which
* is already bound to the component
- *
+ *
* @return <code>true</code> if <code>newReference</code> has higher ranking
*/
private boolean isHigher( ServiceReference newReference, ServiceReference oldReference )
@@ -680,7 +680,7 @@
* if the property exists and is of type <code>java.lang.Integer</code>. If
* the property does not exist or is of another type, zero is returned as
* the default value for service ranking.
- *
+ *
* @param serviceReference The Service reference whose ranking is to be
* returned.
*/
@@ -697,10 +697,10 @@
/**
* Returns the value of the <code>service.id</code> service property.
- *
+ *
* @param serviceReference The Service reference whose service id is to be
* returned.
- *
+ *
* @throws ClassCastException if the <code>service.id</code> property exists
* but is not a <code>java.lang.Long</code> value. This is not
* expected since the framework should guarantee this property and
@@ -990,8 +990,8 @@
try
{
// Case 1 - ServiceReference parameter
- return AbstractComponentManager.getMethod( targetClass, methodname, new Class[]
- { SERVICE_REFERENCE_CLASS }, true );
+ return ReflectionHelper.getMethod( targetClass, methodname, new Class[]
+ { SERVICE_REFERENCE_CLASS }, false, false );
}
catch ( NoSuchMethodException ex )
{
@@ -1009,8 +1009,8 @@
}
parameterClass = loader.loadClass( parameterClassName );
- return AbstractComponentManager.getMethod( targetClass, methodname, new Class[]
- { parameterClass }, true );
+ return ReflectionHelper.getMethod( targetClass, methodname, new Class[]
+ { parameterClass }, false, false );
}
catch ( NoSuchMethodException ex2 )
{
@@ -1062,6 +1062,10 @@
// if we can't load the class, perhaps the method is declared in a super class
// so we try this class next
}
+
+
+ // TODO: Case 4: same as case 2, but + Map param
+ // TODO: Case 5: same as case 3, but + Map param
}
// if we get here, we have no method, so check the super class
@@ -1289,7 +1293,7 @@
}
}
-
+
//------------- Service target filter support -----------------------------
/**
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/ImmediateComponentManager.java b/scr/src/main/java/org/apache/felix/scr/impl/ImmediateComponentManager.java
index 23a9166..f56ea18 100644
--- a/scr/src/main/java/org/apache/felix/scr/impl/ImmediateComponentManager.java
+++ b/scr/src/main/java/org/apache/felix/scr/impl/ImmediateComponentManager.java
@@ -24,7 +24,9 @@
import java.util.Dictionary;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
+import org.osgi.framework.BundleContext;
import org.osgi.service.cm.Configuration;
import org.osgi.service.component.ComponentConstants;
import org.osgi.service.component.ComponentContext;
@@ -37,12 +39,51 @@
*/
class ImmediateComponentManager extends AbstractComponentManager
{
+ private static final Class COMPONENT_CONTEXT_CLASS = ComponentContext.class;
+
+ private static final Class BUNDLE_CONTEXT_CLASS = BundleContext.class;
+
+ private static final Class MAP_CLASS = Map.class;
+
+ // this is an internal field made available only to the unit tests
+ static final Class[][] ACTIVATE_PARAMETER_LIST = {
+ { COMPONENT_CONTEXT_CLASS },
+ { BUNDLE_CONTEXT_CLASS },
+ { MAP_CLASS },
+
+ { COMPONENT_CONTEXT_CLASS, BUNDLE_CONTEXT_CLASS },
+ { COMPONENT_CONTEXT_CLASS, MAP_CLASS },
+
+ { BUNDLE_CONTEXT_CLASS, COMPONENT_CONTEXT_CLASS },
+ { BUNDLE_CONTEXT_CLASS, MAP_CLASS },
+
+ { MAP_CLASS, COMPONENT_CONTEXT_CLASS },
+ { MAP_CLASS, BUNDLE_CONTEXT_CLASS },
+
+ { COMPONENT_CONTEXT_CLASS, BUNDLE_CONTEXT_CLASS, MAP_CLASS },
+ { COMPONENT_CONTEXT_CLASS, MAP_CLASS, BUNDLE_CONTEXT_CLASS },
+
+ { BUNDLE_CONTEXT_CLASS, COMPONENT_CONTEXT_CLASS, MAP_CLASS },
+ { BUNDLE_CONTEXT_CLASS, MAP_CLASS, COMPONENT_CONTEXT_CLASS },
+
+ { MAP_CLASS, COMPONENT_CONTEXT_CLASS, BUNDLE_CONTEXT_CLASS },
+ { MAP_CLASS, BUNDLE_CONTEXT_CLASS, COMPONENT_CONTEXT_CLASS },
+
+ {}
+ };
+
// The object that implements the service and that is bound to other services
private Object m_implementationObject;
// The context that will be passed to the implementationObject
private ComponentContext m_componentContext;
+ // the activate method
+ private Method activateMethod = ReflectionHelper.SENTINEL;
+
+ // the deactivate method
+ private Method deactivateMethod = ReflectionHelper.SENTINEL;
+
// optional properties provided in the ComponentFactory.newInstance method
private Dictionary m_factoryProperties;
@@ -186,34 +227,17 @@
}
}
+ // get the method
+ if ( activateMethod == ReflectionHelper.SENTINEL )
+ {
+ activateMethod = getMethod( implementationObject, getComponentMetadata().getActivate() );
+ }
+
// 4. Call the activate method, if present
- // Search for the activate method
- final String activateMethodName = getComponentMetadata().getActivate();
- try
- {
- Method activateMethod = getMethod( implementationObject.getClass(), activateMethodName, new Class[]
- { ComponentContext.class }, false );
- activateMethod.invoke( implementationObject, new Object[]
- { componentContext } );
- }
- catch ( NoSuchMethodException ex )
- {
- // We can safely ignore this one
- log( LogService.LOG_DEBUG, activateMethodName + " method is not implemented", getComponentMetadata(), null );
- }
- catch ( IllegalAccessException ex )
- {
- // Ignored, but should it be logged?
- log( LogService.LOG_DEBUG, activateMethodName + " method cannot be called", getComponentMetadata(), null );
- }
- catch ( InvocationTargetException ex )
+ if ( activateMethod != null && !invokeMethod( activateMethod, implementationObject, componentContext ) )
{
// 112.5.8 If the activate method throws an exception, SCR must log an error message
// containing the exception with the Log Service and activation fails
- log( LogService.LOG_ERROR, "The " + activateMethodName + " method has thrown an exception",
- getComponentMetadata(), ex.getCause() );
-
- // make sure, we keep no bindings
it = getDependencyManagers();
while ( it.hasNext() )
{
@@ -221,7 +245,7 @@
dm.close();
}
- return null;
+ implementationObject = null;
}
return implementationObject;
@@ -231,34 +255,17 @@
protected void disposeImplementationObject( Object implementationObject, ComponentContext componentContext )
{
+ // get the method
+ if ( deactivateMethod == ReflectionHelper.SENTINEL )
+ {
+ deactivateMethod = getMethod( implementationObject, getComponentMetadata().getDeactivate() );
+ }
+
// 1. Call the deactivate method, if present
- // Search for the activate method
- final String deactivateMethodName = getComponentMetadata().getDeactivate();
- try
- {
- Method deactivateMethod = getMethod( implementationObject.getClass(), deactivateMethodName, new Class[]
- { ComponentContext.class }, false );
- deactivateMethod.invoke( implementationObject, new Object[]
- { componentContext } );
- }
- catch ( NoSuchMethodException ex )
- {
- // We can safely ignore this one
- log( LogService.LOG_DEBUG, deactivateMethodName + " method is not implemented", getComponentMetadata(),
- null );
- }
- catch ( IllegalAccessException ex )
- {
- // Ignored, but should it be logged?
- log( LogService.LOG_DEBUG, deactivateMethodName + " method cannot be called", getComponentMetadata(), null );
- }
- catch ( InvocationTargetException ex )
- {
- // 112.5.12 If the deactivate method throws an exception, SCR must log an error message
- // containing the exception with the Log Service and continue
- log( LogService.LOG_ERROR, "The " + deactivateMethodName + " method has thrown an exception",
- getComponentMetadata(), ex.getCause() );
- }
+ // don't care for the result, the error (acccording to 112.5.12 If the deactivate
+ // method throws an exception, SCR must log an error message containing the
+ // exception with the Log Service and continue) has already been logged
+ invokeMethod( deactivateMethod, implementationObject, componentContext );
// 2. Unbind any bound services
Iterator it = getDependencyManagers();
@@ -382,4 +389,104 @@
reactivate();
}
}
+
+
+ /**
+ * Find the method with the given name in the class hierarchy of the
+ * implementation object's class. This method looks for methods which have
+ * one of the parameter lists of the {@link #ACTIVATE_PARAMETER_LIST} array.
+ *
+ * @param implementationObject The object whose class (and its super classes)
+ * may provide the method
+ * @param methodName Name of the method to look for
+ * @return The named method or <code>null</code> if no such method is available.
+ */
+ private Method getMethod( final Object implementationObject, final String methodName )
+ {
+ try
+ {
+ return ReflectionHelper.getMethod( implementationObject.getClass(), methodName, ACTIVATE_PARAMETER_LIST );
+ }
+ catch ( InvocationTargetException ite )
+ {
+ // We can safely ignore this one
+ log( LogService.LOG_WARNING, methodName + " cannot be found", getComponentMetadata(), ite
+ .getTargetException() );
+ }
+ catch ( NoSuchMethodException ex )
+ {
+ // We can safely ignore this one
+ log( LogService.LOG_DEBUG, methodName + " method is not implemented", getComponentMetadata(), null );
+ }
+
+ return null;
+ }
+
+
+ /**
+ * Invokes the given method on the <code>implementationObject</code> using
+ * the <code>componentContext</code> as the base to create method argument
+ * list.
+ *
+ * @param method The method to call. This method must already have been
+ * made accessible by calling
+ * <code>Method.setAccessible(boolean)</code>.
+ * @param implementationObject The object on which to call the method.
+ * @param componentContext The <code>ComponentContext</code> used to
+ * build the argument list
+ *
+ * @return <code>true</code> if the method should be considered invoked
+ * successfully. <code>false</code> is returned if the method threw
+ * an exception.
+ *
+ * @throws NullPointerException if any of the parameters is <code>null</code>.
+ */
+ private boolean invokeMethod( final Method method, final Object implementationObject,
+ final ComponentContext componentContext )
+ {
+ final String methodName = method.getName();
+ try
+ {
+ // build argument list
+ Class[] paramTypes = method.getParameterTypes();
+ Object[] param = new Object[paramTypes.length];
+ for ( int i = 0; i < param.length; i++ )
+ {
+ if ( paramTypes[i] == COMPONENT_CONTEXT_CLASS )
+ {
+ param[i] = componentContext;
+ }
+ else if ( paramTypes[i] == BUNDLE_CONTEXT_CLASS )
+ {
+ param[i] = componentContext.getBundleContext();
+ }
+ else if ( paramTypes[i] == MAP_CLASS )
+ {
+ // note: getProperties() returns a Hashtable which is a Map
+ param[i] = componentContext.getProperties();
+ }
+ }
+
+ method.invoke( implementationObject, new Object[]
+ { componentContext } );
+ }
+ catch ( IllegalAccessException ex )
+ {
+ // Ignored, but should it be logged?
+ log( LogService.LOG_DEBUG, methodName + " method cannot be called", getComponentMetadata(), null );
+ }
+ catch ( InvocationTargetException ex )
+ {
+ // 112.5.8 If the activate method throws an exception, SCR must log an error message
+ // containing the exception with the Log Service and activation fails
+ log( LogService.LOG_ERROR, "The " + methodName + " method has thrown an exception", getComponentMetadata(),
+ ex.getCause() );
+
+ // method threw, so it was a failure
+ return false;
+ }
+
+ // assume success (also if the method is not available or accessible)
+ return true;
+ }
}
diff --git a/scr/src/main/java/org/apache/felix/scr/impl/ReflectionHelper.java b/scr/src/main/java/org/apache/felix/scr/impl/ReflectionHelper.java
new file mode 100644
index 0000000..2c33f52
--- /dev/null
+++ b/scr/src/main/java/org/apache/felix/scr/impl/ReflectionHelper.java
@@ -0,0 +1,244 @@
+/*
+ * 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;
+
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+
+/**
+ * The <code>ReflectionHelper</code> class provides utility methods to find out
+ * about binding and activation methods in components.
+ */
+final class ReflectionHelper
+{
+
+ static final Method SENTINEL;
+
+ static
+ {
+ Method tmpSentinel = null;
+ try
+ {
+ tmpSentinel = ReflectionHelper.class.getDeclaredMethod( "sentinel", null );
+ }
+ catch ( Throwable t )
+ {
+ // don't care for the reason
+ }
+
+ SENTINEL = tmpSentinel;
+ }
+
+
+ // sentinel method used to assign to the SENTINEL field
+ private static final void sentinel()
+ {
+ }
+
+
+ /**
+ * Finds the named public or protected method in the given class or any
+ * super class. If such a method is found, its accessibility is enfored by
+ * calling the <code>Method.setAccessible</code> method if required and
+ * the method is returned. Enforcing accessibility is required to support
+ * invocation of protected methods.
+ *
+ * @param objectClass The <code>Class</code> at which to start looking for
+ * a matching method
+ * @param name The name of the method.
+ * @param parameterTypes The list of suitable parmameters. Each class is
+ * is asked for a declared method of the given name with parameters
+ * from this list.
+ * @param only Whether to only look at the declared methods of the given
+ * class or also inspect the super classes.
+ *
+ * @return The named method with enforced accessibility
+ *
+ * @throws NoSuchMethodException If no public or protected method with
+ * the given name can be found in the class or any of its super classes.
+ * @throws InvocationTargetException If an unexpected Throwable is caught
+ * trying to access the desired method.
+ */
+ static Method getMethod( final Class objectClass, final String name, final Class[][] parameterTypesList )
+ throws NoSuchMethodException, InvocationTargetException
+ {
+ // whether we accept package private methods
+ boolean acceptPackage = true;
+ String packageName = getPackageName( objectClass );
+
+ for ( Class clazz = objectClass; clazz != null; clazz = clazz.getSuperclass() )
+ {
+ // turns false on first package not equal to the package of objectClass
+ acceptPackage &= packageName.equals( getPackageName( clazz ) );
+
+ for ( int i = 0; i < parameterTypesList.length; i++ )
+ {
+ Class[] parameterTypes = parameterTypesList[i];
+
+ try
+ {
+ // find the declared method in this class
+ return getMethod( clazz, name, parameterTypes, clazz == objectClass, acceptPackage );
+ }
+ catch ( NoSuchMethodException nsme )
+ {
+ // ignore for now
+ }
+ catch ( Throwable throwable )
+ {
+ // unexpected problem accessing the method, don't let everything
+ // blow up in this situation, just throw a declared exception
+ throw new InvocationTargetException( throwable, "Unexpected problem trying to get method " + name );
+ }
+ }
+ }
+
+ // walked up the complete super class hierarchy and still not found
+ // anything, sigh ...
+ throw new NoSuchMethodException( name );
+ }
+
+
+ /**
+ * Finds the named public or protected method in the given class or any
+ * super class. If such a method is found, its accessibility is enfored by
+ * calling the <code>Method.setAccessible</code> method if required and
+ * the method is returned. Enforcing accessibility is required to support
+ * invocation of protected methods.
+ *
+ * @param clazz The <code>Class</code> which provides the method.
+ * @param name The name of the method.
+ * @param parameterTypes The parameters to the method. Passing
+ * <code>null</code> is equivalent to using an empty array.
+ *
+ * @return The named method with enforced accessibility
+ *
+ * @throws NoSuchMethodException If no public or protected method with
+ * the given name can be found in the class or any of its super classes.
+ * @throws InvocationTargetException If an unexpected Throwable is caught
+ * trying to access the desired method.
+ */
+ static Method getMethod( Class clazz, String name, Class[] parameterTypes, boolean acceptPrivate,
+ boolean acceptPackage ) throws NoSuchMethodException, InvocationTargetException
+ {
+ try
+ {
+ // find the declared method in this class
+ Method method = clazz.getDeclaredMethod( name, parameterTypes );
+
+ // accept public and protected methods only and ensure accessibility
+ if ( accept( method, acceptPrivate, acceptPackage ) )
+ {
+ method.setAccessible( true );
+ return method;
+ }
+ }
+ catch ( NoSuchMethodException nsme )
+ {
+ // forward to caller
+ throw nsme;
+ }
+ catch ( Throwable throwable )
+ {
+ // unexpected problem accessing the method, don't let everything
+ // blow up in this situation, just throw a declared exception
+ throw new InvocationTargetException( throwable, "Unexpected problem trying to get method " + name );
+ }
+
+ // walked up the complete super class hierarchy and still not found
+ // anything, sigh ...
+ throw new NoSuchMethodException( name );
+ }
+
+
+ /**
+ * Returns <code>true</code> if the method is acceptable to be returned from the
+ * {@link #getMethod(Class, String, Class[], boolean, boolean)}.
+ * <p>
+ * This method returns <code>true</code> iff:
+ * <ul>
+ * <li>The method has <code>void</code> return type</li>
+ * <li>Is not static</li>
+ * <li>Is public or protected</li>
+ * <li>Is private and <code>acceptPrivate</code> is <code>true</code></li>
+ * <li>Is package private and <code>acceptPackage</code> is <code>true</code></li>
+ * </ul>
+ * <p>
+ * This method is package private for unit testing purposes. It is not
+ * meant to be called from client code.
+ *
+ * @param method The method to check
+ * @param acceptPrivate Whether a private method is acceptable
+ * @param acceptPackage Whether a package private method is acceptable
+ * @return
+ */
+ static boolean accept( Method method, boolean acceptPrivate, boolean acceptPackage )
+ {
+ // method must be void
+ if ( Void.TYPE != method.getReturnType() )
+ {
+ return false;
+ }
+
+ // check modifiers now
+ int mod = method.getModifiers();
+
+ // no static method
+ if ( Modifier.isStatic( mod ) )
+ {
+ return false;
+ }
+
+ // accept public and protected methods
+ if ( Modifier.isPublic( mod ) || Modifier.isProtected( mod ) )
+ {
+ return true;
+ }
+
+ // accept private if accepted
+ if ( Modifier.isPrivate( mod ) )
+ {
+ return acceptPrivate;
+ }
+
+ // accept default (package)
+ if ( acceptPackage )
+ {
+ return true;
+ }
+
+ // else don't accept
+ return false;
+ }
+
+
+ /**
+ * Returns the name of the package to which the class belongs or an
+ * empty string if the class is in the default package.
+ */
+ static String getPackageName( Class clazz )
+ {
+ String name = clazz.getName();
+ int dot = name.lastIndexOf( '.' );
+ return ( dot > 0 ) ? name.substring( 0, dot ) : "";
+ }
+}
diff --git a/scr/src/test/java/DefaultPackageClass.java b/scr/src/test/java/DefaultPackageClass.java
new file mode 100644
index 0000000..85738d3
--- /dev/null
+++ b/scr/src/test/java/DefaultPackageClass.java
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+/**
+ * The DefaultPackageClass is just present for the
+ * {@link org.apache.felix.scr.impl.ReflectionHelperTest} to be able to test
+ * the <code>ReflectionHelper.getPackageName</code> method.
+ */
+public class DefaultPackageClass
+{
+
+}
diff --git a/scr/src/test/java/org/apache/felix/scr/impl/ReflectionHelperTest.java b/scr/src/test/java/org/apache/felix/scr/impl/ReflectionHelperTest.java
new file mode 100644
index 0000000..dc5c052
--- /dev/null
+++ b/scr/src/test/java/org/apache/felix/scr/impl/ReflectionHelperTest.java
@@ -0,0 +1,246 @@
+/*
+ * 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;
+
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import junit.framework.TestCase;
+
+import org.apache.felix.scr.impl.instances.AcceptMethod;
+import org.apache.felix.scr.impl.instances.BaseObject;
+import org.apache.felix.scr.impl.instances.Level1Object;
+import org.apache.felix.scr.impl.instances.Level3Object;
+import org.apache.felix.scr.impl.instances.MethodNameException;
+import org.apache.felix.scr.impl.instances2.Level2Object;
+
+
+public class ReflectionHelperTest extends TestCase
+{
+
+ private static final Class ACCEPT_METHOD_CLASS = AcceptMethod.class;
+
+ BaseObject base = new BaseObject();
+
+ Level1Object level1 = new Level1Object();
+
+ Level2Object level2 = new Level2Object();
+
+ Level3Object level3 = new Level3Object();
+
+
+ public void test_sentinel() throws Exception
+ {
+ assertNotNull( "Sentinel is null", ReflectionHelper.SENTINEL );
+ }
+
+
+ public void test_private_no_arg() throws Exception
+ {
+ checkMethod( base, "activate_no_arg" );
+
+ // activate_no_arg is private to BaseObject and must not be
+ // accessible from extensions
+ ensureMethodNotFoundMethod( level1, "activate_no_arg" );
+ ensureMethodNotFoundMethod( level2, "activate_no_arg" );
+ ensureMethodNotFoundMethod( level3, "activate_no_arg" );
+ }
+
+
+ public void test_protected_activate_comp() throws Exception
+ {
+ // activate_comp is protected in BaseObject and must be accessible
+ // in all instances
+ checkMethod( base, "activate_comp" );
+ checkMethod( level1, "activate_comp" );
+ checkMethod( level2, "activate_comp" );
+ checkMethod( level3, "activate_comp" );
+ }
+
+
+ public void test_private_activate_level1_bundle() throws Exception
+ {
+ // activate_level1_bundle is private in Level1Object and must be
+ // accessible in Level1Object only
+ ensureMethodNotFoundMethod( base, "activate_level1_bundle" );
+ checkMethod( level1, "activate_level1_bundle" );
+ ensureMethodNotFoundMethod( level2, "activate_level1_bundle" );
+ ensureMethodNotFoundMethod( level3, "activate_level1_bundle" );
+ }
+
+
+ public void test_protected_activate_level1_map() throws Exception
+ {
+ // activate_level1_map is protected in Level1Object and must be
+ // accessible in Level1Object and extensions but not in BaseObject
+ ensureMethodNotFoundMethod( base, "activate_level1_map" );
+ checkMethod( level1, "activate_level1_map" );
+ checkMethod( level2, "activate_level1_map" );
+ checkMethod( level3, "activate_level1_map" );
+ }
+
+
+ public void test_private_activate_comp_map() throws Exception
+ {
+ // private_activate_comp_map is private in Level2Object and must be
+ // accessible in Level2Object only
+ ensureMethodNotFoundMethod( base, "activate_comp_map" );
+ ensureMethodNotFoundMethod( level1, "activate_comp_map" );
+ checkMethod( level2, "activate_comp_map" );
+ checkMethod( level3, "activate_comp_map" );
+ }
+
+
+ public void test_public_activate_collision() throws Exception
+ {
+ // activate_collision is private in Level2Object and must be
+ // accessible in Level2Object only.
+ // also the method is available taking no arguments and a single
+ // map argument which takes precedence and which we expect
+ ensureMethodNotFoundMethod( base, "activate_collision" );
+ ensureMethodNotFoundMethod( level1, "activate_collision" );
+ checkMethod( level2, "activate_collision" );
+ checkMethod( level3, "activate_collision" );
+ }
+
+
+ public void test_package_activate_comp_bundle() throws Exception
+ {
+ // activate_comp_bundle is package private and thus visible in
+ // base and level1 but not in level 2 (different package) and
+ // level 3 (inheritance through different package)
+
+ checkMethod( base, "activate_comp_bundle" );
+ checkMethod( level1, "activate_comp_bundle" );
+ ensureMethodNotFoundMethod( level2, "activate_comp_bundle" );
+ ensureMethodNotFoundMethod( level3, "activate_comp_bundle" );
+ }
+
+
+ public void test_getPackage() throws Exception
+ {
+ Class dpc = getClass().getClassLoader().loadClass( "DefaultPackageClass" );
+ assertEquals( "", ReflectionHelper.getPackageName( dpc ) );
+
+ assertEquals( "org.apache.felix.scr.impl.instances", ReflectionHelper.getPackageName( base.getClass() ) );
+ }
+
+
+ public void test_accept() throws Exception
+ {
+ // public visible unless returning non-void
+ assertMethod( true, "public_void", false, false );
+ assertMethod( false, "public_string", false, false );
+
+ // protected visible unless returning non-void
+ assertMethod( true, "protected_void", false, false );
+ assertMethod( false, "protected_string", false, false );
+
+ // private not visible
+ assertMethod( false, "private_void", false, false );
+ assertMethod( false, "private_string", false, false );
+
+ // private visible unless returning non-void
+ assertMethod( true, "private_void", true, false );
+ assertMethod( false, "private_string", true, false );
+ assertMethod( true, "private_void", true, true );
+ assertMethod( false, "private_string", true, true );
+
+ // private not visible, accept package is ignored
+ assertMethod( false, "private_void", false, true );
+ assertMethod( false, "private_string", false, true );
+
+ // package not visible
+ assertMethod( false, "package_void", false, false );
+ assertMethod( false, "package_string", false, false );
+
+ // package visible unless returning non-void
+ assertMethod( true, "package_void", false, true );
+ assertMethod( false, "package_string", false, true );
+ assertMethod( true, "package_void", true, true );
+ assertMethod( false, "package_string", true, true );
+
+ // package not visible, accept private is ignored
+ assertMethod( false, "package_void", true, false );
+ assertMethod( false, "package_string", true, false );
+ }
+
+
+ //---------- internal
+
+ /**
+ * Checks whether a method with the given name can be found for the
+ * activate/deactivate method parameter list and whether the method returns
+ * its name when called.
+ *
+ * @param obj
+ * @param methodName
+ */
+ private void checkMethod( Object obj, String methodName ) throws NoSuchMethodException, InvocationTargetException,
+ IllegalAccessException
+ {
+ Method method = ReflectionHelper.getMethod( obj.getClass(), methodName,
+ ImmediateComponentManager.ACTIVATE_PARAMETER_LIST );
+ try
+ {
+ method.invoke( obj, new Object[method.getParameterTypes().length] );
+ fail( "Expected MethodNameException being thrown" );
+ }
+ catch ( InvocationTargetException ite )
+ {
+ Throwable target = ite.getTargetException();
+ assertTrue( "Expected MethodNameException", target instanceof MethodNameException );
+ assertEquals( methodName, target.getMessage() );
+ }
+ }
+
+
+ /**
+ * Ensures no method with the given name accepting any of the
+ * activate/deactive method parameters can be found.
+ *
+ * @param obj
+ * @param methodName
+ * @throws InvocationTargetException
+ * @throws IllegalAccessException
+ */
+ private void ensureMethodNotFoundMethod( Object obj, String methodName ) throws InvocationTargetException,
+ IllegalAccessException
+ {
+ try
+ {
+ checkMethod( obj, methodName );
+ fail( "Expected to not find method " + methodName + " for " + obj.getClass() );
+ }
+ catch ( NoSuchMethodException nsme )
+ {
+ // expected not to find a method
+ }
+ }
+
+
+ private void assertMethod( boolean expected, String methodName, boolean acceptPrivate, boolean acceptPackage )
+ throws NoSuchMethodException
+ {
+ Method method = ACCEPT_METHOD_CLASS.getDeclaredMethod( methodName, null );
+ boolean accepted = ReflectionHelper.accept( method, acceptPrivate, acceptPackage );
+ assertEquals( expected, accepted );
+ }
+}
diff --git a/scr/src/test/java/org/apache/felix/scr/impl/instances/AcceptMethod.java b/scr/src/test/java/org/apache/felix/scr/impl/instances/AcceptMethod.java
new file mode 100644
index 0000000..e31c601
--- /dev/null
+++ b/scr/src/test/java/org/apache/felix/scr/impl/instances/AcceptMethod.java
@@ -0,0 +1,71 @@
+/*
+ * 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.instances;
+
+
+/**
+ * The <code>AcceptMethod</code> class provides methods, which are used to
+ * test the ReflectionHelper.acceptMethod() method.
+ */
+public class AcceptMethod
+{
+
+ public void public_void()
+ {
+ }
+
+
+ public String public_string()
+ {
+ return "";
+ }
+
+
+ protected void protected_void()
+ {
+ }
+
+
+ protected String protected_string()
+ {
+ return "";
+ }
+
+
+ private void private_void()
+ {
+ }
+
+
+ private String private_string()
+ {
+ return "";
+ }
+
+
+ void package_void()
+ {
+ }
+
+
+ String package_string()
+ {
+ return "";
+ }
+}
diff --git a/scr/src/test/java/org/apache/felix/scr/impl/instances/BaseObject.java b/scr/src/test/java/org/apache/felix/scr/impl/instances/BaseObject.java
new file mode 100644
index 0000000..a30c84f
--- /dev/null
+++ b/scr/src/test/java/org/apache/felix/scr/impl/instances/BaseObject.java
@@ -0,0 +1,50 @@
+/*
+ * 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.instances;
+
+
+import org.osgi.framework.BundleContext;
+import org.osgi.service.component.ComponentContext;
+
+
+/**
+ * The <code>BaseObject</code> is a base class providing a number of methods
+ * to check. All methods take various combinations of arguments and return
+ * a single helper string to indicate what method has been called.
+ */
+public class BaseObject
+{
+
+ private void activate_no_arg()
+ {
+ throw new MethodNameException( "activate_no_arg" );
+ }
+
+
+ protected void activate_comp( ComponentContext ctx )
+ {
+ throw new MethodNameException( "activate_comp" );
+ }
+
+
+ void activate_comp_bundle( ComponentContext ctx, BundleContext bundle )
+ {
+ throw new MethodNameException( "activate_comp_bundle" );
+ }
+}
diff --git a/scr/src/test/java/org/apache/felix/scr/impl/instances/Level1Object.java b/scr/src/test/java/org/apache/felix/scr/impl/instances/Level1Object.java
new file mode 100644
index 0000000..fbac013
--- /dev/null
+++ b/scr/src/test/java/org/apache/felix/scr/impl/instances/Level1Object.java
@@ -0,0 +1,41 @@
+/*
+ * 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.instances;
+
+
+import java.util.Map;
+
+import org.osgi.framework.BundleContext;
+
+
+public class Level1Object extends BaseObject
+{
+
+ private void activate_level1_bundle( BundleContext ctx )
+ {
+ throw new MethodNameException("activate_level1_bundle");
+ }
+
+
+ protected void activate_level1_map( Map props )
+ {
+ throw new MethodNameException("activate_level1_map");
+ }
+
+}
diff --git a/scr/src/test/java/org/apache/felix/scr/impl/instances/Level3Object.java b/scr/src/test/java/org/apache/felix/scr/impl/instances/Level3Object.java
new file mode 100644
index 0000000..732b030
--- /dev/null
+++ b/scr/src/test/java/org/apache/felix/scr/impl/instances/Level3Object.java
@@ -0,0 +1,49 @@
+/*
+ * 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.instances;
+
+
+import java.util.Map;
+
+import org.apache.felix.scr.impl.instances2.Level2Object;
+import org.osgi.service.component.ComponentContext;
+
+
+public class Level3Object extends Level2Object
+{
+
+ private void activate_comp_map( ComponentContext ctx, Map map )
+ {
+ throw new MethodNameException("activate_comp_map");
+ }
+
+
+ // this method should not be found, since the method taking a
+ // Map has higher precedence
+ public void activate_collision()
+ {
+ throw new MethodNameException("not_expected_to_be_found");
+ }
+
+
+ public void activate_collision( Map map )
+ {
+ throw new MethodNameException("activate_collision");
+ }
+}
diff --git a/scr/src/test/java/org/apache/felix/scr/impl/instances/MethodNameException.java b/scr/src/test/java/org/apache/felix/scr/impl/instances/MethodNameException.java
new file mode 100644
index 0000000..3bfb7bb
--- /dev/null
+++ b/scr/src/test/java/org/apache/felix/scr/impl/instances/MethodNameException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.instances;
+
+
+public final class MethodNameException extends RuntimeException
+{
+
+ private static final long serialVersionUID = 1L;
+
+
+ public MethodNameException( String message )
+ {
+ super( message );
+ }
+}
diff --git a/scr/src/test/java/org/apache/felix/scr/impl/instances2/Level2Object.java b/scr/src/test/java/org/apache/felix/scr/impl/instances2/Level2Object.java
new file mode 100644
index 0000000..456f35e
--- /dev/null
+++ b/scr/src/test/java/org/apache/felix/scr/impl/instances2/Level2Object.java
@@ -0,0 +1,50 @@
+/*
+ * 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.instances2;
+
+
+import java.util.Map;
+
+import org.apache.felix.scr.impl.instances.Level1Object;
+import org.apache.felix.scr.impl.instances.MethodNameException;
+import org.osgi.service.component.ComponentContext;
+
+
+public class Level2Object extends Level1Object
+{
+
+ private void activate_comp_map( ComponentContext ctx, Map map )
+ {
+ throw new MethodNameException("activate_comp_map");
+ }
+
+
+ // this method should not be found, since the method taking a
+ // Map has higher precedence
+ public void activate_collision()
+ {
+ throw new MethodNameException("not_expected_to_be_found");
+ }
+
+
+ public void activate_collision( Map map )
+ {
+ throw new MethodNameException("activate_collision");
+ }
+}