//----------------------------------------------------------------------------
// Copyright (C) 2011 Ingrid Nunes
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// To contact the authors:
// http://inf.ufrgs.br/prosoft/bdi4jade/
//
//----------------------------------------------------------------------------
package bdi4jade.util;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import bdi4jade.annotation.Parameter;
import bdi4jade.annotation.Parameter.Direction;
import bdi4jade.belief.Belief;
import bdi4jade.core.Capability;
import bdi4jade.exception.ParameterException;
import bdi4jade.goal.Goal;
import bdi4jade.plan.planbody.PlanBody;
/**
* This is a utility class that provides many methods that use reflection for
* different purposes.
*
* @author Ingrid Nunes
*
*/
public abstract class ReflectionUtils {
private static final String GETTER_PREFIX = "get";
private static final Log log = LogFactory.getLog(ReflectionUtils.class);
private static final String SETTER_PREFIX = "set";
/**
* Adds to the goal owners map the capability classes that owns a goal to
* the capability instance passed as parameter.
*
* @param goalOwnersMap
* the goal owners map to which the owners of the given
* capability should be added.
* @param capability
* the capability that should be added to the map.
*/
public static void addGoalOwner(
Map<Class<? extends Capability>, Set<Capability>> goalOwnersMap,
Capability capability) {
if (!Capability.class.equals(capability.getClass())) {
addGoalOwner(goalOwnersMap, capability.getClass(), capability);
for (Class<? extends Capability> parentCapability : capability
.getParentCapabilities()) {
addGoalOwner(goalOwnersMap, parentCapability, capability);
}
} else {
assert capability.getParentCapabilities().isEmpty();
}
}
private static void addGoalOwner(
Map<Class<? extends Capability>, Set<Capability>> goalOwnersMap,
Class<? extends Capability> cababilityClass, Capability capability) {
Set<Capability> owners = goalOwnersMap.get(cababilityClass);
if (owners == null) {
owners = new HashSet<>();
goalOwnersMap.put(cababilityClass, owners);
}
owners.add(capability);
}
private static void checkSkipIsOK(Parameter parameter, String msg,
Exception cause) throws ParameterException {
if (parameter.mandatory()) {
ParameterException exc = new ParameterException(msg, cause);
log.warn(exc);
throw exc;
}
}
private static boolean isGetter(Method method) {
if (!method.getName().startsWith(GETTER_PREFIX))
return false;
if (method.getParameterTypes().length != 0)
return false;
if (void.class.equals(method.getReturnType()))
return false;
return true;
}
private static boolean isParameterIn(Parameter param, Direction[] directions) {
for (Direction dir : directions) {
if (dir.equals(param.direction())) {
return true;
}
}
return false;
}
private static boolean isSetter(Method method) {
if (!method.getName().startsWith(SETTER_PREFIX))
return false;
if (method.getParameterTypes().length != 1)
return false;
if (!void.class.equals(method.getReturnType()))
return false;
return true;
}
private static String methodToPropertyName(String prefix, Method method) {
String property = method.getName().substring(prefix.length());
return property.substring(0, 1).toLowerCase() + property.substring(1);
}
private static String propertyToMethodName(String prefix, String property) {
return prefix + property.substring(0, 1).toUpperCase()
+ property.substring(1);
}
/**
* Sets the input parameters of a plan body based on the parameters passed
* in the goal that triggered its execution.
*
* @param planBody
* the plan body to have its input parameters set.
* @param goal
* the goal that has input parameters.
* @throws ParameterException
* if an exception occurs in this setting process.
*/
public static void setPlanBodyInput(PlanBody planBody, Goal goal)
throws ParameterException {
setupParameters(planBody, new Direction[] { Direction.IN,
Direction.INOUT }, goal, new Direction[] { Direction.IN,
Direction.INOUT });
}
/**
* Sets the output parameters of a goal based on the output generated by the
* plan body whose execution was triggered by this goal.
*
* @param planBody
* the plan body generated the output parameters.
* @param goal
* the goal to have its output parameters set.
* @throws ParameterException
* if an exception occurs in this setting process.
*/
public static void setPlanBodyOutput(PlanBody planBody, Goal goal)
throws ParameterException {
setupParameters(goal,
new Direction[] { Direction.OUT, Direction.INOUT }, planBody,
new Direction[] { Direction.OUT, Direction.INOUT });
}
/**
* Sets plan body fields annotated with {@link bdi4jade.annotation.Belief}.
*
* @param planBody
* the plan body to be setup with beliefs.
*/
public static void setupBeliefs(PlanBody planBody) {
Capability capability = planBody.getPlan().getPlanLibrary()
.getCapability();
Class<?> currentClass = planBody.getClass();
while (PlanBody.class.isAssignableFrom(currentClass)) {
for (Field field : currentClass.getDeclaredFields()) {
boolean b = field.isAccessible();
field.setAccessible(true);
try {
if (field
.isAnnotationPresent(bdi4jade.annotation.Belief.class)) {
if (Belief.class.isAssignableFrom(field.getType())) {
bdi4jade.annotation.Belief beliefAnnotation = field
.getAnnotation(bdi4jade.annotation.Belief.class);
String beliefName = beliefAnnotation.name();
if (beliefName == null || "".equals(beliefName)) {
beliefName = field.getName();
}
Belief<?, ?> belief = capability.getBeliefBase()
.getBelief(beliefName);
field.set(planBody, belief);
} else {
throw new ClassCastException("Field "
+ field.getName() + " should be a Belief");
}
}
} catch (Exception exc) {
log.warn(exc);
}
field.setAccessible(b);
}
currentClass = currentClass.getSuperclass();
}
}
/**
* Sets the input parameters of goal based on the output parameters of
* another goal. This is useful when goals are executed sequentially, and
* the input of a goal is the output of another.
*
* @param goalOut
* the goal that has output parameters that are input of the
* goalIn.
* @param goalIn
* the goal to have its input parameters set.
* @throws ParameterException
* if an exception occurs in this setting process.
*/
public static void setupParameters(Goal goalOut, Goal goalIn)
throws ParameterException {
setupParameters(goalIn,
new Direction[] { Direction.IN, Direction.INOUT }, goalOut,
new Direction[] { Direction.OUT, Direction.INOUT });
}
private static void setupParameters(Object obj1, Direction[] dir1,
Object obj2, Direction[] dir2) throws ParameterException {
for (Method method : obj1.getClass().getMethods()) {
if (method.isAnnotationPresent(Parameter.class)) {
Parameter parameter = method.getAnnotation(Parameter.class);
if (!isParameterIn(parameter, dir1)) {
continue;
}
if (!(isGetter(method) || isSetter(method))) {
checkSkipIsOK(parameter, "Method " + method
+ " should be a getter or setter.", null);
continue;
}
String property = null;
Method setter = null;
if (isGetter(method)) {
property = methodToPropertyName(GETTER_PREFIX, method);
try {
setter = obj1.getClass().getMethod(
propertyToMethodName(SETTER_PREFIX, property),
method.getReturnType());
} catch (NoSuchMethodException nsme) {
checkSkipIsOK(parameter,
"There is no setter method associated with property "
+ property, nsme);
continue;
}
} else {
property = methodToPropertyName(SETTER_PREFIX, method);
setter = method;
}
Method getter = null;
try {
getter = obj2.getClass().getMethod(
propertyToMethodName(GETTER_PREFIX, property));
if (!getter.isAnnotationPresent(Parameter.class)
|| !isParameterIn(
getter.getAnnotation(Parameter.class), dir2)) {
checkSkipIsOK(parameter,
"There is no parameter associated with method "
+ method + " name " + property, null);
continue;
}
} catch (NoSuchMethodException nsme) {
checkSkipIsOK(parameter,
"There is no getter method associated with property "
+ property, nsme);
continue;
}
try {
setter.invoke(obj1, getter.invoke(obj2));
} catch (Exception exc) {
checkSkipIsOK(parameter, "An unknown error occurrred.", exc);
}
}
}
}
}