/**
   * Parses the provided expression and iterates over the {@link ExampleSet}, interprets attributes
   * as variables, evaluates the function and creates a new attribute with the given name that takes
   * the expression's value. The type of the attribute depends on the expression type and is {@link
   * Ontology#NOMINAL} for strings, {@link Ontology#INTEGER} for integers, {@link Ontology#REAL} for
   * reals, {@link Ontology#DATE_TIME} for Dates, and {@link Ontology#BINOMINAL} with values
   * "true" and "false" for booleans. If the executing operator is defined,
   * there will be a check for stop before the calculation of each example.
   *
   * @param exampleSet the example set to which the generated attribute is added
   * @param name the new attribute name
   * @param expression the expression used to generate attribute values
   * @param parser the expression parser used to parse the expression argument
   * @param resolver the example resolver which is used by the parser to resolve example values
   * @param executingOperator the operator calling this method. <code>null</code> is allowed. If not
   *     null the operator will be used to check for stop
   * @throws ProcessStoppedException in case the process was stopped by the user
   * @throws ExpressionException in case parsing the expression fails
   */
  public static Attribute addAttribute(
      ExampleSet exampleSet,
      String name,
      String expression,
      ExpressionParser parser,
      ExampleResolver resolver,
      Operator executingOperator)
      throws ProcessStoppedException, ExpressionException {

    // parse the expression
    Expression parsedExpression = parser.parse(expression);

    Attribute newAttribute = null;
    // if != null this needs to be overridden
    Attribute existingAttribute = exampleSet.getAttributes().get(name);
    StringBuffer appendix = new StringBuffer();
    String targetName = name;
    if (existingAttribute != null) {
      // If an existing attribute will be overridden, first a unique temporary name has to be
      // generated by appending a random string to the attribute's name until it's a unique
      // attribute name. After the new attribute is build, it's name is set the 'targetName'
      // at the end of this method.
      //
      do {
        appendix.append(RandomGenerator.getGlobalRandomGenerator().nextString(5));
      } while (exampleSet.getAttributes().get(name + appendix.toString()) != null);
      name = name + appendix.toString();
    }

    ExpressionType resultType = parsedExpression.getExpressionType();
    int ontology = resultType.getAttributeType();
    if (ontology == Ontology.BINOMINAL) {
      newAttribute = AttributeFactory.createAttribute(name, Ontology.BINOMINAL);
      newAttribute.getMapping().mapString("false");
      newAttribute.getMapping().mapString("true");
    } else {
      newAttribute = AttributeFactory.createAttribute(name, ontology);
    }

    // set construction description
    newAttribute.setConstruction(expression);

    // add new attribute to table and example set
    exampleSet.getExampleTable().addAttribute(newAttribute);
    exampleSet.getAttributes().addRegular(newAttribute);

    // create attribute of correct type and all values
    for (Example example : exampleSet) {
      if (executingOperator != null) {
        executingOperator.checkForStop();
      }

      // bind example to resolver
      resolver.bind(example);

      // calculate result
      try {
        switch (resultType) {
          case DOUBLE:
          case INTEGER:
            example.setValue(newAttribute, parsedExpression.evaluateNumerical());
            break;
          case DATE:
            Date date = parsedExpression.evaluateDate();
            example.setValue(newAttribute, date == null ? Double.NaN : date.getTime());
            break;
          default:
            example.setValue(newAttribute, parsedExpression.evaluateNominal());
            break;
        }
      } finally {
        // avoid memory leaks
        resolver.unbind();
      }
    }

    // remove existing attribute (if necessary)
    if (existingAttribute != null) {
      AttributeRole oldRole = exampleSet.getAttributes().getRole(existingAttribute);
      exampleSet.getAttributes().remove(existingAttribute);
      newAttribute.setName(targetName);
      // restore role from old attribute to new attribute
      if (oldRole.isSpecial()) {
        exampleSet.getAttributes().setSpecialAttribute(newAttribute, oldRole.getSpecialName());
      }
    }

    // update example resolver after meta data change
    resolver.addAttributeMetaData(
        new AttributeMetaData(exampleSet.getAttributes().getRole(newAttribute), exampleSet, true));

    return newAttribute;
  }