eu.crisis_economics.abm.model.ModelUtils.java Source code

Java tutorial

Introduction

Here is the source code for eu.crisis_economics.abm.model.ModelUtils.java

Source

/*
 * This file is part of CRISIS, an economics simulator.
 * 
 * Copyright (C) 2015 John Kieran Phillips
 *
 * CRISIS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * CRISIS 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with CRISIS.  If not, see <http://www.gnu.org/licenses/>.
 */
package eu.crisis_economics.abm.model;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.SerializationUtils;
import org.apache.commons.lang3.text.WordUtils;

import ai.aitia.meme.paramsweep.platform.mason.recording.annotation.Submodel;

import com.google.common.base.Preconditions;

import cern.colt.Arrays;
import eu.crisis_economics.abm.model.configuration.ComponentConfiguration;
import eu.crisis_economics.utilities.Pair;

/**
  * Static and stateless utility methods for the {@link MasterModel} subpackage.
  * This class provides the following static methods:
  * 
  * <ul>
  *   <li> {@link #search(Object, String, Class[])}<br>
  *        A depth first recursive parameter search tool. This method accepts an object
  *        {@code X}, the base name of a setter method, and an array of method argument
  *        types. Any object in the configuration hierarchy of {@code X} which has a 
  *        setter method with the appropriate signature and arguments is found and
  *        returned.
  *   <li> {@link #applyWithCloning(Object, String, Object...)}<br>
  *        This method invokes, with actual arguments, all methods identified by
  *        {@link #search(Object, String, Class[])}.
  *   <li> {@link #search(Object, String)} <br>
  *        A depth-first recursive parameter search tool. This method accepts an object
  *        {@code X} and the String ID of a {@link Parameter} to search for. 
  *        Any member field in the inheritance hierarchy of {@code X} or the inheritance 
  *        hierarchy of any member field in {@code X}, and so on, which carries a 
  *        {@link Parameter} annotation with the specified ID is returned. See also
  *        {@link #search(Object, String)}.
  * </ul>
  * 
  * @author phillips
  */
public final class ModelUtils {

    public static boolean VERBOSE_MODE = true;

    /**
      * A depth first recursive parameter search tool. This function accepts an object
      * {@code X}, the ({@link String}) name {@code N} of a method, and a {@link Class} array
      * of method argument types. Any object in the configuration hierarchy of {@code X} which
      * has a method with with the appropriate signature and arguments is found and
      * returned.<br>
      * 
      * This search operates as follows:
      * 
      * <ul>
      *   <li> If {@code X} contains a method {@code N} with the specified arguments, then 
      *        store and remember this method;
      *   <li> Otherwise search the subclasses of {@code X} for a method {@code N} with
      *        the specified arguments. If such a method is found, then store and
      *        remember this method;
      *   <li> Apply the above steps recursively (depth first) to every field in {@code X}
      *        of type {@link ComponentConfiguration}. Remember all of the methods identified
      *        by this search process and return these methods as well as the object 
      *        instances in which they were found.
      * </ul>
      * 
      * @param on
      *        The object to search.
      * @param methodToFind
      *        The method name to search for.
      * @param arguments
      *        A list of {@link Class} argument types for the method to find.
      * @return
      *        A {@link List} of {@link Pair}{@code s}. Each entry in this list is a {@link Pair}
      *        composed of one {@link Method} object and one {@link Object}. The {@link Method}
      *        satisfies the parameters of the query. The {@link Object} is an instance of an
      *        object whose class possesses the {@link Method}.
      */
    public static List<Pair<Method, Object>> search(final Object on, final String methodToFind,
            final Class<?>[] arguments) {
        final List<Pair<Method, Object>> result = new ArrayList<Pair<Method, Object>>();
        final Class<?> parentType = on.getClass();
        for (Class<?> typeToSearch = parentType; typeToSearch != null; typeToSearch = typeToSearch
                .getSuperclass()) {
            Method methodPtr = null;
            try {
                // Try to find a method with the specified name and exact argument types:
                methodPtr = typeToSearch.getDeclaredMethod(methodToFind, arguments);
                result.add(Pair.create(methodPtr, on));
                continue;
            } catch (final NoSuchMethodException e) {
                // Try to downcast method arguments for other class methods with the correct name:
                final Method[] allCalleeMethods = typeToSearch.getDeclaredMethods();
                for (final Method method : allCalleeMethods) {
                    if (!method.getName().equals(methodToFind))
                        continue;
                    final Type[] argTypes = method.getGenericParameterTypes();
                    if (argTypes.length != arguments.length)
                        continue;
                    for (int j = 0; j < arguments.length; ++j) {
                        if (!arguments[j].isAssignableFrom(argTypes[j].getClass()))
                            continue;
                    }
                    methodPtr = method;
                    result.add(Pair.create(methodPtr, on));
                    continue;
                }
            }
            if (methodPtr == null)
                continue;
        }
        // Search for any ComponentConfiguration fields in the specified object:
        for (Class<?> typeToSearch = parentType; typeToSearch != null; typeToSearch = typeToSearch
                .getSuperclass()) {
            for (Field field : typeToSearch.getDeclaredFields()) {
                if (!ComponentConfiguration.class.isAssignableFrom(field.getType()))
                    continue;
                field.setAccessible(true);
                final Object instance;
                try {
                    instance = field.get(on);
                } catch (final IllegalArgumentException e) {
                    continue; // Not found
                } catch (final IllegalAccessException e) {
                    continue; // Not found
                }
                if (instance != null) {
                    final List<Pair<Method, Object>> subResult = search(instance, methodToFind, arguments); // Descend into fields
                    if (subResult != null && !subResult.isEmpty())
                        result.addAll(subResult);
                    else
                        continue;
                }
            }
        }
        return result;
    }

