/FileExtraction/Java_unseen/crisis-economics_CRISIS/CRISIS/src/eu/crisis_economics/abm/model/ModelUtils.java
/*
 * 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;
   }
}
