/*
 * This file is part of JGAP.
 *
 * JGAP offers a dual license model containing the LGPL as well as the MPL.
 *
 * For licensing information please see the file license.txt included with JGAP
 * or have a look at the top of class org.jgap.Chromosome which representatively
 * includes the JGAP license policy applicable for any file delivered with JGAP.
 */
package org.jgap.gp.impl;

import java.lang.reflect.*;
import java.util.*;

import org.apache.log4j.*;
import org.jgap.*;
import org.jgap.gp.*;
import org.jgap.gp.terminal.*;
import org.jgap.util.*;

/**
 * Chromosome representing a single GP Program.
 *
 * @author Klaus Meffert
 * @since 3.0
 */
public class ProgramChromosome
    extends BaseGPChromosome implements Comparable, Cloneable, IBusinessKey {
  /** String containing the CVS revision. Read out via reflection!*/
  private final static String CVS_REVISION = "$Revision: 1.49 $";

  final static String PERSISTENT_FIELD_DELIMITER = ":";

  final static String GENE_DELIMITER_HEADING = "<";

  final static String GENE_DELIMITER_CLOSING = ">";

  final static String GENE_DELIMITER = "#";

  private transient static Logger LOGGER = Logger.getLogger(ProgramChromosome.class);

  /**
   * The list of allowed functions/terminals.
   */
  private CommandGene[] m_functionSet;

  /**
   * Array to hold the depths of each node.
   */
  private int[] m_depth;

  /**
   * Array to hold the types of the arguments to this Chromosome.
   */
  private Class[] argTypes;

  private transient int m_index;

  private transient int m_maxDepth;

  /**
   * The array of genes contained in this chromosome.
   */
  private CommandGene[] m_genes;

  /**
   * Application-specific data that is attached to this Chromosome.
   * This data may assist the application in evaluating this Chromosome
   * in the fitness function. JGAP does not operate on the data, aside
   * from allowing it to be set and retrieved, and considering it with
   * comparations (if user opted in to do so).
   */
  private Object m_applicationData;

  /**
   * Method compareTo(): Should we also consider the application data when
   * comparing? Default is "false" as "true" means a Chromosome's losing its
   * identity when application data is set differently!
   *
   * @since 3.0
   */
  private boolean m_compareAppData;

  public ProgramChromosome(GPConfiguration a_conf, int a_size)
      throws InvalidConfigurationException {
    super(a_conf);
    if (a_size <= 0) {
      throw new IllegalArgumentException(
          "Chromosome size must be greater than zero");
    }
    init(a_size);
  }

  public ProgramChromosome(GPConfiguration a_conf, int a_size,
                           IGPProgram a_ind)
      throws InvalidConfigurationException {
    super(a_conf, a_ind);
    if (a_size <= 0) {
      throw new IllegalArgumentException(
          "Chromosome size must be greater than zero");
    }
    if (a_ind == null) {
      throw new IllegalArgumentException("Individual must not be null");
    }
    init(a_size);
  }

  public ProgramChromosome(GPConfiguration a_conf, int a_size,
                           CommandGene[] a_functionSet,
                           Class[] a_argTypes,
                           IGPProgram a_ind)
      throws InvalidConfigurationException {
    super(a_conf, a_ind);
    if (a_size <= 0) {
      throw new IllegalArgumentException(
          "Chromosome size must be greater than zero");
    }
    if (a_ind == null) {
      throw new IllegalArgumentException("Individual must not be null");
    }
    m_functionSet = a_functionSet;
    argTypes = a_argTypes;
    init(a_size);
  }

  public ProgramChromosome(GPConfiguration a_conf, CommandGene[] a_initialGenes)
      throws InvalidConfigurationException {
    super(a_conf);
    int i = 0;
    while (i < a_initialGenes.length && a_initialGenes[i] != null) {
      i++;
    }
    init(a_initialGenes.length);
    for (int k = 0; k < i; k++) {
      m_genes[k] = a_initialGenes[k];
    }
  }

  public ProgramChromosome(final GPConfiguration a_conf)
      throws InvalidConfigurationException {
    super(a_conf);
    init();
  }

  /**
   * Default constructor. Only use for dynamic instantiation.
   *
   * @throws InvalidConfigurationException
   *
   * @author Klaus Meffert
   * @since 3.0
   */
  public ProgramChromosome()
      throws InvalidConfigurationException {
    this(GPGenotype.getStaticGPConfiguration());
  }

  private void init()
      throws InvalidConfigurationException {
    init(getGPConfiguration().getPopulationSize());
  }

  private void init(final int a_size)
      throws InvalidConfigurationException {
    m_depth = new int[a_size];
    m_genes = new CommandGene[a_size];
    /**@todo is speedup possible by using dynamic list?*/
  }

  public void setArgTypes(Class[] a_argTypes) {
    argTypes = a_argTypes;
  }

  public synchronized Object clone() {
    try {
      int size = m_genes.length;
      CommandGene[] genes = new CommandGene[size];
      for (int i = 0; i < size; i++) {
        if (m_genes[i] == null) {
          break;
        }
        // Try deep clone of genes.
        // ------------------------
        if (ICloneable.class.isAssignableFrom(m_genes[i].getClass())) {
          genes[i] = (CommandGene) ( (ICloneable) m_genes[i]).clone();
        }
        else {
          // No deep clone possible.
          // -----------------------
          genes[i] = m_genes[i];
        }
      }
      ProgramChromosome chrom = new ProgramChromosome( (GPConfiguration)
          getGPConfiguration(), (CommandGene[]) genes);
      chrom.argTypes = (Class[]) argTypes.clone();
      if (getFunctionSet() != null) {
        chrom.setFunctionSet( (CommandGene[]) getFunctionSet().clone());
      }
      if (m_depth != null) {
        chrom.m_depth = (int[]) m_depth.clone();
      }
      chrom.setIndividual(getIndividual());
      return chrom;
    } catch (Exception cex) {
      // Rethrow to have a more convenient handling.
      // -------------------------------------------
      throw new IllegalStateException(cex);
    }
  }

  /**
   * Clean up the chromosome.
   *
   * @author Klaus Meffert
   * @since 3.0
   */
  public void cleanup() {
    int len = m_genes.length;
    for (int i = 0; i < len; i++) {
      if (m_genes[i] == null) {
        break;
      }
      m_genes[i].cleanup();
    }
  }

  /**
   * Initialize this chromosome using the grow or the full method.
   *
   * @param a_num the chromosome's index in the individual of this chromosome
   * @param a_depth the maximum depth of the chromosome to create
   * @param a_type the type of the chromosome to create
   * @param a_argTypes the array of argument types for this chromosome
   * @param a_functionSet the set of nodes valid to pick from
   * @param a_grow true: use grow method; false: use full method
   * @param a_tries maximum number of tries to create a valid program
   *
   * @author Klaus Meffert
   * @since 3.0
   */
  public void growOrFull(final int a_num, final int a_depth, final Class a_type,
                         final Class[] a_argTypes,
                         final CommandGene[] a_functionSet, boolean a_grow,
                         int a_tries) {
    try {
      argTypes = a_argTypes;
      setFunctionSet(new CommandGene[a_functionSet.length + a_argTypes.length]);
      System.arraycopy(a_functionSet, 0, getFunctionSet(), 0,
                       a_functionSet.length);
      for (int i = 0; i < a_argTypes.length; i++) {
        m_functionSet[a_functionSet.length + i]
            = new Argument(getGPConfiguration(), i, a_argTypes[i]);
      }
      // Initialization of genotype according to specific problem requirements.
      // ----------------------------------------------------------------------
      CommandGene n;
      IGPInitStrategy programIniter = getGPConfiguration().getInitStrategy();
      if (programIniter == null) {
        n = null;
      }
      else {
        try {
          n = programIniter.init(this, a_num);
        } catch (Exception ex) {
          throw new IllegalStateException(ex);
        }
      }
      // Build the (rest of the) GP program.
      // -----------------------------------
      int localDepth = a_depth;
      m_index = 0;
      m_maxDepth = localDepth;
      growOrFullNode(a_num, localDepth, a_type, 0, m_functionSet, n, 0, a_grow,
                     -1, false);
      // Give the chance of validating the whole program.
      // ------------------------------------------------
      if (!getGPConfiguration().validateNode(this, null, n, a_tries,
          a_num, 0, a_type, m_functionSet,
          a_depth, a_grow, -1, true)) {
        throw new IllegalStateException("Randomly created program violates"
                                        +
                                        " configuration constraints (symptom 3).");
      }
      redepth();
    } catch (InvalidConfigurationException iex) {
      throw new IllegalStateException(iex.getMessage());
    }
  }

  /**
   * Output program in left-hand notion (e.g.: "+ X Y" for "X + Y").
   *
   * @param a_startNode node to start with
   * @return output in left-hand notion
   *
   * @author Klaus Meffert
   * @since 3.0
   */
  public String toString(final int a_startNode) {
    if (a_startNode < 0) {
      return "";
    }
    // Replace any occurance of placeholders (e.g. &1, &2...) in the function's
    // name.
    // ------------------------------------------------------------------------
    String funcName = m_genes[a_startNode].toString();
    int j = 1;
    do {
      String placeHolder = "&" + j;
      int foundIndex = funcName.indexOf(placeHolder);
      if (foundIndex < 0) {
        break;
      }
      funcName = funcName.replaceFirst(placeHolder, "");
      j++;
    } while (true);
    // Now remove any leading and trailing spaces.
    // -------------------------------------------
    if (j > 0) {
      funcName = funcName.trim();
    }
    IGPProgram ind = getIndividual();
    if (getFunctions()[a_startNode].getArity(ind) == 0) {
      return funcName + " ";
    }
    String str = "";
    str += funcName + " ( ";
    int arity = m_genes[a_startNode].getArity(ind);
    for (int i = 0; i < arity; i++) {
      str += toString(getChild(a_startNode, i));
    }
    if (a_startNode == 0) {
      str += ")";
    }
    else {
      str += ") ";
    }
    return str;
  }

  /**
   * Output program in "natural" notion (e.g.: "X + Y" for "X + Y").
   *
   * @param a_startNode the node to start with, e.g. 0 for a complete dump of
   * the program
   * @return output in normalized notion
   *
   * @author Klaus Meffert
   * @since 3.0
   */
  public String toStringNorm(final int a_startNode) {
    if (a_startNode < 0) {
      return "";
    }
    IGPProgram ind = getIndividual();
    if (m_genes[a_startNode].getArity(ind) == 0) {
      return getFunctions()[a_startNode].toString();
    }
    String str = "";
    boolean paramOutput = false;
    if (m_genes[a_startNode].getArity(ind) > 0) {
      if (m_genes[a_startNode].toString().indexOf("&1") >= 0) {
        paramOutput = true;
      }
    }
    if (m_genes[a_startNode].getArity(ind) == 1 || paramOutput) {
      str += getFunctions()[a_startNode].toString();
    }
    if (a_startNode > 0) {
      str = "(" + str;
    }
    for (int i = 0; i < m_genes[a_startNode].getArity(ind); i++) {
      String childString = toStringNorm(getChild(a_startNode, i));
      String placeHolder = "&" + (i + 1);
      int placeholderIndex = str.indexOf(placeHolder);
      if (placeholderIndex >= 0) {
        str = str.replaceFirst(placeHolder, childString);
      }
      else {
        str += childString;
      }
      if (i == 0 && m_genes[a_startNode].getArity(ind) != 1
          && !paramOutput) {
        str += " " + m_genes[a_startNode].toString() + " ";
      }
    }
    if (a_startNode > 0) {
      str += ")";
    }
    return str;
  }

  /**
   * @return business key of the chromosome
   *
   * @author Klaus Meffert
   * @since 3.4
   */
  public String getBusinessKey() {
    return toStringNorm(0);
  }

  /**
   * @return debug representation of progrm chromosome, containing class names
   * of all children
   *
   * @author Klaus Meffert
   * @since 3.4
   */
  public String toStringDebug() {
    IGPProgram ind = getIndividual();
    if (m_genes[0].getArity(ind) == 0) {
      return getClass().getName();
    }
    String s = "";
    for (int i = 0; i < m_genes[0].getArity(ind); i++) {
      String childString = toStringNorm(getChild(0, i));
      s = s + "<" + childString + " >";
    }
    return s;
  }

  /**
   * Determines whether there exists a function or terminal in the given node
   * set with the given return and sub return type.
   *
   * @param a_returnType the return type to look for
   * @param a_subReturnType the sub return type to look for
   * @param a_nodeSet the array of nodes to look through
   * @param a_function true to look for a function, false to look for a terminal
   * @param a_growing true: grow mode, false: full mode
   *
   * @return true if such a node exists, false otherwise
   *
   * @author Klaus Meffert
   * @since 3.0
   */
  public boolean isPossible(Class a_returnType, int a_subReturnType,
                            CommandGene[] a_nodeSet,
                            boolean a_function, boolean a_growing) {
    IGPProgram ind = getIndividual();
    for (int i = 0; i < a_nodeSet.length; i++) {
      if (a_nodeSet[i].getReturnType() == a_returnType
          && (a_subReturnType == 0
              || a_subReturnType == a_nodeSet[i].getSubReturnType())) {
        if (a_nodeSet[i].getArity(ind) == 0 && (!a_function || a_growing)) {
          return true;
        }
        if (a_nodeSet[i].getArity(ind) != 0 && a_function) {
          return true;
        }
      }
    }
    return false;
  }

  /**
   * Randomly chooses a valid node from the functions set.
   *
   * @param a_chromIndex index of the chromosome in the individual (0..n-1)
   * @param a_returnType the return type of node to choose
   * @param a_subReturnType the sub return type to look for
   * @param a_functionSet the functions to use
   * @param a_function true to choose a function, false to choose a terminal
   * @param a_growing true to ignore the function parameter, false otherwise
   * @return the node chosen
   *
   * @author Klaus Meffert
   * @since 3.0
   */
  protected CommandGene selectNode(int a_chromIndex, Class a_returnType,
                                   int a_subReturnType,
                                   CommandGene[] a_functionSet,
                                   boolean a_function, boolean a_growing) {
    // Determine possible functions.
    // -----------------------------
    Vector possibleFunctions=new Vector(0);
    IGPProgram ind = getIndividual();
    for (int i = 0; i < a_functionSet.length; i++) {
      if (a_functionSet[i].getReturnType() == a_returnType
          && (a_subReturnType == 0
              || a_subReturnType == a_functionSet[i].getSubReturnType())) {
        if (a_functionSet[i].getArity(ind) == 0 && (!a_function || a_growing)) {
          possibleFunctions.add(a_functionSet[i]);
        }
        if (a_functionSet[i].getArity(ind) != 0 && a_function) {
          possibleFunctions.add(a_functionSet[i]);
        }
      }
    }
    // Error handing in case no valid function found.
    // ----------------------------------------------
    if (possibleFunctions.isEmpty()) {
      if (a_growing && (a_returnType == CommandGene.VoidClass
                        || a_returnType == Void.class)) {
        // We simply return a NOP, it does nothing :-)
        // -------------------------------------------
        try {
          return new NOP(getGPConfiguration(), a_subReturnType);
        } catch (InvalidConfigurationException iex) {
          // Should never happen.
          // --------------------
          throw new RuntimeException(iex);
        }
      }
      final String errormsg = "Chromosome (depth "
          + getDepth(0)
          + ", index " + a_chromIndex
          + ") requires a " +
          (a_function ?
           ("function" + (a_growing ? " or terminal" : ""))
           : "terminal") + " of return type " +
          a_returnType
          + " (sub return type " + a_subReturnType + ")"
          + " but there is no such node available";
      if (!getGPConfiguration().isStrictProgramCreation()) {
        // Allow another try in case it is allowed.
        // ----------------------------------------
        throw new IllegalStateException(errormsg);
      }
      else {
        // Interrupt the whole evolution process.
        // --------------------------------------
        throw new RuntimeException(errormsg);
      }
    }
    // Select a function randomly.
    // ---------------------------
    int index = getGPConfiguration().getRandomGenerator().nextInt(
        possibleFunctions.size());
    CommandGene n = possibleFunctions.elementAt(index);
    return n;
  }

  /**
   * Create a tree of nodes using the grow or the full method.
   *
   * @param a_num the chromosome's index in the individual of this chromosome
   * @param a_depth the maximum depth of the tree to create
   * @param a_returnType the return type the lastly evaluated node must have
   * @param a_subReturnType the sub return type to look for
   * @param a_functionSet the set of function valid to pick from
   * @param a_rootNode null, or parent node of the node to develop
   * @param a_recurseLevel 0 for first call
   * @param a_grow true: use grow method; false: use full method
   * @param a_childNum index of the child in the parent node to which it belongs
   * (-1 if node is root node)
   * @param a_validateNode true: check if node selected is valid (when called
   * recursively a_validateNode is set to true)
   *
   * @return possible modified set of functions (e.g. to avoid having a unique
   * command more than once)
   *
   * @author Klaus Meffert
   * @since 3.0
   */
  protected CommandGene[] growOrFullNode(int a_num, int a_depth,
      Class a_returnType, int a_subReturnType, CommandGene[] a_functionSet,
      CommandGene a_rootNode, int a_recurseLevel,
      boolean a_grow, int a_childNum, boolean a_validateNode) {
    boolean mutated = false;
    boolean uncloned = true;
    GPConfiguration conf = getGPConfiguration();
    RandomGenerator random = conf.getRandomGenerator();
    if (a_rootNode == null || a_validateNode) {
      int tries = 0;
      int evolutionRound = getGPConfiguration().getGenerationNr();
      boolean aFunction = a_depth >= 1;
      // Clone the array, not the content of the array.
      // ----------------------------------------------
      CommandGene[] localFunctionSet = (CommandGene[]) a_functionSet.clone();
      int len = a_functionSet.length;
      do {
        CommandGene node = selectNode(a_num, a_returnType, a_subReturnType,
                                      localFunctionSet, aFunction, a_grow);
        if (!conf.validateNode(this, node, a_rootNode, tries++, a_num,
                               a_recurseLevel, a_returnType, localFunctionSet,
                               a_depth, a_grow, a_childNum, false)) {
          // In the first round of evolution ensure to always have one valid
          // individual as we need a prototype for cloning!
          // ---------------------------------------------------------------
          if (evolutionRound > 0 || tries <= len*2) {
            // Remove invalid node from local function set.
            // --------------------------------------------
            localFunctionSet = remove(localFunctionSet, node);
            if (localFunctionSet.length == 0) {
              throw new IllegalStateException("No appropriate function found"
                  + " during program creation!");
            }
            continue;
          }
        }
        // Optionally use a mutant/clone of the originally selected command
        // instead of reusing the same command instance.
        // ----------------------------------------------------------------
        if (random.nextDouble() <= conf.getMutationProb()) {
          if (IMutateable.class.isAssignableFrom(node.getClass())) {
            try {
              CommandGene node2 = ( (IMutateable) node).applyMutation(0,
                  random.nextDouble());
              // Check if mutant's function is allowed.
              // --------------------------------------
              if (getCommandOfClass(0, node2.getClass()) >= 0) {
                mutated = true;
                if (node2 != node) {
                  node = node2;
                  uncloned = false;
                }
              }
            } catch (InvalidConfigurationException iex) {
              // Ignore but log.
              // ---------------
              LOGGER.warn("Ignored problem", iex);
            }
          }
        }
        // Avoid using commands more than once if allowed only once.
        // ---------------------------------------------------------
        if (IUniqueCommand.class.isAssignableFrom(node.getClass())) {
          a_functionSet = remove(a_functionSet, node);
        }
        a_rootNode = node;
        break;
      } while (true);
    }
    // Generate the new node.
    // ----------------------
    m_depth[m_index] = m_maxDepth - a_depth;
    // Optional dynamize the arity for commands with a flexible number
    // of children. Normally, dynamizeArity does nothing, see declaration
    // of method in CommandGene, which can be overridden in sub classes.
    // -------------------------------------------------------------------
    boolean dynamize = random.nextDouble() <= conf.getDynamizeArityProb();
    if (dynamize) {
      a_rootNode.dynamizeArity();
    }
    // Clone node if possible and not already done via mutation.
    // ---------------------------------------------------------
    if (uncloned && !conf.isNoCommandGeneCloning()
        && a_rootNode instanceof ICloneable) {
        /**@todo we could optionally use the clone handler*/
      a_rootNode = (CommandGene) ( (ICloneable) a_rootNode).clone();
      m_genes[m_index++] = a_rootNode;
    }
    else {
      m_genes[m_index++] = a_rootNode;
    }
    if (a_depth >= 1) {
      IGPProgram ind = getIndividual();
      int arity = a_rootNode.getArity(ind);
      for (int i = 0; i < arity; i++) {
        // Ensure required depth is cared about.
        // -------------------------------------
        if (m_index < m_depth.length) {
          a_functionSet = growOrFullNode(a_num, a_depth - 1,
              a_rootNode.getChildType(getIndividual(), i),
              a_rootNode.getSubChildType(i),
              a_functionSet, a_rootNode, a_recurseLevel + 1, a_grow,
              i, true);
        }
        else {
          // No valid program could be generated. Abort.
          // -------------------------------------------
          throw new IllegalStateException("Randomly created program violates"
              + " configuration constraints (symptom 1). It may be that you"
              + " specified a too small number of maxNodes to use"
              + " (current arity: "
              + i
              + ", overall arity: "
              + arity
              + ")!");
        }
      }
    }
    else {
      if (a_rootNode.getArity(getIndividual()) > 0) {
        // No valid program could be generated. Abort.
        // -------------------------------------------
        throw new IllegalStateException("Randomly created program violates"
                                        + " configuration constraints"
                                        + " (symptom 2)");
      }
    }
    return a_functionSet;
  }

  /**
   * Recalculate the depth of each node.
   *
   * @author Klaus Meffert
   * @since 3.0
   */
  public void redepth() {
    m_depth[0] = 0;
    redepth(0);
  }

  /**
   * Calculate the depth of the next node and the indices of the children
   * of the current node.
   * The depth of the next node is just one plus the depth of the current node.
   * The index of the first child is always the next node. The index of the
   * second child is found by recursively calling this method on the tree
   * starting with the first child.
   *
   * @param a_index the index of the reference depth
   * @return the index of the next node of the same depth as the current node
   * (i.e. the next sibling node)
   *
   * @author Klaus Meffert
   * @since 3.0
   */
  protected int redepth(int a_index) {
    int num = a_index + 1;
    CommandGene command = getNode(a_index);
    if (command == null) {
      throw new IllegalStateException("ProgramChromosome invalid at index "
                                      + a_index
                                      + " (command gene is null)");
    }
    IGPProgram ind = getIndividual();
    int arity = command.getArity(ind);
    for (int i = 0; i < arity; i++) {
      if (num < m_depth.length) {
        m_depth[num] = m_depth[a_index] + 1;
        // children[i][n] = num;
        num = redepth(num);
        if (num < 0) {
          break;
        }
      }
      else {
        return -1;
      }
    }
    return num;
  }

  /**
   * Gets the a_child'th child of the a_index'th node in this chromosome. This
   * is the same as the a_child'th node whose depth is one more than the depth
   * of the a_index'th node.
   *
   * @param a_index the node number of the parent
   * @param a_child the child number (starting from 0) of the parent
   * @return the node number of the child, or -1 if not found
   *
   * @author Klaus Meffert
   * @since 3.01
   */
  public int getChild(int a_index, int a_child) {
    /**@todo speedup*/
    int len = getFunctions().length;
    for (int i = a_index + 1; i < len; i++) {
      if (m_depth[i] <= m_depth[a_index]) {
        return -1;
      }
      if (m_depth[i] == m_depth[a_index] + 1) {
        if (--a_child < 0) {
          return i;
        }
      }
    }
    throw new RuntimeException("Bad child "
                               + a_child
                               + " of node with index = "
                               + a_index);
  }

  public int getChild(CommandGene a_node, int a_child) {
    /**@todo speedup*/
    int len = getFunctions().length;
    int index = -1;
    for (int i = 0; i < len; i++) {
      if (m_genes[i] == a_node) {
        index = i;
        break;
      }
    }
    if (index == -1) {
      return -2;
    }
    for (int i = index + 1; i < len; i++) {
      if (m_depth[i] <= m_depth[index]) {
        return -1;
      }
      if (m_depth[i] == m_depth[index] + 1) {
        if (--a_child < 0) {
          return i;
        }
      }
    }
    throw new RuntimeException("Bad child " + a_child +
                               " of node with index = " + index);
  }

  public CommandGene[] getFunctionSet() {
    return m_functionSet;
  }

  public void setFunctionSet(CommandGene[] a_functionSet) {
    m_functionSet = a_functionSet;
  }

  public CommandGene[] getFunctions() {
    return m_genes;
  }

  public void setFunctions(CommandGene[] a_functions)
      throws InvalidConfigurationException {
    m_genes = a_functions;
  }

  /**
   * Gets the number of nodes in the branch starting at the a_index'th node.
   *
   * @param a_index the index of the node at which to start counting
   * @return the number of nodes in the branch starting at the a_index'th node
   *
   * @author Klaus Meffert
   * @since 3.0
   */
  public int getSize(int a_index) {
    int i;
    // Get the node at which the depth is <= depth[n].
    // -----------------------------------------------
    for (i = a_index + 1; i < m_genes.length && m_genes[i] != null; i++) {
      if (m_depth[i] <= m_depth[a_index]) {
        break;
      }
    }
    return i - a_index;
  }

  /**
   * Gets the depth of the branch starting at the a_index'th node.
   *
   * @param a_index the index of the node at which to check the depth
   * @return the depth of the branch starting at the a_index'th node
   *
   * @author Klaus Meffert
   * @since 3.0
   */
  public int getDepth(int a_index) {
    int maxdepth = m_depth[a_index];
    for (int i = a_index + 1; i < m_genes.length && m_genes[i] != null; i++) {
      if (m_depth[i] <= m_depth[a_index]) {
        break;
      }
      if (m_depth[i] > maxdepth) {
        maxdepth = m_depth[i];
      }
    }
    return maxdepth - m_depth[a_index];
  }

  /**
   * Gets the node which is the parent of the given node in this chromosome. If
   * the child is at depth d then the parent is the first function at depth d-1
   * when iterating backwards through the function list starting from the child.
   *
   * @param a_child the child node
   * @return the parent node, or null if the child is the root node
   *
   * @author Klaus Meffert
   * @since 3.0
   */
  public int getParentNode(int a_child) {
    if (a_child >= m_genes.length || m_genes[a_child] == null) {
      return -1;
    }
    for (int i = a_child - 1; i >= 0; i--) {
      if (m_depth[i] == m_depth[a_child] - 1) {
        return i;
      }
    }
    return -1;
  }

  /**
   * Checks whether a node with a given type is contained in the program.
   *
   * @param a_type the type to look for
   * @param a_exactMatch true: look for exactly the given type: false: also look
   * for sub types
   * @return true specific node found
   *
   * @author Klaus Meffert
   * @since 3.2.1
   */
  public CommandGene getNode(Class a_type, boolean a_exactMatch) {
    return getNode(a_type, a_exactMatch, 0);
  }

  public CommandGene getNode(Class a_type, boolean a_exactMatch,
                             int a_startIndex) {
    int size = m_genes.length;
    for (int i = a_startIndex; i < size; i++) {
      if (m_genes[i] != null) {
        if (a_exactMatch) {
          if (m_genes[i].getClass() == a_type) {
            m_genes[i].nodeIndex = i; /**@todo work over*/
            return m_genes[i];
          }
        }
        else {
          if (a_type.isAssignableFrom(m_genes[i].getClass())) {
            m_genes[i].nodeIndex = i; /**@todo work over*/
            return m_genes[i];
          }
        }
      }
      else {
        break;
      }
    }
    return null;
  }

  /**
   * Executes this node as a boolean.
   *
   * @param args the arguments for execution
   * @return the boolean return value of this node
   * @throws UnsupportedOperationException if the type of this node is not
   * boolean
   *
   * @author Klaus Meffert
   * @since 3.0
   */
  public boolean execute_boolean(Object[] args) {
    boolean rtn = m_genes[0].execute_boolean(this, 0, args);
    cleanup();
    return rtn;
  }

  /**
   * Executes this node as a boolean.
   *
   * @param n the index of the parent node
   * @param child the child number of the node to execute
   * @param args the arguments for execution
   * @return the boolean return value of this node
   * @throws UnsupportedOperationException if the type of this node is not
   * boolean
   *
   * @author Klaus Meffert
   * @since 3.0
   */
  public boolean execute_boolean(int n, int child, Object[] args) {
    if (child == 0) {
      return m_genes[n + 1].execute_boolean(this, n + 1, args);
    }
    int other = getChild(n, child);
    return m_genes[other].execute_boolean(this, other, args);
  }

  /**
   * Executes this node, returning nothing.
   *
   * @param args the arguments for execution
   * @throws UnsupportedOperationException if the type of this node is not void
   *
   * @author Klaus Meffert
   * @since 3.0
   */
  public void execute_void(Object[] args) {
    m_genes[0].execute_void(this, 0, args);
    cleanup();
  }

  public void execute_void(int n, int child, Object[] args) {
    if (child == 0) {
      m_genes[n + 1].execute_void(this, n + 1, args);
    }
    else {
      int other = getChild(n, child);
      m_genes[other].execute_void(this, other, args);
    }
  }

  /**
   * Executes this node as an integer.
   *
   * @param args the arguments for execution
   * @return the integer return value of this node
   * @throws UnsupportedOperationException if the type of this node is not
   * integer
   *
   * @author Klaus Meffert
   * @since 3.0
   */
  public int execute_int(Object[] args) {
    int rtn = m_genes[0].execute_int(this, 0, args);
    cleanup();
    return rtn;
  }

  public int execute_int(int n, int child, Object[] args) {
    if (child == 0) {
      return m_genes[n + 1].execute_int(this, n + 1, args);
    }
    else {
      int other = getChild(n, child);
      return m_genes[other].execute_int(this, other, args);
    }
  }

  /**
   * Executes this node as a long.
   *
   * @param args the arguments for execution
   * @return the long return value of this node
   * @throws UnsupportedOperationException if the type of this node is not long
   *
   * @author Klaus Meffert
   * @since 3.0
   */
  public long execute_long(Object[] args) {
    long rtn = m_genes[0].execute_long(this, 0, args);
    cleanup();
    return rtn;
  }

  public long execute_long(int n, int child, Object[] args) {
    if (child == 0) {
      return m_genes[n + 1].execute_long(this, n + 1, args);
    }
    int other = getChild(n, child);
    return m_genes[other].execute_long(this, other, args);
  }

  /**
   * Executes this node as a float.
   *
   * @param args the arguments for execution
   * @return the float return value of this node
   * @throws UnsupportedOperationException if the type of this node is not float
   *
   * @author Klaus Meffert
   * @since 3.0
   */
  public float execute_float(Object[] args) {
    float rtn = m_genes[0].execute_float(this, 0, args);
    cleanup();
    return rtn;
  }

  public float execute_float(int n, int child, Object[] args) {
    if (child == 0) {
      return m_genes[n + 1].execute_float(this, n + 1, args);
    }
    int other = getChild(n, child);
    return m_genes[other].execute_float(this, other, args);
  }

  /**
   * Executes this node as a double.
   *
   * @param args the arguments for execution
   * @return the double return value of this node
   * @throws UnsupportedOperationException if this node's type is not double
   *
   * @author Klaus Meffert
   * @since 3.0
   */
  public double execute_double(Object[] args) {
    double rtn = m_genes[0].execute_double(this, 0, args);
    cleanup();
    return rtn;
  }

  public double execute_double(int n, int child, Object[] args) {
    if (child == 0) {
      return m_genes[n + 1].execute_double(this, n + 1, args);
    }
    int other = getChild(n, child);
    return m_genes[other].execute_double(this, other, args);
  }

  /**
   * Executes this node as an object.
   *
   * @param args the arguments for execution
   * @return the object return value of this node
   * @throws UnsupportedOperationException if the type of this node is not
   * of type Object
   *
   * @author Klaus Meffert
   * @since 3.0
   */
  public Object execute_object(Object[] args) {
    Object rtn = m_genes[0].execute_object(this, 0, args);
    cleanup();
    return rtn;
  }

  public Object execute_object(int n, int child, Object[] args) {
    if (child == 0) {
      return m_genes[n + 1].execute_object(this, n + 1, args);
    }
    int other = getChild(n, child);
    return m_genes[other].execute_object(this, other, args);
  }

  /**
   * Executes this node without knowing its return type.
   *
   * @param args the arguments for execution
   * @return the Object which wraps the return value of this node, or null
   * if the return type is null or unknown
   *
   * @author Klaus Meffert
   * @since 3.0
   */
  public Object execute(Object[] args) {
    return m_genes[0].execute_object(this, 0, args);
  }

  public Object execute(int n, int child, Object[] args) {
    return execute_object(n, child, args);
  }

  public void setGene(int index, CommandGene a_gene) {
    if (a_gene == null) {
      throw new IllegalArgumentException("Gene must not be null!");
    }
    m_genes[index] = a_gene;
  }

  public Class[] getArgTypes() {
    return argTypes;
  }

  public int getArity() {
    return argTypes.length;
  }

  /**
   * @return number of functions and terminals present
   *
   * @author Klaus Meffert
   * @since 3.0
   */
  public int size() {
    int i = 0;
    while (i < m_genes.length && m_genes[i] != null) {
      i++;
    }
    return i;
  }

  /**
   * Compares the given chromosome to this chromosome. This chromosome is
   * considered to be "less than" the given chromosome if it has a fewer
   * number of genes or if any of its gene values (alleles) are less than
   * their corresponding gene values in the other chromosome.
   *
   * @param a_other the chromosome against which to compare this chromosome
   * @return a negative number if this chromosome is "less than" the given
   * chromosome, zero if they are equal to each other, and a positive number if
   * this chromosome is "greater than" the given chromosome
   *
   * @author Klaus Meffert
   * @since 3.0
   */
  public int compareTo(Object a_other) {
    // First, if the other Chromosome is null, then this chromosome is
    // automatically the "greater" Chromosome.
    // ---------------------------------------------------------------
    if (a_other == null) {
      return 1;
    }
    int size = size();
    ProgramChromosome otherChromosome = (ProgramChromosome) a_other;
    CommandGene[] otherGenes = otherChromosome.m_genes;
    // If the other Chromosome doesn't have the same number of genes,
    // then whichever has more is the "greater" Chromosome.
    // --------------------------------------------------------------
    if (otherChromosome.size() != size) {
      return size() - otherChromosome.size();
    }
    // Next, compare the gene values (alleles) for differences. If
    // one of the genes is not equal, then we return the result of its
    // comparison.
    // ---------------------------------------------------------------
    for (int i = 0; i < size; i++) {
      int comparison = m_genes[i].compareTo(otherGenes[i]);
      if (comparison != 0) {
        return comparison;
      }
    }
    /**@todo compare m_functionSet*/
    if (isCompareApplicationData()) {
      // Compare application data.
      // -------------------------
      if (getApplicationData() == null) {
        if (otherChromosome.getApplicationData() != null) {
          return -1;
        }
      }
      else if (otherChromosome.getApplicationData() == null) {
        return 1;
      }
      else {
        if (getApplicationData() instanceof Comparable) {
          try {
            return ( (Comparable) getApplicationData()).compareTo(
                otherChromosome.getApplicationData());
          } catch (ClassCastException cex) {
            return -1;
          }
        }
        else {
          return getApplicationData().getClass().getName().compareTo(
              otherChromosome.getApplicationData().getClass().getName());
        }
      }
    }
    // Everything is equal. Return zero.
    // ---------------------------------
    return 0;
  }

  /**
   * Compares this chromosome against the specified object.
   *
   * @param a_other the object to compare against
   * @return true: if the objects are the same, false otherwise
   *
   * @author Klaus Meffert
   * @since 3.0
   */
  public boolean equals(Object a_other) {
    try {
      return compareTo(a_other) == 0;
    } catch (ClassCastException cex) {
      return false;
    }
  }

  /**
   * Should we also consider the application data when comparing? Default is
   * "false" as "true" means a Chromosome is losing its identity when
   * application data is set differently!
   *
   * @param a_doCompare true: consider application data in method compareTo
   *
   * @author Klaus Meffert
   * @since 3.0
   */
  public void setCompareApplicationData(boolean a_doCompare) {
    m_compareAppData = a_doCompare;
  }

  /*
   * @return should we also consider the application data when comparing?
   *
   * @author Klaus Meffert
   * @since 3.0
   */
  public boolean isCompareApplicationData() {
    return m_compareAppData;
  }

  /**
   * Retrieves the application-specific data that is attached to this
   * Chromosome. Attaching application-specific data may be useful for
   * some applications when it comes time to evaluate this Chromosome
   * in the fitness function. JGAP ignores this data functionally.
   *
   * @return the application-specific data previously attached to this
   * Chromosome, or null if there is no data attached
   *
   * @author Klaus Meffert
   * @since 3.0
   */
  public Object getApplicationData() {
    return m_applicationData;
  }

  /**
   * Returns the Gene at the given index (locus) within the Chromosome. The
   * first gene is at index zero and the last gene is at the index equal to
   * the size of this Chromosome - 1.
   *
   * @param a_locus index of the gene value to be returned
   * @return Gene at the given index
   *
   * @author Klaus Meffert
   * @since 3.0
   */
  public synchronized CommandGene getGene(int a_locus) {
    return m_genes[a_locus];
  }

  private CommandGene[] remove(CommandGene[] a_functionSet, CommandGene node) {
    int size = a_functionSet.length;
    for (int i = 0; i < size; i++) {
      if (a_functionSet[i] == node) {
        // Remove found element
        CommandGene[] result = new CommandGene[size - 1];
        if (i > 0) {
          System.arraycopy(a_functionSet, 0, result, 0, i);
        }
        if (size - i > 1) {
          System.arraycopy(a_functionSet, i + 1, result, i, size - i - 1);
        }
        return result;
      }
    }
    return a_functionSet;
  }

  protected String encode(String a_string) {
    return StringKit.encode(a_string);
  }

  protected String decode(String a_string) {
    return StringKit.decode(a_string);
  }

  /**
   * @return the persistent representation of the chromosome, including all
   * genes
   *
   * @author Klaus Meffert
   * @since 3.3
   */
  public String getPersistentRepresentation() {
    StringBuffer b = new StringBuffer();
    // Store current state.
    // --------------------
//    String state = PERSISTENT_FIELD_DELIMITER+m_functionSet

    // Process the contained genes.
    // ----------------------------
    for (CommandGene gene : m_genes) {
      if (gene == null) {
        break;
      }
      b.append(GENE_DELIMITER_HEADING);
      b.append(encode(
          gene.getClass().getName() +
          GENE_DELIMITER +
          gene.getPersistentRepresentation()));
      b.append(GENE_DELIMITER_CLOSING);
    }
    return b.toString();
  }

  /**
   *
   * @param a_representation String
   * @throws UnsupportedRepresentationException
   *
   * @author Klaus Meffert
   * @since 3.3
   */
  public void setValueFromPersistentRepresentation(final String
      a_representation)
      throws UnsupportedRepresentationException {
    if (a_representation != null) {
      try {
        List r = split(a_representation);
        Iterator iter = r.iterator();
        StringTokenizer st;
        String clas;
        String representation;
        String g;
        CommandGene gene;
        List genes = new Vector();
        while (iter.hasNext()) {
          g = decode( (String) iter.next());
          st = new StringTokenizer(g, GENE_DELIMITER);
          if (st.countTokens() != 2)
            throw new UnsupportedRepresentationException("In " + g + ", " +
                "expecting two tokens, separated by " + GENE_DELIMITER);
          clas = st.nextToken();
          representation = st.nextToken();
          gene = createGene(clas, representation);
          genes.add(gene);
        }
        m_genes = (CommandGene[]) genes.toArray(new CommandGene[0]);
      } catch (Exception ex) {
        throw new UnsupportedRepresentationException(ex.toString());
      }
    }
  }

  /**
   * Creates a new instance of gene.
   *
   * @param a_geneClassName name of the gene class
   * @param a_persistentRepresentation persistent representation of the gene to
   * create (could be obtained via getPersistentRepresentation)
   *
   * @return newly created gene
   * @throws Exception
   *
   * @author Klaus Meffert
   * @since 3.3
   */
  protected CommandGene createGene(String a_geneClassName,
                                   String a_persistentRepresentation)
      throws Exception {
    Class geneClass = Class.forName(a_geneClassName);
    Constructor constr = geneClass.getConstructor(new Class[] {GPConfiguration.class});
    CommandGene gene = (CommandGene) constr.newInstance(new Object[] {
        getGPConfiguration()});
    gene.setValueFromPersistentRepresentation(a_persistentRepresentation);
    return gene;
  }

  /**
   * Splits a_string into individual gene representations.
   *
   * @param a_string the string to split
   * @return the elements of the returned array are the persistent
   * representation strings of the gene's components
   * @throws UnsupportedRepresentationException
   *
   * @author Klaus Meffert
   * @since 3.3
   */
  protected static final List split(String a_string)
      throws UnsupportedRepresentationException {
    List a = Collections.synchronizedList(new ArrayList());
    StringTokenizer st = new StringTokenizer
        (a_string, GENE_DELIMITER_HEADING + GENE_DELIMITER_CLOSING, true);
    while (st.hasMoreTokens()) {
      if (!st.nextToken().equals(GENE_DELIMITER_HEADING)) {
        throw new UnsupportedRepresentationException(a_string +
            " no opening tag");
      }
      String n = st.nextToken();
      if (n.equals(GENE_DELIMITER_CLOSING)) {
        // Empty token.
        a.add("");
      }
      else {
        a.add(n);
        if (!st.nextToken().equals(GENE_DELIMITER_CLOSING)) {
          throw new UnsupportedRepresentationException
              (a_string + " no closing tag");
        }
      }
    }
    return a;
  }
}

 

Ads help maintain this website.