    public static class GetterSetterPair {
        private Method getter, setter;
        private Object instance;

        private GetterSetterPair(final Method getter, final Method setter, final Object instance) {
            this.getter = Preconditions.checkNotNull(getter);
            this.setter = Preconditions.checkNotNull(setter);
            this.instance = Preconditions.checkNotNull(instance);
        }

        public Method getGetter() {
            return getter;
        }

        public Method getSetter() {
            return setter;
        }

        public Object getInstance() {
            return instance;
        }
    }

    public static List<GetterSetterPair> parameters(final Object of, final Class<?> paramType) {
        final List<GetterSetterPair> result = new ArrayList<GetterSetterPair>();
        final Class<?> parentType = of.getClass();
        for (Class<?> typeToSearch = parentType; typeToSearch != null; typeToSearch = typeToSearch
                .getSuperclass()) {
            final Map<String, Method> methodNames = new HashMap<String, Method>();
            final Method[] allCalleeMethods = typeToSearch.getDeclaredMethods();
            for (final Method method : allCalleeMethods) {
                final String name = method.getName();
                if (name.startsWith("get") && ClassUtils.isAssignable(paramType, method.getReturnType(), true))
                    methodNames.put(method.getName(), method);
                else if (name.startsWith("set") && method.getParameterTypes().length == 1
                        && ClassUtils.isAssignable(paramType, method.getParameterTypes()[0], true))
                    methodNames.put(method.getName(), method);
                else
                    continue;
                final String complement;
                if (name.startsWith("get")) {
                    complement = "set" + name.substring(3);
                    if (methodNames.containsKey(complement))
                        result.add(new GetterSetterPair(method, methodNames.get(complement), of));
                } else if (name.startsWith("set")) {
                    complement = "get" + name.substring(3);
                    if (methodNames.containsKey(complement))
                        result.add(new GetterSetterPair(methodNames.get(complement), method, of));
                }
            }
        }
        // Search for any ComponentConfiguration fields in the specified object:
        for (Class<?> typeToSearch = parentType; typeToSearch != null; typeToSearch = typeToSearch
                .getSuperclass()) {
            for (Field field : typeToSearch.getDeclaredFields()) {
                if (!ComponentConfiguration.class.isAssignableFrom(field.getType()))
                    continue;
                field.setAccessible(true);
                final Object instance;
                try {
                    instance = field.get(of);
                } catch (final IllegalArgumentException e) {
                    continue; // Not found
                } catch (final IllegalAccessException e) {
                    continue; // Not found
                }
                if (instance != null) {
                    final List<GetterSetterPair> subResult = parameters(instance, paramType); // Descend into fields
                    if (subResult != null && !subResult.isEmpty())
                        result.addAll(subResult);
                    else
                        continue;
                }
            }
        }
        return result;
    }

