All Downloads are FREE. Search and download functionalities are using the official Maven repository.

weka.gui.beans.FlowByExpression Maven / Gradle / Ivy

Go to download

The Waikato Environment for Knowledge Analysis (WEKA), a machine learning workbench. This version represents the developer version, the "bleeding edge" of development, you could say. New functionality gets added to this version.

There is a newer version: 3.9.6
Show newest version
/*
 *   This program 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.
 *
 *   This program 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 this program.  If not, see .
 */

/*
 *    FlowByExpression.java
 *    Copyright (C) 2011-2012 University of Waikato, Hamilton, New Zealand
 *
 */

package weka.gui.beans;

import java.awt.BorderLayout;
import java.beans.EventSetDescriptor;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

import javax.swing.JPanel;
import javax.swing.tree.DefaultMutableTreeNode;

import weka.core.Attribute;
import weka.core.Environment;
import weka.core.EnvironmentHandler;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.Utils;
import weka.gui.Logger;

/**
 * A bean that splits incoming instances (or instance streams) according to the
 * evaluation of a logical expression. The expression can test the values of one
 * or more incoming attributes. The test can involve constants or comparing one
 * attribute's values to another. Inequalities along with string operations such
 * as contains, starts-with, ends-with and regular expressions may be used as
 * operators.
 * 
 * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
 * @version $Revision: 14911 $
 */
