/**
  * Obtain a new Node from a type that matches the given string. It will try to correctly detect
  * RealVar and constant nodes. The Node is cloned and initialized, so it can be used separatedly
  *
  * @param name The type of the requested Node. It must coincide with one of the class names of
  *     available Nodes, unless it represents a variable or constant terminal
  * @param currentGlobalDepth The depth of the requested Node in the Tree
  */
 public Node newNode(String name, int currentGlobalDepth) {
   System.out.println(name);
   Node outNode = null;
   if (name.startsWith("X")) {
     outNode = new RealVar(Integer.parseInt(name.substring("X".length(), name.length())));
   } else if (name.startsWith("angle")) {
     outNode = new Angle(Integer.parseInt(name.substring("angle".length(), name.length())));
   } else if (name.substring(0, 1).matches("\\d")) {
     outNode = new RealConstant(Double.parseDouble(name));
   } else if (name.equals("true")) {
     outNode = new LogicConstant(1);
   } else if (name.equals("false")) {
     outNode = new LogicConstant(0);
   } else {
     List<Node> all = new ArrayList<Node>();
     for (int i = 0; i < nodeSets.length; i++) {
       all.addAll(nodeSets[i].getAll());
     }
     for (Node n : all) {
       if (name.equals(n.name())) {
         try {
           outNode = (Node) n.clone();
         } catch (CloneNotSupportedException e) {
           Logger.log(e);
         }
         break;
       }
     }
   }
   outNode.setCurrentDepth(currentGlobalDepth);
   return outNode;
 }
 /**
  * Obtain a new Node randomly chosen from the given list. The Node is cloned and initialized, so
  * it can be used separatedly
  *
  * @param l The list of Nodes from which to choose
  * @param currentGlobalDepth The depth of the requested Node in the Tree
  */
 public Node newRandomNode(List<Node> l, int currentGlobalDepth) {
   int which = Common.globalRandom.nextInt(l.size());
   Node outNode = null;
   try {
     outNode = (Node) l.get(which).clone();
   }
   /* This should never happen, as nodes do support cloning, so it's ok
    * to catch this exception here
    */
   catch (CloneNotSupportedException e) {
     Logger.log(e);
   }
   outNode.init(config, problemData);
   outNode.setCurrentDepth(currentGlobalDepth);
   return outNode;
 }
  /**
   * Read Node config from file config.nodeConfigFileName, including available nodes and their
   * possible connections
   */
  public NodeFactory(Config config, ProblemData problemData) {
    this.config = config;
    this.problemData = problemData;
    try {
      FileInputStream in = new FileInputStream(config.nodeConfigFileName);
      Properties props = new Properties();
      props.load(in);
      in.close();

      String separator = "\\s*,\\s*";

      String tmp = props.getProperty("sets");
      String[] sets = tmp.split(separator);

      nodeSets = new NodeSet[sets.length];

      int i, j, k;
      for (i = 0; i < sets.length; i++) {
        nodeSets[i] = new NodeSet(sets[i]);
        String[] nodes;
        tmp = props.getProperty(sets[i] + "Functions");
        if (tmp == null)
          Logger.log(
              "Warning: when reading "
                  + config.nodeConfigFileName
                  + ", no functions found for set "
                  + sets[i]);
        else {
          nodes = tmp.split(separator);
          for (j = 0; j < nodes.length; j++) {
            Class cl = Class.forName("gpalta.nodes." + nodes[j]);
            java.lang.reflect.Constructor co = cl.getConstructor();
            nodeSets[i].addFunction((Node) co.newInstance());
          }
        }
        tmp = props.getProperty(sets[i] + "Terminals");
        if (tmp == null)
          Logger.log(
              "Warning: when reading "
                  + config.nodeConfigFileName
                  + ", no terminals found for set "
                  + sets[i]);
        else {
          nodes = tmp.split(separator);
          for (j = 0; j < nodes.length; j++) {
            Class cl = Class.forName("gpalta.nodes." + nodes[j]);
            if (nodes[j].contains("Var")) {
              for (k = 0; k < problemData.nVars; k++) {
                java.lang.reflect.Constructor[] co = cl.getConstructors();
                nodeSets[i].addTerminal((Node) co[0].newInstance(k + 1));
              }
            } else if (nodes[j].contains("Angle")) {
              for (k = 0; k < problemData.nVars - 1; k++) {
                java.lang.reflect.Constructor[] co = cl.getConstructors();
                nodeSets[i].addTerminal((Node) co[0].newInstance(k + 1));
              }
            } else {
              java.lang.reflect.Constructor co = cl.getConstructor();
              nodeSets[i].addTerminal((Node) co.newInstance());
            }
          }
        }
      }

      for (i = 0; i < nodeSets.length; i++) {
        for (Node n : nodeSets[i].getAll()) {
          n.setType(nodeSets[i]);
        }
      }

      for (i = 0; i < nodeSets.length; i++) {
        for (Node n : nodeSets[i].getAll()) {
          tmp = props.getProperty("kids" + n.getClass().getSimpleName());
          if (tmp != null) {
            String[] kids = tmp.split(separator);
            if (kids.length != n.nKids()) {
              Logger.log(
                  "Error reading "
                      + config.nodeConfigFileName
                      + ": must specify "
                      + n.nKids()
                      + " kids for "
                      + n.getClass().getSimpleName());
            }
            for (j = 0; j < n.nKids(); j++) {
              for (k = 0; k < nodeSets.length; k++) {
                if (nodeSets[k].getName().equals(kids[j])) {
                  n.setTypeOfKids(j, nodeSets[k]);
                  break;
                }
              }
              if (k == nodeSets.length)
                Logger.log(
                    "Error reading "
                        + config.nodeConfigFileName
                        + ": Setting kids for "
                        + n.getClass().getSimpleName()
                        + ", "
                        + kids[j]
                        + " doesn't match any set name");
            }
          } else for (j = 0; j < n.nKids(); j++) n.setTypeOfKids(j, nodeSets[i]);
        }
      }
      tmp = props.getProperty("treeRoot");
      if (tmp == null) {
        Logger.log(
            "Error reading " + config.nodeConfigFileName + ": property \"treeRoot\" not present");
      }
      for (i = 0; i < nodeSets.length; i++) {
        if (nodeSets[i].getName().equals(tmp)) {
          treeRoot = nodeSets[i];
          break;
        }
      }
      if (i == nodeSets.length)
        Logger.log(
            "Error reading "
                + config.nodeConfigFileName
                + ": Setting treeRoot, "
                + tmp
                + " doesn't match any set name");
    } catch (IOException e) {
      Logger.log("Error reading " + config.nodeConfigFileName + ":");
      Logger.log(e);
    } catch (ClassNotFoundException e) {
      Logger.log("Error reading " + config.nodeConfigFileName + ":");
      Logger.log(e);
    } catch (NoSuchMethodException e) {
      Logger.log("Error reading " + config.nodeConfigFileName + ":");
      Logger.log(e);
    } catch (InstantiationException e) {
      Logger.log("Error reading " + config.nodeConfigFileName + ":");
      Logger.log(e);
    } catch (IllegalAccessException e) {
      Logger.log("Error reading " + config.nodeConfigFileName + ":");
      Logger.log(e);
    } catch (InvocationTargetException e) {
      Logger.log("Error reading " + config.nodeConfigFileName + ":");
      Logger.log(e);
    }
  }