    /**
      * This method supplements {@link #search(Object, String, Class[])} by explicitly 
      * calling each of the {@link Method}{@code s} identified by {@code search} with 
      * concrete arguments.<br><br>
      * 
      * For each element in the return value of {@link #search(Object, String, Class[])}, this
      * method calls {@link Method#invoke(Object, Object...)} exactly once. The first argument
      * of this call is the object instance identified by {@code search}. The second (varargs) 
      * argument of this call is the list of parameters specified by the third argument of this
      * method.<br><br>
      * 
      * By default, every {@link Serializable} {@code argument} supplied to this method will
      * be cloned for each {@link Method} invocation.
      * 
      * @param on
      *        The object to configure.
      * @param setterName
      *        The base name of a setter method to search for. This method will be 
      *        converted to @{code set + capitalize(N)} if it does not already have
      *        this format.
      * @param arguments
      *        A list of arguments to invoke methods with the above name.
      * @return
      *        A list containing the return values of each call to
      *        {@link Method#invoke(Object, Object...)} made by this function.
      */
    public static List<Object> applyWithCloning(final Object on, final String setterName,
            final Object... arguments) {
        return ModelUtils.apply(on, true, setterName, arguments);
    }

    /**
      * See {@link #applyWithCloning(Object, String, Object...)}.<br><br> This method supplements 
      * {@link #applyWithCloning(Object, String, Object...)} with the boolean argument
      * {@code doCloneArguments}.
      * 
      * @param doCloneArguments
      *        If <code>true</code>, all {@link Serializable} {@code arguments} will
      *        be cloned anew for each use. Otherwise, if <code>false</code>, the objects
      *        listed in {@code arguments} will not be cloned and may potentially be 
      *        reused for several {@link Method} invocations.
      */
    public static List<Object> apply(final Object on, final boolean doCloneArguments, final String setterName,
            final Object... arguments) {
        final Class<?>[] argTypes = new Class<?>[arguments.length];
        for (int i = 0; i < arguments.length; ++i)
            argTypes[i] = arguments[i].getClass();
        final List<Pair<Method, Object>> targets = search(on, setterName, argTypes);
        final List<Object> result = new ArrayList<Object>();
        for (final Pair<Method, Object> target : targets)
            try {
                final Object[] argumentsToUse = new Object[arguments.length];
                for (int i = 0; i < arguments.length; ++i)
                    if (arguments[i] instanceof Serializable && doCloneArguments)
                        argumentsToUse[i] = SerializationUtils.clone((Serializable) arguments[i]);
                    else
                        argumentsToUse[i] = arguments[i];
                result.add(target.getFirst().invoke(target.getSecond(), argumentsToUse));
                if (VERBOSE_MODE)
                    System.out.printf("Applying %s(%s) to %s\n", target.getFirst().getName(),
                            arguments.length == 0 ? "void"
                                    : arguments.length == 1 ? arguments[0].toString() : Arrays.toString(arguments),
                            target.getFirst().getDeclaringClass().getSimpleName());
            } catch (final IllegalArgumentException e) {
                throw new IllegalStateException();
            } catch (final IllegalAccessException e) {
                throw new IllegalStateException();
            } catch (final InvocationTargetException e) {
                throw new IllegalStateException();
            }
        return result;
    }

    /**
      * Convert the basename {@code B} of a method to a full setter-type method name.<br><br>
      * This method is equivalent to the following:
      * 
      * <ul>
      *   <li> If {@code B} has the format {@code setX} where {@code X}
      *        is a {@link String} beginning with an uppercase letter, return {@code B};
      *   <li> Otherwise return {@code "set" + capitalize(B)}.
      * </ul>
      * 
      * @param baseName
      *        The {@link String} {@code B} above. This argument should be 
      *        non-<code>null</code> and nonempty.
      */
    public static String asSetter(final String baseName) {
        if (Preconditions.checkNotNull(baseName).isEmpty())
            throw new IllegalArgumentException();
        if (baseName.length() > 3 && baseName.startsWith("set") && Character.isUpperCase(baseName.charAt(3)))
            return baseName;
        else
            return "set" + WordUtils.capitalize(baseName);
    }

    /**
      * Convert the basename {@code B} of a method to a full getter-type method name.<br><br>
      * This method is equivalent to the following:
      * 
      * <ul>
      *   <li> If {@code B} has the format {@code getX} where {@code X}
      *        is a {@link String} beginning with an uppercase letter, return {@code B};
      *   <li> Otherwise return {@code "get" + capitalize(B)}.
      * </ul>
      * 
      * @param baseName
      *        The {@link String} {@code B} above. This argument should be 
      *        non-<code>null</code> and nonempty.
      */
    public static String asGetter(final String baseName) {
        if (Preconditions.checkNotNull(baseName).isEmpty())
            throw new IllegalArgumentException();
        if (baseName.length() > 3 && baseName.startsWith("get") && Character.isUpperCase(baseName.charAt(3)))
            return baseName;
        else
            return "get" + WordUtils.capitalize(baseName);
    }

    /**
      * A depth-first recursive parameter search tool. This function accepts an object
      * ({@code X}) and a {@link String} ID ({@code P}) of a parameter to search for.<br><br>
      * 
      * Any object {@code O} in the configuration hierarchy of {@code X} which possesses a field with
      * a) the appropriate annotation and b) parameter ID is identified and returned.
      * This method will search for member fields {@code F} (with any modifer) which satisfy:
      * 
      * <ul>
      *   <li> {@code F} carries a {@link Parameter} {@link Annotation}.
      *   <li> The {@code ID} of this {@link Parameter} is equal to {@code P}.
      *   <li> {@code F} does not itself belongs to a {@link Submodel} field that satisfies the
      *        above two conditions.
      * </ul>
      * 
      * This search operates as follows:<br><br>
      * 
      * <ul>
      *   <li> If {@code X} contains a member field {@code F} satisfying the above conditions, 
      *        {@code F} is accepted and returned.
      *   <li> Otherwise, each supertype in the inheritance hierarchy of {@code X} is searched.
      *   <li> Apply the above steps recursively (depth-first) to every field {@code F}
      *        in {@code X} annotated with {@link Submodel}, unless {@code F} itself
      *        satisfies the above conditions, in which case {@code F} is accepted and returned.
      * </ul>
      * 
      * This method returns a {@link List} of {@link ConfigurationModifier} objects. Each element
      * in this list corresponds to a field {@code F} somewhere in the inheritance hierarchy of
      * {@code X} which satisfied the above search conditions. {@link ConfigurationModifier}
      * objects facilitate direct changes to the value of each such {@code F}.<br><br>
      * 
      * @param on (<code>X</code>) <br>
      *        The object to search.
      * @param parameterIdToFind on (<code>P</code>) <br>
      *        The ID of the {@link Parameter} to search for.
      */
    public static List<ConfigurationModifier> search(final Object on, final String parameterIdToFind) {
        if (on == null)
            throw new IllegalArgumentException("search: object to search is null.");
        if (parameterIdToFind == null || parameterIdToFind.isEmpty())
            throw new IllegalArgumentException("search: parameter name is empty or null.");
        if (VERBOSE_MODE)
            System.out.println("search: searching object " + on.getClass().getSimpleName() + on.hashCode()
                    + " for parameters of type " + parameterIdToFind + ".");
        final Class<?> objClass = on.getClass();
        final List<ConfigurationModifier> methodsIdentified = new ArrayList<ConfigurationModifier>();
        for (Class<?> typeToSearch = objClass; typeToSearch != null; typeToSearch = typeToSearch.getSuperclass()) {
            for (final Field field : typeToSearch.getDeclaredFields()) {
                field.setAccessible(true);
                if (VERBOSE_MODE)
                    System.out.println("inspecting field with name: " + field.getName() + ".");
                try {
                    Annotation drilldownAnnotation = null, modelParameterAnnotation = null;
                    for (final Annotation element : field.getAnnotations()) {
                        if (element.annotationType().getName() == Submodel.class.getName()) { // Proxies
                            drilldownAnnotation = element;
                            if (VERBOSE_MODE)
                                System.out.println("field " + field.getName() + " is a subconfiguration.");
                            continue;
                        } else if (element.annotationType().getName() == Parameter.class.getName()) { // Proxies
                            final Class<? extends Annotation> type = element.annotationType();
                            final String id = (String) type.getMethod("ID").invoke(element);
                            if (parameterIdToFind.equals(id)) {
                                modelParameterAnnotation = element;
                                if (VERBOSE_MODE)
                                    System.out.println("* field is valid.");
                                continue;
                            } else if (VERBOSE_MODE)
                                System.out.println("field ID [" + id + "] does not match the required ID: "
                                        + parameterIdToFind + ".");
                            continue;
                        } else
                            continue;
                    }
                    if (modelParameterAnnotation != null) {
                        final ConfigurationModifier fieldWithMutators = findGetterSetterMethods(field, on,
                                parameterIdToFind);
                        methodsIdentified.add(fieldWithMutators);
                        continue;
                    } else if (drilldownAnnotation != null) {
                        if (VERBOSE_MODE)
                            System.out.println("descending into subconfiguration: " + field.getName());
                        final Object fieldValue = field.get(on);
                        methodsIdentified.addAll(search(fieldValue, parameterIdToFind));
                        continue;
                    }
                    if (VERBOSE_MODE)
                        System.out.println("rejecting parameter: " + field.getName());
                } catch (final SecurityException e) {
                    throw new IllegalStateException(
                            "search: a security exception was raised when testing a field with name "
                                    + field.getName() + " for model parameter annotations. Details follow: "
                                    + e.getMessage() + ".");
                } catch (final IllegalArgumentException e) {
                    throw new IllegalStateException(
                            "search: an illegal argument exception was raised when testing a field with name "
                                    + field.getName() + " for model parameter annotations. Details follow: "
                                    + e.getMessage() + ".");
                } catch (final IllegalAccessException e) {
                    throw new IllegalStateException(
                            "search: a security exception was raised when testing a field with name "
                                    + field.getName() + " for model parameter annotations. Details follow: "
                                    + e.getMessage() + ".");
                } catch (final InvocationTargetException e) {
                    throw new IllegalStateException(
                            "search: an invokation target exception was raised when testing a field with" + " name "
                                    + field.getName() + " for model parameter annotations. Details follow: "
                                    + e.getMessage() + ".");
                } catch (final NoSuchMethodException e) {
                    throw new IllegalStateException(
                            "search: a missing-method exception was raised when testing a field with name "
                                    + field.getName() + " for model parameter annotations. Details follow: "
                                    + e.getMessage() + ".");
                }
            }
        }
        if (VERBOSE_MODE)
            System.out.println("searched: " + on.getClass().getSimpleName() + on.hashCode()
                    + " for parameters of type " + parameterIdToFind + ".");
        return methodsIdentified;
    }

    /**
      * Modify every instance of a parameter in the hierarchy of a configurator.
      * 
      * @param on <br>
      *        A configurator to modify.
      * @param parameterIdToFind <br>
      *        The name of a parameter to search for.
      * @param substitute (<code>V</code>) <br>
      *        The substitute (new value) to apply, if and when an instance of the parameter
      *        is found.
      * @param doClone <br>
      *        Whether or not <code>V</code> should be cloned for each substitution applied.
      *        If this argument is <code>true</code>, <code>V</code> must be an instance of
      *        {@link Serializable}.
      */
    public static void apply(Object on, final String parameterIdToFind, Object substitute, boolean doClone) {
        final List<ConfigurationModifier> results = search(on, parameterIdToFind);
        for (ConfigurationModifier modifier : results) {
            if (doClone)
                modifier.set(SerializationUtils.clone((Serializable) substitute));
            else
                modifier.set(substitute);
        }
    }

    private static ConfigurationModifier findGetterSetterMethods(final Field field, final Object classInstance,
            final String parameterID) throws SecurityException, NoSuchMethodException {
        final Class<?> type = classInstance.getClass();
        final String fieldName = field.getName(), expectedGetterName = "get" + WordUtils.capitalize(fieldName),
                expectedSetterName = "set" + WordUtils.capitalize(fieldName);
        final Method getterMethod = type.getMethod(expectedGetterName),
                setterMethod = type.getMethod(expectedSetterName, field.getType());
        return new ConfigurationModifier(field, getterMethod, setterMethod, classInstance, parameterID);
    }

    /**
      * A lightweight aggregation class for 
      * 
      * @author phillips
      */
    public static final class ConfigurationModifier {

        @SuppressWarnings("unused")
        private final Field field;
        private final Method getter;
        private final Method setter;
        private final Object instance;
        private final String parameterId;

        private ConfigurationModifier(Field field, Method getter, Method setter, Object instance,
                String parameterId) {
            super();
            this.field = Preconditions.checkNotNull(field);
            this.getter = Preconditions.checkNotNull(getter);
            this.setter = Preconditions.checkNotNull(setter);
            this.instance = Preconditions.checkNotNull(instance);
            this.parameterId = Preconditions.checkNotNull(parameterId);
        }

        public Object get() {
            try {
                return getter.invoke(instance);
            } catch (final IllegalArgumentException neverThrows) {
                throw new IllegalStateException(getClass().getSimpleName()
                        + ": an unexpected illegal argument exception was raised" + " when a method with name "
                        + getter.getName() + " was invoked with no arguments. " + "Details follows: "
                        + neverThrows.getMessage());
            } catch (final IllegalAccessException e) {
                throw new IllegalStateException(getClass().getSimpleName()
                        + ": a security exception was raised when a method with " + "name " + getter.getName()
                        + " was invoked with no arguments. Details follows: " + e.getMessage());
            } catch (final InvocationTargetException e) {
                throw new IllegalStateException(
                        getClass().getSimpleName() + ": a invokation target exception was raised when a"
                                + " method with name " + getter.getName() + " was invoked with no arguments. "
                                + "Details follows: " + e.getMessage());
            }
        }

        public void set(final Object setting) {
            try {
                setter.invoke(instance, setting);
            } catch (final IllegalArgumentException e) {
                throw new IllegalStateException(
                        getClass().getSimpleName() + ": an illegal argument exception was raised"
                                + " when a method with name " + setter.getName() + " was invoked with argument "
                                + setting + ". Details follows: " + e.getMessage());
            } catch (final IllegalAccessException e) {
                throw new IllegalStateException(getClass().getSimpleName()
                        + ": a security exception was raised when a method" + " with name " + setter.getName()
                        + " was invoked with argument " + setting + ". Details follows: " + e.getMessage());
            } catch (final InvocationTargetException e) {
                throw new IllegalStateException(
                        getClass().getSimpleName() + ": an invokation target exception was raised when a"
                                + " method with name " + setter.getName() + " was invoked with argument " + setting
                                + ". Details follows: " + e.getMessage());
            }
        }

        /**
          * Get the {@link String} ID of the parameter this class modifies.
          */
        public String getParameterID() {
            return parameterId;
        }

        /**
          * Returns a brief description of this object. The exact details of the
          * string are subject to change, and should not be regarded as fixed.
          */
        @Override
        public String toString() {
            return "Field With Mutators, parameter ID: " + parameterId + ".";
        }
    }

    /**
      * Get a list of subconfiguration objects for a class instance (<code>X</code>). This 
      * method will do the following:
      * 
      * <ul>
      *   <li> Identify any fields in the input <code>X</code> which carry the
      *        <code>@Parameter</code> and <code>@Submodel</code> annotations.
      *   <li> If <code>X</code> is an instance of {@link ComponentConfiguration}, and
      *        the ID of the accompanying <code>@Parameter</code> annotation is nonempty,
      *        set the scope string of the field to the parameter ID.
      *   <li> Add a reference to the field to a list <code>L</code>.
      *   <li> Repeat the above steps depth-first recursively for each field in <code>X</code>.
      *   <li> Return <code>L</code>
      * </ul>
      * 
      * @param on <br>
      *        The class instance <code>X</code> to search.
      */
    public static List<ComponentConfiguration> getSubconfigurators(final Object on) {
        final Class<?> objClass = Preconditions.checkNotNull(on).getClass();
        final List<ComponentConfiguration> result = new ArrayList<ComponentConfiguration>();
        for (Class<?> typeToSearch = objClass; typeToSearch != null; typeToSearch = typeToSearch.getSuperclass()) {
            for (final Field field : typeToSearch.getDeclaredFields()) {
                if (!ComponentConfiguration.class.isAssignableFrom(field.getType()))
                    continue;
                field.setAccessible(true);
                try {
                    Annotation drilldownAnnotation = null, modelParameterAnnotation = null;
                    for (final Annotation element : field.getAnnotations()) {
                        if (element.annotationType().getName() == Submodel.class.getName()) { // Proxies
                            drilldownAnnotation = element;
                            continue;
                        } else if (element.annotationType().getName() == Parameter.class.getName()) { // Proxies
                            modelParameterAnnotation = element;
                            continue;
                        } else
                            continue;
                    }
                    if (modelParameterAnnotation != null && drilldownAnnotation != null) {
                        final Object value = field.get(on);
                        if (value == null)
                            throw new IllegalStateException(on.getClass().getSimpleName()
                                    + ": the value of a configurator member field" + " in "
                                    + (on.getClass().getSimpleName() + on.hashCode()) + " is null.");
                        result.add((ComponentConfiguration) value);
                        final boolean isScoped = (Boolean) modelParameterAnnotation.annotationType()
                                .getMethod("Scoped").invoke(modelParameterAnnotation);
                        if (!isScoped)
                            continue;
                        final String id = (String) modelParameterAnnotation.annotationType().getMethod("ID")
                                .invoke(modelParameterAnnotation);
                        if (id.isEmpty())
                            continue;
                        ((ComponentConfiguration) value).setScope(id);
                    }
                } catch (final SecurityException e) {
                    throw new IllegalStateException(on.getClass().getSimpleName()
                            + ": a security exception was raised when testing a field with name " + field.getName()
                            + " for model parameter annotations. Details follow: " + e.getMessage() + ".");
                } catch (final IllegalArgumentException e) {
                    throw new IllegalStateException(on.getClass().getSimpleName()
                            + "search: an illegal argument exception was raised when testing a field with name "
                            + field.getName() + " for model parameter annotations. Details follow: "
                            + e.getMessage() + ".");
                } catch (final IllegalAccessException e) {
                    throw new IllegalStateException(on.getClass().getSimpleName()
                            + "search: a security exception was raised when testing a field with name "
                            + field.getName() + " for model parameter annotations. Details follow: "
                            + e.getMessage() + ".");
                } catch (final InvocationTargetException e) {
                    throw new IllegalStateException(on.getClass().getSimpleName()
                            + "search: an invokation target exception was raised when testing a field with"
                            + " name " + field.getName() + " for model parameter annotations. Details follow: "
                            + e.getMessage() + ".");
                } catch (final NoSuchMethodException e) {
                    throw new IllegalStateException(on.getClass().getSimpleName()
                            + "search: a missing-method exception was raised when testing a field with name "
                            + field.getName() + " for model parameter annotations. Details follow: "
                            + e.getMessage() + ".");
                }
            }
        }
        return result;
    }
}