@KFStep(category = "Flow", toolTipText = "Route instances according to a boolean expression")
public class FlowByExpression extends JPanel implements BeanCommon, Visible,
  Serializable, InstanceListener, TrainingSetListener, TestSetListener,
  DataSourceListener, EventConstraints, EnvironmentHandler, DataSource,
  StructureProducer {

  /** Added ID to avoid warning */
  private static final long serialVersionUID = 2492050246494259885L;

  /**
   * Abstract base class for parts of a boolean expression.
   * 
   * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
   */
  protected static abstract class ExpressionNode implements Serializable {

    /** For serialization */
    private static final long serialVersionUID = -8427857202322768762L;

    /** boolean operator for combining with result so far */
    protected boolean m_isAnOr;

    /** is this node negated? */
    protected boolean m_isNegated;

    /** Environment variables */
    protected transient Environment m_env;

    /** Whether to show the combination operator in the textual representation */
    protected boolean m_showAndOr = true;

    /**
     * Set whether this node is to be OR'ed to the result so far
     * 
     * @param isOr true if this node is to be OR'd
     */
    public void setIsOr(boolean isOr) {
      m_isAnOr = isOr;
    }

    /**
     * Get whether this node is to be OR'ed
     * 
     * @return true if this node is to be OR'ed with the result so far
     */
    public boolean isOr() {
      return m_isAnOr;
    }

    /**
     * Get whether this node is negated.
     * 
     * @return
     */
    public boolean isNegated() {
      return m_isNegated;
    }

    /**
     * Set whether this node is negated
     * 
     * @param negated true if this node is negated
     */
    public void setNegated(boolean negated) {
      m_isNegated = negated;
    }

    /**
     * Set whether to show the combination operator in the textual description
     * 
     * @param show true if the combination operator is to be shown
     */
    public void setShowAndOr(boolean show) {
      m_showAndOr = show;
    }

    /**
     * Initialize the node
     * 
     * @param structure the structure of the incoming instances
     * @param env Environment variables
     */
    public void init(Instances structure, Environment env) {
      m_env = env;
    }

    /**
     * Evaluate this node and combine with the result so far
     * 
     * @param inst the incoming instance to evalute with
     * @param result the result to combine with
     * @return the result after combining with this node
     */
    public abstract boolean evaluate(Instance inst, boolean result);

    /**
     * Get the internal representation of this node
     * 
     * @param buff the string buffer to append to
     */
    protected abstract void toStringInternal(StringBuffer buff);

    /**
     * Get the display representation of this node
     * 
     * @param buff the string buffer to append to
     */
    public abstract void toStringDisplay(StringBuffer buff);

    /**
     * Parse and initialize from the internal representation
     * 
     * @param expression the expression to parse in internal representation
     * @return the remaining parts of the expression after parsing and removing
     *         the part for this node
     */
    protected abstract String parseFromInternal(String expression);

    /**
     * Get a DefaultMutableTreeNode for this node
     * 
     * @param parent the parent of this node (if any)
     * @return the DefaultMutableTreeNode for this node
     */
    public abstract DefaultMutableTreeNode toJTree(DefaultMutableTreeNode parent);
  }

  /**
   * An expression node that encloses other expression nodes in brackets
   * 
   * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
   */
  protected static class BracketNode extends ExpressionNode implements
    Serializable {

    /** For serialization */
    private static final long serialVersionUID = 8732159083173001115L;

    protected List m_children = new ArrayList();

    @Override
    public void init(Instances structure, Environment env) {
      super.init(structure, env);

      for (ExpressionNode n : m_children) {
        n.init(structure, env);
      }
    }

    @Override
    public boolean evaluate(Instance inst, boolean result) {

      boolean thisNode = true;
      if (m_children.size() > 0) {
        for (ExpressionNode n : m_children) {
          thisNode = n.evaluate(inst, thisNode);
        }
        if (isNegated()) {
          thisNode = !thisNode;
        }
      }

      return (isOr() ? (result || thisNode) : (result && thisNode));
    }

    /**
     * Add a child to this bracket node
     * 
     * @param child the ExpressionNode to add
     */
    public void addChild(ExpressionNode child) {
      m_children.add(child);

      if (m_children.size() > 0) {
        m_children.get(0).setShowAndOr(false);
      }
    }

    /**
     * Remove a child from this bracket node
     * 
     * @param child the ExpressionNode to remove
     */
    public void removeChild(ExpressionNode child) {
      m_children.remove(child);

      if (m_children.size() > 0) {
        m_children.get(0).setShowAndOr(false);
      }
    }

    @Override
    public String toString() {
      // just the representation of this node (suitable for the abbreviated
      // JTree node label

      String result = "( )";
      if (isNegated()) {
        result = "!" + result;
      }

      if (m_showAndOr) {
        if (m_isAnOr) {
          result = "|| " + result;
        } else {
          result = "&& " + result;
        }
      }

      return result;
    }

    @Override
    public DefaultMutableTreeNode toJTree(DefaultMutableTreeNode parent) {

      DefaultMutableTreeNode current = new DefaultMutableTreeNode(this);
      if (parent != null) {
        parent.add(current);
      }

      for (ExpressionNode child : m_children) {
        child.toJTree(current);
      }

      return current;
    }

    private void toString(StringBuffer buff, boolean internal) {
      if (m_children.size() >= 0) {
        if (internal || m_showAndOr) {
          if (m_isAnOr) {
            buff.append("|| ");
          } else {
            buff.append("&& ");
          }
        }

        if (isNegated()) {
          buff.append("!");
        }
        buff.append("(");

        int count = 0;
        for (ExpressionNode child : m_children) {
          if (internal) {
            child.toStringInternal(buff);
          } else {
            child.toStringDisplay(buff);
          }
          count++;
          if (count != m_children.size()) {
            buff.append(" ");
          }
        }
        buff.append(")");
      }
    }

    @Override
    public void toStringDisplay(StringBuffer buff) {
      toString(buff, false);
    }

    @Override
    protected void toStringInternal(StringBuffer buff) {
      toString(buff, true);
    }

    @Override
    protected String parseFromInternal(String expression) {
      if (expression.startsWith("|| ")) {
        m_isAnOr = true;
      }

      if (expression.startsWith("|| ") || expression.startsWith("&& ")) {
        expression = expression.substring(3, expression.length());
      }

      if (expression.charAt(0) == '!') {
        setNegated(true);
        expression = expression.substring(1, expression.length());
      }

      if (expression.charAt(0) != '(') {
        throw new IllegalArgumentException(
          "Malformed expression! Was expecting a \"(\"");
      }

      expression = expression.substring(1, expression.length());

      while (expression.charAt(0) != ')') {
        int offset = 3;

        if (expression.charAt(offset) == '(') {
          ExpressionNode child = new BracketNode();
          expression = child.parseFromInternal(expression);
          m_children.add(child);
        } else {
          // must be an ExpressionClause
          ExpressionNode child = new ExpressionClause();
          expression = child.parseFromInternal(expression);
          m_children.add(child);
        }
      }

      if (m_children.size() > 0) {
        m_children.get(0).setShowAndOr(false);
      }

      return expression;
    }
  }

  protected static class ExpressionClause extends ExpressionNode implements
    Serializable {

    /** For serialization */
    private static final long serialVersionUID = 2754006654981248325L;

    /** The operator for this expression */
    protected ExpressionType m_operator;

    /** The name of the lhs attribute */
    protected String m_lhsAttributeName;

    /** The index of the lhs attribute */
    protected int m_lhsAttIndex = -1;

    /** The rhs operand (constant value or attribute name) */
    protected String m_rhsOperand;

    /** True if the rhs operand is an attribute */
    protected boolean m_rhsIsAttribute;

    /** index of the rhs if it is an attribute */
    protected int m_rhsAttIndex = -1;

    /** The name of the lhs attribute after resolving variables */
    protected String m_resolvedLhsName;

    /** The rhs operand after resolving variables */
    protected String m_resolvedRhsOperand;

    /** the compiled regex pattern (if the operator is REGEX) */
    protected Pattern m_regexPattern;

    /** The rhs operand (if constant and is a number ) */
    protected double m_numericOperand;

    public static enum ExpressionType {
      EQUALS(" = ") {
        @Override
        boolean evaluate(Instance inst, int lhsAttIndex, String rhsOperand,
          double numericOperand, Pattern regexPattern, boolean rhsIsAttribute,
          int rhsAttIndex) {

          if (rhsIsAttribute) {
            if (inst.isMissing(lhsAttIndex) && inst.isMissing(rhsAttIndex)) {
              return true;
            }
            if (inst.isMissing(lhsAttIndex) || inst.isMissing(rhsAttIndex)) {
              return false;
            }
            return (inst.value(lhsAttIndex) == inst.value(rhsAttIndex));
          }

          if (inst.isMissing(lhsAttIndex)) {
            return false;
          }
          return (inst.value(lhsAttIndex) == numericOperand);
        }
      },
      NOTEQUAL(" != ") {
        @Override
        boolean evaluate(Instance inst, int lhsAttIndex, String rhsOperand,
          double numericOperand, Pattern regexPattern, boolean rhsIsAttribute,
          int rhsAttIndex) {

          return !EQUALS.evaluate(inst, lhsAttIndex, rhsOperand,
            numericOperand, regexPattern, rhsIsAttribute, rhsAttIndex);
        }
      },
      LESSTHAN(" < ") {
        @Override
        boolean evaluate(Instance inst, int lhsAttIndex, String rhsOperand,
          double numericOperand, Pattern regexPattern, boolean rhsIsAttribute,
          int rhsAttIndex) {

          if (rhsIsAttribute) {
            if (inst.isMissing(lhsAttIndex) || inst.isMissing(rhsAttIndex)) {
              return false;
            }
            return (inst.value(lhsAttIndex) < inst.value(rhsAttIndex));
          }

          if (inst.isMissing(lhsAttIndex)) {
            return false;
          }
          return (inst.value(lhsAttIndex) < numericOperand);
        }
      },
      LESSTHANEQUAL(" <= ") {
        @Override
        boolean evaluate(Instance inst, int lhsAttIndex, String rhsOperand,
          double numericOperand, Pattern regexPattern, boolean rhsIsAttribute,
          int rhsAttIndex) {

          if (rhsIsAttribute) {
            if (inst.isMissing(lhsAttIndex) || inst.isMissing(rhsAttIndex)) {
              return false;
            }
            return (inst.value(lhsAttIndex) <= inst.value(rhsAttIndex));
          }

          if (inst.isMissing(lhsAttIndex)) {
            return false;
          }
          return (inst.value(lhsAttIndex) <= numericOperand);
        }
      },
      GREATERTHAN(" > ") {
        @Override
        boolean evaluate(Instance inst, int lhsAttIndex, String rhsOperand,
          double numericOperand, Pattern regexPattern, boolean rhsIsAttribute,
          int rhsAttIndex) {

          return !LESSTHANEQUAL.evaluate(inst, lhsAttIndex, rhsOperand,
            numericOperand, regexPattern, rhsIsAttribute, rhsAttIndex);
        }
      },
      GREATERTHANEQUAL(" >= ") {
        @Override
        boolean evaluate(Instance inst, int lhsAttIndex, String rhsOperand,
          double numericOperand, Pattern regexPattern, boolean rhsIsAttribute,
          int rhsAttIndex) {

          return !LESSTHAN.evaluate(inst, lhsAttIndex, rhsOperand,
            numericOperand, regexPattern, rhsIsAttribute, rhsAttIndex);
        }
      },
      ISMISSING(" isMissing ") {
        @Override
        boolean evaluate(Instance inst, int lhsAttIndex, String rhsOperand,
          double numericOperand, Pattern regexPattern, boolean rhsIsAttribute,
          int rhsAttIndex) {

          return (inst.isMissing(lhsAttIndex));
        }
      },
      CONTAINS(" contains ") {
        @Override
        boolean evaluate(Instance inst, int lhsAttIndex, String rhsOperand,
          double numericOperand, Pattern regexPattern, boolean rhsIsAttribute,
          int rhsAttIndex) {

          if (inst.isMissing(lhsAttIndex)) {
            return false;
          }

          String lhsString = "";
          try {
            lhsString = inst.stringValue(lhsAttIndex);
          } catch (IllegalArgumentException ex) {
            return false;
          }

          if (rhsIsAttribute) {
            if (inst.isMissing(rhsAttIndex)) {
              return false;
            }

            try {
              String rhsString = inst.stringValue(rhsAttIndex);

              return lhsString.contains(rhsString);
            } catch (IllegalArgumentException ex) {
              return false;
            }
          }

          return lhsString.contains(rhsOperand);
        }
      },
      STARTSWITH(" startsWith ") {
        @Override
        boolean evaluate(Instance inst, int lhsAttIndex, String rhsOperand,
          double numericOperand, Pattern regexPattern, boolean rhsIsAttribute,
          int rhsAttIndex) {

          if (inst.isMissing(lhsAttIndex)) {
            return false;
          }

          String lhsString = "";
          try {
            lhsString = inst.stringValue(lhsAttIndex);
          } catch (IllegalArgumentException ex) {
            return false;
          }

          if (rhsIsAttribute) {
            if (inst.isMissing(rhsAttIndex)) {
              return false;
            }

            try {
              String rhsString = inst.stringValue(rhsAttIndex);

              return lhsString.startsWith(rhsString);
            } catch (IllegalArgumentException ex) {
              return false;
            }
          }

          return lhsString.startsWith(rhsOperand);
        }
      },
      ENDSWITH(" endsWith ") {
        @Override
        boolean evaluate(Instance inst, int lhsAttIndex, String rhsOperand,
          double numericOperand, Pattern regexPattern, boolean rhsIsAttribute,
          int rhsAttIndex) {

          if (inst.isMissing(lhsAttIndex)) {
            return false;
          }

          String lhsString = "";
          try {
            lhsString = inst.stringValue(lhsAttIndex);
          } catch (IllegalArgumentException ex) {
            return false;
          }

          if (rhsIsAttribute) {
            if (inst.isMissing(rhsAttIndex)) {
              return false;
            }

            try {
              String rhsString = inst.stringValue(rhsAttIndex);

              return lhsString.endsWith(rhsString);
            } catch (IllegalArgumentException ex) {
              return false;
            }
          }

          return lhsString.endsWith(rhsOperand);
        }
      },
      REGEX(" regex ") {
        @Override
        boolean evaluate(Instance inst, int lhsAttIndex, String rhsOperand,
          double numericOperand, Pattern regexPattern, boolean rhsIsAttribute,
          int rhsAttIndex) {

          if (inst.isMissing(lhsAttIndex)) {
            return false;
          }

          if (regexPattern == null) {
            return false;
          }

          String lhsString = "";
          try {
            lhsString = inst.stringValue(lhsAttIndex);
          } catch (IllegalArgumentException ex) {
            return false;
          }

          return regexPattern.matcher(lhsString).matches();
        }
      };

      abstract boolean evaluate(Instance inst, int lhsAttIndex,
        String rhsOperand, double numericOperand, Pattern regexPattern,
        boolean rhsIsAttribute, int rhsAttIndex);

      private final String m_stringVal;

      ExpressionType(String name) {
        m_stringVal = name;
      }

      @Override
      public String toString() {
        return m_stringVal;
      }
    }

    public ExpressionClause() {
    }

    /**
     * Construct a new ExpressionClause
     * 
     * @param operator the operator to use
     * @param lhsAttributeName the lhs attribute name
     * @param rhsOperand the rhs operand
     * @param rhsIsAttribute true if the rhs operand is an attribute
     * @param isAnOr true if the result of this expression is to be OR'ed with
     *          the result so far
     */
    public ExpressionClause(ExpressionType operator, String lhsAttributeName,
      String rhsOperand, boolean rhsIsAttribute, boolean isAnOr) {
      m_operator = operator;
      m_lhsAttributeName = lhsAttributeName;
      m_rhsOperand = rhsOperand;
      m_rhsIsAttribute = rhsIsAttribute;
      m_isAnOr = isAnOr;
    }

    @Override
    public void init(Instances structure, Environment env) {
      super.init(structure, env);

      m_resolvedLhsName = m_lhsAttributeName;
      m_resolvedRhsOperand = m_rhsOperand;
      try {
        m_resolvedLhsName = m_env.substitute(m_resolvedLhsName);
        m_resolvedRhsOperand = m_env.substitute(m_resolvedRhsOperand);
      } catch (Exception ex) {
      }

      Attribute lhs = null;
      // try as an index or "special" label first
      if (m_resolvedLhsName.toLowerCase().startsWith("/first")) {
        lhs = structure.attribute(0);
      } else if (m_resolvedLhsName.toLowerCase().startsWith("/last")) {
        lhs = structure.attribute(structure.numAttributes() - 1);
      } else {
        // try as an index
        try {
          int indx = Integer.parseInt(m_resolvedLhsName);
          indx--;
          lhs = structure.attribute(indx);
        } catch (NumberFormatException ex) {
        }
      }

      if (lhs == null) {
        lhs = structure.attribute(m_resolvedLhsName);
      }
      if (lhs == null) {
        throw new IllegalArgumentException("Data does not contain attribute "
          + "\"" + m_resolvedLhsName + "\"");
      }
      m_lhsAttIndex = lhs.index();

      if (m_rhsIsAttribute) {
        Attribute rhs = null;

        // try as an index or "special" label first
        if (m_resolvedRhsOperand.toLowerCase().equals("/first")) {
          rhs = structure.attribute(0);
        } else if (m_resolvedRhsOperand.toLowerCase().equals("/last")) {
          rhs = structure.attribute(structure.numAttributes() - 1);
        } else {
          // try as an index
          try {
            int indx = Integer.parseInt(m_resolvedRhsOperand);
            indx--;
            rhs = structure.attribute(indx);
          } catch (NumberFormatException ex) {
          }
        }

        if (rhs == null) {
          rhs = structure.attribute(m_resolvedRhsOperand);
        }
        if (rhs == null) {
          throw new IllegalArgumentException("Data does not contain attribute "
            + "\"" + m_resolvedRhsOperand + "\"");
        }
        m_rhsAttIndex = rhs.index();
      } else if (m_operator != ExpressionType.CONTAINS
        && m_operator != ExpressionType.STARTSWITH
        && m_operator != ExpressionType.ENDSWITH
        && m_operator != ExpressionType.REGEX
        && m_operator != ExpressionType.ISMISSING) {
        // make sure the operand is parseable as a number (unless missing has
        // been specified - equals only)
        if (lhs.isNominal()) {
          m_numericOperand = lhs.indexOfValue(m_resolvedRhsOperand);

          if (m_numericOperand < 0) {
            throw new IllegalArgumentException("Unknown nominal value '"
              + m_resolvedRhsOperand + "' for attribute '" + lhs.name() + "'");
          }
        } else {
          try {
            m_numericOperand = Double.parseDouble(m_resolvedRhsOperand);
          } catch (NumberFormatException e) {
            throw new IllegalArgumentException("\"" + m_resolvedRhsOperand
              + "\" is not parseable as a number!");
          }
        }
      }

      if (m_operator == ExpressionType.REGEX) {
        m_regexPattern = Pattern.compile(m_resolvedRhsOperand);
      }
    }

    @Override
    public boolean evaluate(Instance inst, boolean result) {

      boolean thisNode = m_operator.evaluate(inst, m_lhsAttIndex, m_rhsOperand,
        m_numericOperand, m_regexPattern, m_rhsIsAttribute, m_rhsAttIndex);

      if (isNegated()) {
        thisNode = !thisNode;
      }

      return (isOr() ? (result || thisNode) : (result && thisNode));
    }

    @Override
    public String toString() {
      StringBuffer buff = new StringBuffer();
      toStringDisplay(buff);

      return buff.toString();
    }

    @Override
    public void toStringDisplay(StringBuffer buff) {
      toString(buff, false);
    }

    @Override
    protected void toStringInternal(StringBuffer buff) {
      toString(buff, true);
    }

    @Override
    public DefaultMutableTreeNode toJTree(DefaultMutableTreeNode parent) {
      parent.add(new DefaultMutableTreeNode(this));

      return parent;
    }

    private void toString(StringBuffer buff, boolean internal) {
      if (internal || m_showAndOr) {
        if (m_isAnOr) {
          buff.append("|| ");
        } else {
          buff.append("&& ");
        }
      }
      if (isNegated()) {
        buff.append("!");
      }

      buff.append("[");

      buff.append(m_lhsAttributeName);
      if (internal) {
        buff.append("@EC@" + m_operator.toString());
      } else {
        buff.append(" " + m_operator.toString());
      }

      if (m_operator != ExpressionType.ISMISSING) {
        // @@ indicates that the rhs is an attribute
        if (internal) {
          buff.append("@EC@" + (m_rhsIsAttribute ? "@@" : "") + m_rhsOperand);
        } else {
          buff.append(" " + (m_rhsIsAttribute ? "ATT: " : "") + m_rhsOperand);
        }
      } else {
        if (internal) {
          buff.append("@EC@");
        } else {
          buff.append(" ");
        }
      }

      buff.append("]");
    }

    @Override
    protected String parseFromInternal(String expression) {

      // first the boolean operator for this clause
      if (expression.startsWith("|| ")) {
        m_isAnOr = true;
      }

      if (expression.startsWith("|| ") || expression.startsWith("&& ")) {
        // strip the boolean operator
        expression = expression.substring(3, expression.length());
      }

      if (expression.charAt(0) == '!') {
        setNegated(true);
        expression = expression.substring(1, expression.length());
      }

      if (expression.charAt(0) != '[') {
        throw new IllegalArgumentException(
          "Was expecting a \"[\" to start this ExpressionClause!");
      }
      expression = expression.substring(1, expression.length());
      m_lhsAttributeName = expression.substring(0, expression.indexOf("@EC@"));
      expression = expression.substring(expression.indexOf("@EC@") + 4,
        expression.length());
      String oppName = expression.substring(0, expression.indexOf("@EC@"));
      expression = expression.substring(expression.indexOf("@EC@") + 4,
        expression.length());
      for (ExpressionType n : ExpressionType.values()) {
        if (n.toString().equals(oppName)) {
          m_operator = n;
          break;
        }
      }

      if (expression.startsWith("@@")) {
        // rhs is an attribute
        expression = expression.substring(2, expression.length()); // strip off
        // "@@"
        m_rhsIsAttribute = true;
      }
      m_rhsOperand = expression.substring(0, expression.indexOf(']'));

      expression = expression.substring(expression.indexOf(']') + 1,
        expression.length()); // remove "]"
      if (expression.charAt(0) == ' ') {
        expression = expression.substring(1, expression.length());
      }

      return expression;
    }
  }

  /** The root of the expression tree */
  protected ExpressionNode m_root;

  /** The expression tree to use in internal textual format */
  protected String m_expressionString = "";

  /**
   * The one or two downstream steps - one for instances that match the
   * expression and the other for instances that don't
   */
  protected Object[] m_downstream;

  /**
   * Name of the step to receive instances that evaluate to true via the
   * expression
   */
  protected String m_customNameOfTrueStep = "";

  /**
   * Name of the step to receive instances that evaluate to false via the
   * expression
   */
  protected String m_customNameOfFalseStep = "";

  protected int m_indexOfTrueStep;
  protected int m_indexOfFalseStep;

  /** Logging */
  protected transient Logger m_log;

  /** Busy indicator */
  protected transient boolean m_busy;

  /** Component talking to us */
  protected Object m_listenee;

  /** The type of the incoming connection */
  protected String m_connectionType;

  /** format of instances for current incoming connection (if any) */
  private Instances m_connectedFormat;

  protected transient Environment m_env;

  /** Instance event to use */
  protected InstanceEvent m_ie = new InstanceEvent(this);

  /**
   * Default visual filters
   */
  protected BeanVisual m_visual = new BeanVisual("FlowByExpression",
    BeanVisual.ICON_PATH + "FlowByExpression.png", BeanVisual.ICON_PATH
      + "FlowByExpression.png");

  /**
   * Constructor
   */
  public FlowByExpression() {
    setLayout(new BorderLayout());
    add(m_visual, BorderLayout.CENTER);

    m_env = Environment.getSystemWide();
  }

  public String globalInfo() {
    return "Splits incoming instances (or instance stream) according to the "
      + "evaluation of a logical expression. The expression can test the values of "
      + "one or more incoming attributes. The test can involve constants or comparing "
      + "one attribute's values to another. Inequalities along with string operations "
      + "such as contains, starts-with, ends-with and regular expressions may be used "
      + "as operators. \"True\" instances can be sent to one downstream step and "
      + "\"False\" instances sent to another.";
  }

  /**
   * Set the expression (in internal format)
   * 
   * @param expressionString the expression to use (in internal format)
   */
  public void setExpressionString(String expressionString) {
    m_expressionString = expressionString;
  }

  /**
   * Get the current expression (in internal format)
   * 
   * @return the current expression (in internal format)
   */
  public String getExpressionString() {
    return m_expressionString;
  }

  /**
   * Set the name of the connected step to send "true" instances to
   * 
   * @param trueStep the name of the step to send "true" instances to
   */
  public void setTrueStepName(String trueStep) {
    m_customNameOfTrueStep = trueStep;
  }

  /**
   * Get the name of the connected step to send "true" instances to
   * 
   * @return the name of the step to send "true" instances to
   */
  public String getTrueStepName() {
    return m_customNameOfTrueStep;
  }

  /**
   * Set the name of the connected step to send "false" instances to
   * 
   * @param falseStep the name of the step to send "false" instances to
   */
  public void setFalseStepName(String falseStep) {
    m_customNameOfFalseStep = falseStep;
  }

  /**
   * Get the name of the connected step to send "false" instances to
   * 
   * @return the name of the step to send "false" instances to
   */
  public String getFalseStepName() {
    return m_customNameOfFalseStep;
  }

  @Override
  public void addDataSourceListener(DataSourceListener dsl) {
    if (m_downstream == null) {
      m_downstream = new Object[2];
    }

    if (m_downstream[0] == null && m_downstream[1] == null) {
      m_downstream[0] = dsl;
      return;
    }

    if (m_downstream[0] == null || m_downstream[1] == null) {
      if (m_downstream[0] == null
        && m_downstream[1] instanceof DataSourceListener) {
        m_downstream[0] = dsl;
        return;
      } else if (m_downstream[1] == null
        && m_downstream[0] instanceof DataSourceListener) {
        m_downstream[1] = dsl;
        return;
      }
    }
  }

  protected void remove(Object dsl) {
    if (m_downstream[0] == dsl) {
      m_downstream[0] = null;
      return;
    }

    if (m_downstream[1] == dsl) {
      m_downstream[1] = null;
    }
  }

  @Override
  public void removeDataSourceListener(DataSourceListener dsl) {
    if (m_downstream == null) {
      m_downstream = new Object[2];
    }

    remove(dsl);
  }

  @Override
  public void addInstanceListener(InstanceListener dsl) {
    if (m_downstream == null) {
      m_downstream = new Object[2];
    }

    if (m_downstream[0] == null && m_downstream[1] == null) {
      m_downstream[0] = dsl;
      return;
    }

    if (m_downstream[0] == null || m_downstream[1] == null) {
      if (m_downstream[0] == null
        && m_downstream[1] instanceof InstanceListener) {
        m_downstream[0] = dsl;
        return;
      } else if (m_downstream[1] == null
        && m_downstream[0] instanceof InstanceListener) {
        m_downstream[1] = dsl;
        return;
      }
    }
  }

  @Override
  public void removeInstanceListener(InstanceListener dsl) {
    if (m_downstream == null) {
      m_downstream = new Object[2];
    }

    remove(dsl);

  }

  @Override
  public void setEnvironment(Environment env) {
    m_env = env;
  }

  @Override
  public boolean eventGeneratable(String eventName) {
    if (m_listenee == null) {
      return false;
    }

    if (m_listenee instanceof EventConstraints) {

      if (eventName.equals("dataSet")) {
        return ((EventConstraints) m_listenee).eventGeneratable(eventName)
          || ((EventConstraints) m_listenee).eventGeneratable("trainingSet")
          || ((EventConstraints) m_listenee).eventGeneratable("testSet");
      }

      return ((EventConstraints) m_listenee).eventGeneratable(eventName);
    }

    return true;
  }

  /**
   * Initialize with respect to the incoming instance format
   * 
   * @param data the incoming instance format
   */
  protected void init(Instances data) {
    m_indexOfTrueStep = -1;
    m_indexOfFalseStep = -1;
    m_connectedFormat = data;

    if (m_downstream == null) {
      return;
    }

    if (m_downstream[0] != null
      && ((BeanCommon) m_downstream[0]).getCustomName().equals(
        m_customNameOfTrueStep)) {
      m_indexOfTrueStep = 0;
    }
    if (m_downstream[0] != null
      && ((BeanCommon) m_downstream[0]).getCustomName().equals(
        m_customNameOfFalseStep)) {
      m_indexOfFalseStep = 0;
    }

    if (m_downstream[1] != null
      && ((BeanCommon) m_downstream[1]).getCustomName().equals(
        m_customNameOfTrueStep)) {
      m_indexOfTrueStep = 1;
    }
    if (m_downstream[1] != null
      && ((BeanCommon) m_downstream[1]).getCustomName().equals(
        m_customNameOfFalseStep)) {
      m_indexOfFalseStep = 1;
    }

    if (m_env == null) {
      m_env = Environment.getSystemWide();
    }

    try {
      if (m_expressionString != null && m_expressionString.length() > 0) {
        m_root = new BracketNode();
        m_root.parseFromInternal(m_expressionString);
      }
      if (m_root != null) {
        m_root.init(data, m_env);
      }
    } catch (Exception ex) {
      ex.printStackTrace();
      stop();
      m_busy = false;
    }
  }

  @Override
  public void acceptDataSet(DataSetEvent e) {

    m_busy = true;
    if (m_log != null && !e.isStructureOnly()) {
      m_log.statusMessage(statusMessagePrefix() + "Processing batch...");
    }

    init(new Instances(e.getDataSet(), 0));

    if (m_root != null) {
      Instances trueBatch = new Instances(e.getDataSet(), 0);
      Instances falseBatch = new Instances(e.getDataSet(), 0);

      for (int i = 0; i < e.getDataSet().numInstances(); i++) {
        Instance current = e.getDataSet().instance(i);

        boolean result = m_root.evaluate(current, true);

        if (result) {
          if (m_indexOfTrueStep >= 0) {
            trueBatch.add(current);
          }
        } else {
          if (m_indexOfFalseStep >= 0) {
            falseBatch.add(current);
          }
        }
      }

      if (m_indexOfTrueStep >= 0) {
        DataSetEvent d = new DataSetEvent(this, trueBatch);
        ((DataSourceListener) m_downstream[m_indexOfTrueStep]).acceptDataSet(d);
      }

      if (m_indexOfFalseStep >= 0) {
        DataSetEvent d = new DataSetEvent(this, falseBatch);
        ((DataSourceListener) m_downstream[m_indexOfFalseStep])
          .acceptDataSet(d);
      }
    } else {
      if (m_indexOfTrueStep >= 0) {
        DataSetEvent d = new DataSetEvent(this, e.getDataSet());
        ((DataSourceListener) m_downstream[m_indexOfTrueStep]).acceptDataSet(d);
      }
    }

    if (m_log != null && !e.isStructureOnly()) {
      m_log.statusMessage(statusMessagePrefix() + "Finished");
    }

    m_busy = false;
  }

  @Override
  public void acceptTestSet(TestSetEvent e) {
    Instances test = e.getTestSet();
    DataSetEvent d = new DataSetEvent(this, test);
    acceptDataSet(d);
  }

  @Override
  public void acceptTrainingSet(TrainingSetEvent e) {
    Instances train = e.getTrainingSet();
    DataSetEvent d = new DataSetEvent(this, train);
    acceptDataSet(d);
  }

  @Override
  public void acceptInstance(InstanceEvent e) {
    m_busy = true;

    if (e.getStatus() == InstanceEvent.FORMAT_AVAILABLE) {
      Instances structure = e.getStructure();
      init(structure);

      if (m_log != null) {
        m_log.statusMessage(statusMessagePrefix() + "Processing stream...");
      }

      // notify listeners of structure
      m_ie.setStructure(structure);
      if (m_indexOfTrueStep >= 0) {
        ((InstanceListener) m_downstream[m_indexOfTrueStep])
          .acceptInstance(m_ie);
      }
      if (m_indexOfFalseStep >= 0) {
        ((InstanceListener) m_downstream[m_indexOfFalseStep])
          .acceptInstance(m_ie);
      }
    } else {
      Instance inst = e.getInstance();
      m_ie.setStatus(e.getStatus());

      if (inst == null || e.getStatus() == InstanceEvent.BATCH_FINISHED) {
        if (inst != null) {
          // evaluate and notify
          boolean result = true;
          if (m_root != null) {
            result = m_root.evaluate(inst, true);
          }

          if (result) {
            if (m_indexOfTrueStep >= 0) {
              m_ie.setInstance(inst);
              ((InstanceListener) m_downstream[m_indexOfTrueStep])
                .acceptInstance(m_ie);
            }
            if (m_indexOfFalseStep >= 0) {
              m_ie.setInstance(null);
              ((InstanceListener) m_downstream[m_indexOfFalseStep])
                .acceptInstance(m_ie);
            }
          } else {
            if (m_indexOfFalseStep >= 0) {
              m_ie.setInstance(inst);
              ((InstanceListener) m_downstream[m_indexOfFalseStep])
                .acceptInstance(m_ie);
            }
            if (m_indexOfTrueStep >= 0) {
              m_ie.setInstance(null);
              ((InstanceListener) m_downstream[m_indexOfTrueStep])
                .acceptInstance(m_ie);
            }
          }
        } else {
          // notify both of end of stream
          m_ie.setInstance(null);
          if (m_indexOfTrueStep >= 0) {
            ((InstanceListener) m_downstream[m_indexOfTrueStep])
              .acceptInstance(m_ie);
          }
          if (m_indexOfFalseStep >= 0) {
            ((InstanceListener) m_downstream[m_indexOfFalseStep])
              .acceptInstance(m_ie);
          }
        }

        if (m_log != null) {
          m_log.statusMessage(statusMessagePrefix() + "Finished");
        }
      } else {
        boolean result = true;
        if (m_root != null) {
          result = m_root.evaluate(inst, true);
        }
        m_ie.setInstance(inst);
        if (result) {
          if (m_indexOfTrueStep >= 0) {
            ((InstanceListener) m_downstream[m_indexOfTrueStep])
              .acceptInstance(m_ie);
          }
        } else {
          if (m_indexOfFalseStep >= 0) {
            ((InstanceListener) m_downstream[m_indexOfFalseStep])
              .acceptInstance(m_ie);
          }
        }
      }
    }

    m_busy = false;
  }

  @Override
  public void useDefaultVisual() {
    m_visual.loadIcons(BeanVisual.ICON_PATH + "FlowByExpression.png",
      BeanVisual.ICON_PATH + "FlowByExpression.png");
    m_visual.setText("FlowByExpression");
  }

  @Override
  public void setVisual(BeanVisual newVisual) {
    m_visual = newVisual;
  }

  @Override
  public BeanVisual getVisual() {
    return m_visual;
  }

  @Override
  public void setCustomName(String name) {
    m_visual.setText(name);
  }

  @Override
  public String getCustomName() {
    return m_visual.getText();
  }

  @Override
  public void stop() {
    if (m_listenee != null) {
      if (m_listenee instanceof BeanCommon) {
        ((BeanCommon) m_listenee).stop();
      }
    }

    if (m_log != null) {
      m_log.statusMessage(statusMessagePrefix() + "Stopped");
    }

    m_busy = false;
  }

  @Override
  public boolean isBusy() {
    return m_busy;
  }

  @Override
  public void setLog(Logger logger) {
    m_log = logger;
  }

  @Override
  public boolean connectionAllowed(EventSetDescriptor esd) {
    return connectionAllowed(esd.getName());
  }

  @Override
  public boolean connectionAllowed(String eventName) {
    if (m_listenee != null) {
      return false;
    }

    return true;
  }

  @Override
  public void connectionNotification(String eventName, Object source) {
    if (connectionAllowed(eventName)) {
      m_listenee = source;
      m_connectionType = eventName;
    }
  }

  @Override
  public void disconnectionNotification(String eventName, Object source) {
    if (source == m_listenee) {
      m_listenee = null;
    }
  }

  protected String statusMessagePrefix() {
    return getCustomName() + "$" + hashCode() + "|";
  }

  private Instances getUpstreamStructure() {
    if (m_listenee != null && m_listenee instanceof StructureProducer) {
      return ((StructureProducer) m_listenee).getStructure(m_connectionType);
    }
    return null;
  }

  /**
   * Get the structure of the output encapsulated in the named event. If the
   * structure can't be determined in advance of seeing input, or this
   * StructureProducer does not generate the named event, null should be
   * returned.
   * 
   * @param eventName the name of the output event that encapsulates the
   *          requested output.
   * 
   * @return the structure of the output encapsulated in the named event or null
   *         if it can't be determined in advance of seeing input or the named
   *         event is not generated by this StructureProducer.
   */
  @Override
  public Instances getStructure(String eventName) {
    if (!eventName.equals("dataSet") && !eventName.equals("instance")) {
      return null;
    }

    if (eventName.equals("dataSet")
      && (m_downstream == null || m_downstream.length == 0)) {
      return null;
    }

    if (eventName.equals("instance")
      && (m_downstream == null || m_downstream.length == 0)) {
      return null;
    }

    if (m_connectedFormat == null) {
      m_connectedFormat = getUpstreamStructure();
    }

    return m_connectedFormat;
  }

  /**
   * Returns the structure of the incoming instances (if any)
   * 
   * @return an Instances value
   */
  public Instances getConnectedFormat() {
    if (m_connectedFormat == null) {
      m_connectedFormat = getUpstreamStructure();
    }

    return m_connectedFormat;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy