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

weka.knowledgeflow.StepManagerImpl 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 .
 */

/*
 *    StepManagerImpl.java
 *    Copyright (C) 2015 University of Waikato, Hamilton, New Zealand
 *
 */

package weka.knowledgeflow;

import weka.core.Environment;
import weka.core.Instances;
import weka.core.OptionHandler;
import weka.core.SerializationHelper;
import weka.core.SerializedObject;
import weka.core.Settings;
import weka.core.Utils;
import weka.core.WekaException;
import weka.gui.Logger;
import weka.gui.beans.StreamThroughput;
import weka.gui.knowledgeflow.StepVisual;
import weka.knowledgeflow.steps.KFStep;
import weka.knowledgeflow.steps.Step;
import weka.knowledgeflow.steps.WekaAlgorithmWrapper;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * Concrete implementation of the StepManager interface. Has a number of
 * methods, beyond those aimed at Step implementations, that are useful for
 * applications that manipulate Steps and their connections.
 *
 * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
 * @version $Revision: $
 */
public class StepManagerImpl implements StepManager {

  /** The step being managed by this step manager */
  protected Step m_managedStep;

  /**
   * True if the runtime environment has requested that the managed step stop
   * processing
   */
  protected boolean m_stopRequested;

  /** True if, at the current time, the managed step is busy with processing */
  protected boolean m_stepIsBusy;

  /** True if the step is finished with processing (as far as it can tell) */
  protected boolean m_stepIsFinished;

  /**
   * Set and get arbitrary properties relating to this step/step manager. E.g. a
   * plugin execution environment might allow a step to be marked as execute
   * remotely or locally
   */
  protected Map m_stepProperties =
    new HashMap();

  /**
   * Holds the name of the class of the editor for the managed step. If
   * null/empty then the environment will dynamically generate an editor using
   * the GeneicObjectEditor
   */
  protected String m_managedStepEditor;

  /** Map of incoming connections, keyed by connection name */
  protected Map> m_connectedByTypeIncoming =
    new LinkedHashMap>();

  /** Map of outgoing connections, keyed by connection name */
  protected Map> m_connectedByTypeOutgoing =
    new LinkedHashMap>();

  /** Non-step parties interested in outgoing data */
  protected Map> m_outputListeners =
    new LinkedHashMap>();

  /**
   * The StepVisual for this step (non-null if existing in a GUI environment)
   */
  protected StepVisual m_stepVisual;

  /**
   * Temporary holder for the x axis visual location of this step. Populated
   * when a flow is loaded if coordinates are present in the step's JSON
   * representation. If running in an interactive graphical environment then a
   * StepVisual will be created and initialized with these values
   */
  protected int m_x = -1;

  /**
   * Temporary holder for the y axis visual location of this step. Populated
   * when a flow is loaded if coordinates are present in the step's JSON
   * representation. If running in an interactive graphical environment then a
   * StepVisual will be created and initialized with these values
   */
  protected int m_y = -1;

  /**
   * Holds the executing environment. Will be able to query to see if
   * environment is headless. Will be able to request a stop of the entire flow.
   */
  protected BaseExecutionEnvironment m_executionEnvironment;

  /** The log to use */
  protected LogManager m_log;

  /** For measuring performance of instance streams */
  protected transient StreamThroughput m_throughput;

  /**
   * Used when interrogating the managed step for what output connections it can
   * produce at present given the incoming connections to the step. Normally, a
   * step decides what it can produce on the basis of what physical incoming
   * connections are present, regardless of whether the connection may or may
   * not produce data (e.g. if there is a broken link in the graph further
   * upstream). When this flag is true, the routine adjusts the number of
   * incoming connections of a given type to account for broken upstream links.
   * This is primarily used by the graphical UI in order to change connections
   * from red to grey when rendering.
   */
  protected boolean m_adjustForGraphicalRendering;

  /** True if the managed step is a resource (cpu/memory) intensive step */
  protected boolean m_stepIsResourceIntensive;

  /**
   * True if the managed step must run single threaded - i.e. in an executor
   * service with one worker thread
   */
  protected boolean m_stepMustRunSingleThreaded;

  /**
   * Constructor
   *
   * @param step the Step to manage
   */
  public StepManagerImpl(Step step) {
    setManagedStep(step);
  }

  /**
   * Get the name of the Step being managed
   *
   * @return the name of the Step being managed
   */
  @Override
  public String getName() {
    return m_managedStep.getName();
  }

  /**
   * Get the step managed by this manager
   *
   * @return the step managed by this manager
   */
  @Override
  public Step getManagedStep() {
    return m_managedStep;
  }

  /**
   * Set the step managed by this manager
   *
   * @param step the step to manage
   */
  public void setManagedStep(Step step) {
    m_managedStep = step;
    step.setStepManager(this);
    setManagedStepEditorClass(step.getCustomEditorForStep());

    Annotation a = step.getClass().getAnnotation(KFStep.class);
    m_stepIsResourceIntensive = a != null && ((KFStep) a).resourceIntensive();

    a = step.getClass().getAnnotation(SingleThreadedExecution.class);
    m_stepMustRunSingleThreaded = a != null;
  }

  /**
   * Set whether the managed step is resource (cpu/memory) intensive or not
   *
   * @param resourceIntensive true if the managed step is resource intensive
   */
  @Override
  public void setStepIsResourceIntensive(boolean resourceIntensive) {
    m_stepIsResourceIntensive = resourceIntensive;
  }

  /**
   * Get whether the managed step is resource (cpu/memory) intensive or not
   *
   * @return true if the step is resource intensive
   */
  @Override
  public boolean stepIsResourceIntensive() {
    return m_stepIsResourceIntensive;
  }

  /**
   * Set whether the managed step must run single-threaded. I.e. in an executor
   * service with one worker thread, thus effectively preventing more than one
   * copy of the step from executing at any one point in time
   *
   * @param mustRunSingleThreaded true if the managed step must run
   *          single-threaded
   */
  @Override
  public void setStepMustRunSingleThreaded(boolean mustRunSingleThreaded) {
    m_stepMustRunSingleThreaded = mustRunSingleThreaded;
  }

  /**
   * Get whether the managed step must run single-threaded. I.e. in an executor
   * service with one worker thread, thus effectively preventing more than one
   * copy of the step from executing at any one point in time
   *
   * @return true if the managed step must run single-threaded
   */
  @Override
  public boolean getStepMustRunSingleThreaded() {
    return m_stepMustRunSingleThreaded;
  }

  /**
   * Get the step visual in use (if running in a visual environment)
   *
   * @return the step visual in use
   */
  public StepVisual getStepVisual() {
    return m_stepVisual;
  }

  /**
   * Set the step visual to use when running in a graphical environment
   *
   * @param visual the step visual to use
   */
  public void setStepVisual(StepVisual visual) {
    m_stepVisual = visual;
    if (m_x != -1 && m_y != -1) {
      m_stepVisual.setX(m_x);
      m_stepVisual.setY(m_y);
    }
  }

  /**
   * Set a property for this step
   *
   * @param name the name of the property
   * @param value the value of the property
   */
  public void setStepProperty(String name, Object value) {
    m_stepProperties.put(name, value);
  }

  /**
   * Get a named property for this step.
   *
   * @param name the name of the property to get
   * @return the value of the property or null if the property is not set
   */
  public Object getStepProperty(String name) {
    return m_stepProperties.get(name);
  }

  /**
   * Get the fully qualified name of an editor component that can be used to
   * graphically configure the step. If not supplied, then the environment will
   * dynamically generate an editor using the GenericObjectEditor.
   *
   * @return editor the editor class to use
   */
  protected String getManagedStepEditorClass() {
    return m_managedStepEditor;
  }

  /**
   * Set the fully qualified name of an editor component that can be used to
   * graphically configure the step. If not supplied, then the environment will
   * dynamically generate an editor using the GenericObjectEditor.
   *
   * @param editor the editor class to use
   */
  protected void setManagedStepEditorClass(String editor) {
    m_managedStepEditor = editor;
  }

  /**
   * Get the execution environment the managed step is running in
   *
   * @return the execution environment
   */
  @Override
  public ExecutionEnvironment getExecutionEnvironment() {
    return m_executionEnvironment;
  }

  /**
   * Get the current knowledge flow settings
   *
   * @return the current knowledge flow settings
   * @throws IllegalStateException if there is no execution environment
   *           available
   */
  @Override
  public Settings getSettings() {
    if (getExecutionEnvironment() == null) {
      throw new IllegalStateException("There is no execution environment "
        + "available!");
    }
    return getExecutionEnvironment().getSettings();
  }

  /**
   * Set the execution environment the managed step is running in
   *
   * @param env the execution environment
   * @throws WekaException if a problem occurs
   */
  protected void setExecutionEnvironment(ExecutionEnvironment env)
    throws WekaException {

    if (!(env instanceof BaseExecutionEnvironment)) {
      throw new WekaException(
        "Execution environments need to be BaseExecutionEnvironment "
          + "(or subclass thereof)");
    }

    m_executionEnvironment = (BaseExecutionEnvironment) env;
    setLog(m_executionEnvironment.getLog());
    setLoggingLevel(m_executionEnvironment.getLoggingLevel());
  }

  /**
   * Get the logging level in use
   *
   * @return the logging level in use
   */
  @Override
  public LoggingLevel getLoggingLevel() {
    return m_log != null ? m_log.getLoggingLevel() : LoggingLevel.BASIC;
  }

  /**
   * Set the logging level to use
   *
   * @param newLevel the level to use
   */
  public void setLoggingLevel(LoggingLevel newLevel) {
    if (m_log == null) {
      m_log = new LogManager(getManagedStep());
    }
    m_log.setLoggingLevel(newLevel);
  }

  /**
   * Get the log to use
   *
   * @return the log in use or null if no log has been set
   */
  @Override
  public Logger getLog() {
    return m_log != null ? m_log.getLog() : null;
  }

  /**
   * Set the log to use
   *
   * @param log the log to use
   */
  public void setLog(Logger log) {
    m_log = new LogManager(getManagedStep());

    m_log.setLog(log);
  }

  /**
   * Initialize the step being managed
   *
   * @return true if the initialization was successful
   */
  protected boolean initStep() {
    boolean initializedOK = false;
    m_stepIsBusy = false;
    m_stopRequested = false;
    m_stepIsFinished = false;
    try {
      getManagedStep().stepInit();
      // getManagedStep().init();
      initializedOK = true;
    } catch (WekaException ex) {
      logError(ex.getMessage(), ex);
    } catch (Throwable ex) {
      logError(ex.getMessage(), ex);
    }

    m_throughput = null;

    return initializedOK;
  }

  /**
   * Returns true if, at the current time, the managed step is busy with
   * processing
   *
   * @return true if the managed step is busy with processing
   */
  @Override
  public boolean isStepBusy() {
    return m_stepIsBusy;
  }

  /**
   * Return true if a stop has been requested by the runtime environment
   *
   * @return true if a stop has been requested
   */
  @Override
  public boolean isStopRequested() {
    return m_stopRequested;
  }

  /**
   * Return true if the current step is finished.
   *
   * @return true if the current step is finished
   */
  @Override
  public boolean isStepFinished() {
    return m_stepIsFinished;
  }

  /**
   * Set the status of the stop requested flag
   *
   * @param stopRequested true if a stop has been requested
   */
  public void setStopRequested(boolean stopRequested) {
    m_stopRequested = stopRequested;
  }

  /**
   * Started processing. Sets the busy flag to true.
   */
  @Override
  public void processing() {
    m_stepIsBusy = true;
  }

  /**
   * Finished all processing. Sets the busy flag to false and prints a finished
   * message to the status area of the log.
   */
  @Override
  public void finished() {
    m_stepIsBusy = false;
    m_stepIsFinished = true;
    if (!isStopRequested()) {
      statusMessage("Finished.");
    }
  }

  /**
   * Finished processing due to a stop being requested. Sets the busy flag to
   * false.
   */
  @Override
  public void interrupted() {
    m_stepIsBusy = false;
  }

  /**
   * Returns true if this data object marks the end of an incremental stream.
   * Note - does not check that the data object is actually an incremental one
   * of some sort! Just checks to see if the CON_AUX_DATA_INCREMENTAL_STREAM_END
   * flag is set to true or not;
   *
   * @param data the data element to check
   * @return true if the data element is flagged as end of stream
   */
  @Override
  public boolean isStreamFinished(Data data) {
    return data.getPayloadElement(CON_AUX_DATA_INCREMENTAL_STREAM_END, false);
  }

  /**
   * Clients can use this to record a start point for streaming throughput
   * measuring
   */
  @Override
  public void throughputUpdateStart() {
    if (m_throughput == null) {
      m_throughput = new StreamThroughput(stepStatusMessagePrefix());
    }
    processing();
    m_throughput.updateStart();
  }

  /**
   * Clients can use this to record a stop point for streaming throughput
   * measuring
   */
  @Override
  public void throughputUpdateEnd() {
    if (m_throughput != null) {
      m_throughput.updateEnd(m_log.getLog());

      if (isStopRequested()) {
        finished();
      }
    }
  }

  /**
   * Clients can use this to indicate that throughput measuring is finished
   * (i.e. the stream being processed has ended). Final throughput information
   * is printed to the log and status
   *
   * @param data one or more Data events (with appropriate connection type set)
   *          to pass on to downstream connected steps. These are used to carry
   *          any final data and to inform the downstream step(s) that the
   *          stream has ended
   * @throws WekaException if a problem occurs
   */
  @Override
  public void throughputFinished(Data... data) throws WekaException {
    finished();
    if (data.length > 0) {
      for (Data d : data) {
        d.setPayloadElement(CON_AUX_DATA_INCREMENTAL_STREAM_END, true);
      }
      outputData(data);
    }
    if (m_throughput != null) {
      m_throughput.finished(m_log.getLog());
    }
    // not actually interrupted - we just abuse this method in order to
    // set the busy flag to false
    interrupted();
  }

  private void disconnectStep(List connList, Step toDisconnect) {
    Iterator iter = connList.iterator();
    while (iter.hasNext()) {
      StepManagerImpl candidate = (StepManagerImpl) iter.next();
      if (toDisconnect == candidate.getManagedStep()) {
        iter.remove();
        break;
      }
    }
  }

  /**
   * Disconnect the supplied step under the associated connection type from both
   * the incoming and outgoing connections for the step managed by this manager.
   * Does nothing if this step does not have any connections to the supplied
   * step, or does not have connections to the supplied step of the required
   * type.
   *
   * @param toDisconnect the step to disconnect
   * @param connType the connection type to disconnect
   */
  public void disconnectStepWithConnection(Step toDisconnect, String connType) {
    // incoming first
    List connectedWithType =
      m_connectedByTypeIncoming.get(connType);
    if (connectedWithType != null) {
      disconnectStep(connectedWithType, toDisconnect);
      if (connectedWithType.size() == 0) {
        m_connectedByTypeIncoming.remove(connType);
      }
    }

    // outgoing
    connectedWithType = m_connectedByTypeOutgoing.get(connType);
    if (connectedWithType != null) {
      disconnectStep(connectedWithType, toDisconnect);
      if (connectedWithType.size() == 0) {
        m_connectedByTypeOutgoing.remove(connType);
      }
    }
  }

  /**
   * Remove the supplied step from connections (both incoming and outgoing of
   * all types) for the step managed by this manager. Does nothing if the this
   * step does not have any connections to the supplied step
   *
   * @param toDisconnect the step to disconnect
   */
  public void disconnectStep(Step toDisconnect) {

    // incoming first
    List emptyCons = new ArrayList();
    for (Map.Entry> e : m_connectedByTypeIncoming
      .entrySet()) {
      // for (List sList : m_connectedByTypeIncoming.values()) {
      List sList = e.getValue();
      disconnectStep(sList, toDisconnect);
      if (sList.size() == 0) {
        emptyCons.add(e.getKey());
      }
    }
    for (String conn : emptyCons) {
      m_connectedByTypeIncoming.remove(conn);
    }
    emptyCons.clear();

    // outgoing
    for (Map.Entry> e : m_connectedByTypeOutgoing
      .entrySet()) {
      // for (List sList : m_connectedByTypeOutgoing.values()) {
      List sList = e.getValue();
      disconnectStep(sList, toDisconnect);
      if (sList.size() == 0) {
        emptyCons.add(e.getKey());
      }
    }
    for (String conn : emptyCons) {
      m_connectedByTypeOutgoing.remove(conn);
    }
  }

  /**
   * Clear all connections to/from the step managed by this manager. Also makes
   * sure that all directly connected upstream and downstream steps remove their
   * respective outgoing and incoming connections to this step
   */
  public void clearAllConnections() {
    m_connectedByTypeIncoming.clear();
    m_connectedByTypeOutgoing.clear();
  }

  /**
   * Add an incoming connection (comprising of the type of connection and
   * associated step component) to this step of the specified type
   *
   * @param connectionName the name of the type of connection to add
   * @param step the source step component that is connecting with given
   *          connection type
   */
  public void
    addIncomingConnection(String connectionName, StepManagerImpl step) {
    List steps = m_connectedByTypeIncoming.get(connectionName);
    if (steps == null) {
      steps = new ArrayList();
      m_connectedByTypeIncoming.put(connectionName, steps);
    }
    steps.add(step);
  }

  /**
   * Remove an incoming connection to this step of the specified type
   *
   * @param connectionName the name of the type of connection to remove
   * @param step the source step component associated with the given connection
   *          type
   */
  public void removeIncomingConnection(String connectionName,
    StepManagerImpl step) {
    List steps = m_connectedByTypeIncoming.get(connectionName);
    steps.remove(step);
  }

  /**
   * Add an outgoing connection (comprising of the type of connection and
   * associated target step) to this step of the specified type. Connection is
   * only made if the target step will accept the connection type at this time
   *
   * @param connectionName the name of the type of connection to add
   * @param step the target step component that is receiving the given
   *          connection type it can't accept the connection at the present time
   * @return true if the connection was successful
   */
  public boolean addOutgoingConnection(String connectionName,
    StepManagerImpl step) {
    return addOutgoingConnection(connectionName, step, false);
  }

  /**
   * Add an outgoing connection (comprising of the type of connection and
   * associated target step) to this step of the specified type. Connection is
   * only made if the target step will accept the connection type at this time
   *
   * @param connectionName the name of the type of connection to add
   * @param step the target step component that is receiving the given
   *          connection type
   * @param force whether to force the connection, even if the target step says
   *          it can't accept the connection at the present time
   * @return true if the connection was successful
   */
  public boolean addOutgoingConnection(String connectionName,
    StepManagerImpl step, boolean force) {

    // if target step can accept this connection type at this time then
    // create outgoing connection on this step and incoming connection
    // on the target step
    boolean connSuccessful = false;
    List targetCanAccept =
      step.getManagedStep().getIncomingConnectionTypes();
    if (targetCanAccept.contains(connectionName) || force) {
      List steps = m_connectedByTypeOutgoing.get(connectionName);
      if (steps == null) {
        steps = new ArrayList();
        m_connectedByTypeOutgoing.put(connectionName, steps);
      }
      step.addIncomingConnection(connectionName, this);
      steps.add(step);
      connSuccessful = true;
    }
    return connSuccessful;
  }

  /**
   * Remove an outgoing connection from this step of the specified type
   *
   * @param connectionName the name of the type of connection to remove
   * @param step the target step component associated with the given connection
   *          type
   */
  public void removeOutgoingConnection(String connectionName,
    StepManagerImpl step) {
    List steps = m_connectedByTypeOutgoing.get(connectionName);
    steps.remove(step);

    // target step now loses an incoming connection
    step.removeIncomingConnection(connectionName, this);
  }

  /**
   * Get a list of steps providing incoming connections of the specified type
   *
   * @param connectionName the type of connection being received by this step
   * @return a list of connected steps
   */
  @Override
  public List getIncomingConnectedStepsOfConnectionType(
    String connectionName) {
    return m_connectedByTypeIncoming.get(connectionName) != null ? m_connectedByTypeIncoming
      .get(connectionName) : new ArrayList();
  }

  @Override
  public List getOutgoingConnectedStepsOfConnectionType(
    String connectionName) {
    return m_connectedByTypeOutgoing.get(connectionName) != null ? m_connectedByTypeOutgoing
      .get(connectionName) : new ArrayList();
  }

  private StepManager getConnectedStepWithName(String stepName,
    Map> connectedSteps) {
    StepManager result = null;

    for (Map.Entry> e : connectedSteps.entrySet()) {
      List stepsOfConnType = e.getValue();
      for (StepManager s : stepsOfConnType) {
        if (((StepManagerImpl) s).getManagedStep().getName().equals(stepName)) {
          result = s;
          break;
        }
      }
    }

    return result;
  }

  /**
   * Get a named step connected to this step with an incoming connection
   *
   * @param stepName the name of the step to look for
   * @return the connected step
   */
  @Override
  public StepManager getIncomingConnectedStepWithName(String stepName) {
    return getConnectedStepWithName(stepName, m_connectedByTypeIncoming);
  }

  /**
   * Get a named step connected to this step with an outgoing connection
   *
   * @param stepName the name of the step to look for
   * @return the connected step
   */
  @Override
  public StepManager getOutgoingConnectedStepWithName(String stepName) {
    return getConnectedStepWithName(stepName, m_connectedByTypeOutgoing);
  }

  /**
   * Get the map of downstream (outgoing connections) connected steps
   *
   * @return the map of downstream connected steps
   */
  @Override
  public Map> getOutgoingConnections() {
    return m_connectedByTypeOutgoing;
  }

  /**
   * Get the man of upstream (incoming connections) connected steps
   *
   * @return the map of upstream connected steps
   */
  @Override
  public Map> getIncomingConnections() {
    return m_connectedByTypeIncoming;
  }

  /**
   * Register non-step third party to receive data from the managed step for the
   * specified outgoing connection type. Output listeners are not serialized
   * into the JSON flow when flows are saved.
   *
   * @param listener the output listener to register
   * @param outputConnectionName the name of the connection type
   */
  public void addStepOutputListener(StepOutputListener listener,
    String outputConnectionName) {
    List listenersForConnectionType =
      m_outputListeners.get(outputConnectionName);
    if (listenersForConnectionType == null) {
      listenersForConnectionType = new ArrayList();
      m_outputListeners.put(outputConnectionName, listenersForConnectionType);
    }

    if (!listenersForConnectionType.contains(listener)) {
      listenersForConnectionType.add(listener);
    }
  }

  /**
   * De-register non-step third party from receiving data from the managed step
   *
   * @param listener the output listener to de-register
   * @param outputConnectionName the name of the connection type the listener is
   *          registered against
   */
  public void removeStepOutputListener(StepOutputListener listener,
    String outputConnectionName) {
    List listenersForConnectionType =
      m_outputListeners.get(outputConnectionName);

    if (listenersForConnectionType != null) {
      listenersForConnectionType.remove(listener);
    }
  }

  /**
   * Clear all registered StepOutputListeners
   */
  public void clearAllStepOutputListeners() {
    m_outputListeners.clear();
  }

  /**
   * Clear all the StepOutputListeners that are registered to receive the
   * supplied connection type.
   * 
   * @param outputConnectionName type of the connection to clear the listeners
   *          for
   */
  public void clearStepOutputListeners(String outputConnectionName) {
    List listenersForConnectionType =
      m_outputListeners.get(outputConnectionName);

    if (listenersForConnectionType != null) {
      listenersForConnectionType.clear();
    }
  }

  /**
   * Pass any StepOutputListeners the supplied Data object
   * 
   * @param data the data to pass on
   */
  protected void notifyOutputListeners(Data data) throws WekaException {
    List listenersForType =
      m_outputListeners.get(data.getConnectionName());
    if (listenersForType != null) {
      for (StepOutputListener l : listenersForType) {
        if (!l.dataFromStep(data)) {
          logWarning("StepOutputListener '" + l.getClass().getCanonicalName()
            + "' " + "did not process data '" + data.getConnectionName()
            + "' successfully'");
        }
      }
    }
  }

  /**
   * Output a Data object to all downstream connected Steps that are connected
   * with the supplied connection name. Sets the connection type on the supplied
   * Data object to the supplied connection name. Also notifies any registered
   * StepOutputListeners.
   * 
   * @param outgoingConnectionName the type of the outgoing connection to send
   *          data to
   * @param data a single Data object to send
   * @throws WekaException
   */
  @Override
  public void outputData(String outgoingConnectionName, Data data)
    throws WekaException {
    if (!isStopRequested()) {
      data.setConnectionName(outgoingConnectionName);
      data.setSourceStep(m_managedStep);

      List toNotify =
        m_connectedByTypeOutgoing.get(outgoingConnectionName);
      boolean copyPrimaryPayload =
        payloadNotThreadSafe(data) && toNotify.size() > 1;
      boolean first = true;
      if (toNotify != null) {
        for (StepManager s : toNotify) {
          if (!isStopRequested()) {
            if (!first && copyPrimaryPayload) {
              copyPrimaryPayload(data);
            }
            m_executionEnvironment.sendDataToStep((StepManagerImpl) s, data);
            first = false;
          }
        }
      }

      notifyOutputListeners(data);
    }
  }

  /**
   * Output one or more Data objects to all relevant steps. Populates the source
   * in each Data object for the client, HOWEVER, the client must have populated
   * the connection type in each Data object to be output so that the
   * StepManager knows which connected steps to send the data to. Also notifies
   * any registered {@code StepOutputListeners}. Note that the downstream
   * step(s)' processIncoming() method is called in a separate thread for batch
   * connections. Furthermore, if multiple Data objects are supplied via the
   * varargs argument, and a target step will receive more than one of the Data
   * objects, then they will be passed on to the step in question sequentially
   * within the same thread of execution.
   *
   * @param data one or more Data objects to be sent
   * @throws WekaException if a problem occurs
   */
  @Override
  public void outputData(Data... data) throws WekaException {
    if (!isStopRequested()) {
      Map> stepsToSendTo =
        new LinkedHashMap>();

      for (Data d : data) {
        d.setSourceStep(m_managedStep);
        if (d.getConnectionName() == null
          || d.getConnectionName().length() == 0) {
          throw new WekaException("Data does not have a connection name set.");
        }
        List candidates =
          m_connectedByTypeOutgoing.get(d.getConnectionName());
        if (candidates != null) {
          boolean copyPrimaryPayload =
            payloadNotThreadSafe(d) && candidates.size() > 1;
          boolean first = true;
          for (StepManager s : candidates) {
            List toReceive = stepsToSendTo.get(s);
            if (toReceive == null) {
              toReceive = new ArrayList();
              stepsToSendTo.put((StepManagerImpl) s, toReceive);
            }
            if (!first && copyPrimaryPayload) {
              copyPrimaryPayload(d);
            }
            toReceive.add(d);
            first = false;
          }
        }

        notifyOutputListeners(d);
      }

      for (Map.Entry> e : stepsToSendTo.entrySet()) {
        if (!e.getKey().isStopRequested()) {
          m_executionEnvironment.sendDataToStep(e.getKey(), e.getValue()
            .toArray(new Data[e.getValue().size()]));
        }
      }
    }
  }

  /**
   * Outputs the supplied Data object to the named Step. Does nothing if the
   * named step is not connected immediately downstream of this Step. Sets the
   * supplied connection name on the Data object. Also notifies any
   * StepOutputListeners.
   * 
   * @param outgoingConnectionName the name of the outgoing connection
   * @param stepName the name of the step to send the data to
   * @param data the data to send
   * @throws WekaException
   */
  @Override
  public void outputData(String outgoingConnectionName, String stepName,
    Data data) throws WekaException {
    if (!isStopRequested()) {
      data.setConnectionName(outgoingConnectionName);
      data.setSourceStep(m_managedStep);

      List outConnsOfType =
        m_connectedByTypeOutgoing.get(outgoingConnectionName);
      StepManagerImpl namedTarget = null;
      for (StepManager c : outConnsOfType) {
        if (((StepManagerImpl) c).getManagedStep().getName().equals(stepName)) {
          namedTarget = (StepManagerImpl) c;
        }
      }

      if (namedTarget != null && !namedTarget.isStopRequested()) {
        m_executionEnvironment.sendDataToStep(namedTarget, data);
      } else {
        // TODO log an error here and stop?
      }

      notifyOutputListeners(data);
    }
  }

  /**
   * Start the managed step processing
   */
  protected void startStep() {
    try {
      getManagedStep().start();
    } catch (WekaException ex) {
      interrupted();
      logError(ex.getMessage(), ex);
    } catch (Throwable ex) {
      interrupted();
      logError(ex.getMessage(), ex);
    }
  }

  /**
   * Stop the managed step's processing
   */
  protected void stopStep() {
    m_stopRequested = true;
    getManagedStep().stop();
  }

  /**
   * Have the managed step process the supplied data object
   *
   * @param data the data for the managed step to process
   */
  protected void processIncoming(Data data) {
    try {
      getManagedStep().processIncoming(data);
    } catch (WekaException ex) {
      interrupted();
      logError(ex.getMessage(), ex);
    } catch (Throwable e) {
      interrupted();
      logError(e.getMessage(), e);
    }
  }

  /**
   * Used by the rendering routine in LayoutPanel to ensure that connections
   * downstream from a deleted connection get rendered in grey rather than red.
   * 
   * @return a list of outgoing connection types that the managed step can
   *         produce (adjusted to take into account any upstream broken
   *         connections)
   */
  public List getStepOutgoingConnectionTypes() {
    m_adjustForGraphicalRendering = true;
    List results = getManagedStep().getOutgoingConnectionTypes();
    m_adjustForGraphicalRendering = false;
    return results;
  }

  /**
   * Get the number of incoming connections to the managed step
   *
   * @return the number of incoming connections
   */
  @Override
  public int numIncomingConnections() {
    int size = 0;

    for (Map.Entry> e : m_connectedByTypeIncoming
      .entrySet()) {
      if (m_adjustForGraphicalRendering) {
        size += numIncomingConnectionsOfType(e.getKey());
      } else {
        size += e.getValue().size();
      }
    }

    return size;
  }

  /**
   * Get the number of incoming connections to the managed step of a given type
   *
   * @param connectionName the name of the connection type
   * @return the number of incoming connections of this type
   */
  @Override
  public int numIncomingConnectionsOfType(String connectionName) {
    int num = 0;
    List inOfType = m_connectedByTypeIncoming.get(connectionName);
    if (inOfType != null) {
      if (m_adjustForGraphicalRendering) {
        // adjust num incoming connections according
        // to what the upstream steps can produce at present
        for (StepManager connS : inOfType) {
          List generatableOutputCons =
            ((StepManagerImpl) connS).getStepOutgoingConnectionTypes();
          if (generatableOutputCons.contains(connectionName)) {
            num++;
          }
        }
      } else {
        num = inOfType.size();
      }
    }

    return num;
  }

  /**
   * Get the number of outgoing connections from the managed step
   *
   * @return the number of incoming connections
   */
  @Override
  public int numOutgoingConnections() {
    int size = 0;

    for (Map.Entry> e : m_connectedByTypeOutgoing
      .entrySet()) {
      size += e.getValue().size() - (m_adjustForGraphicalRendering ? 1 : 0);
    }
    if (size < 0) {
      size = 0;
    }
    return size;
  }

  /**
   * Get the number of outgoing connections from the managed step of a given
   * type
   *
   * @param connectionName the name of the connection type
   * @return the number of outgoing connections of this type
   */
  @Override
  public int numOutgoingConnectionsOfType(String connectionName) {
    int num = 0;
    List outOfType = m_connectedByTypeOutgoing.get(connectionName);
    if (outOfType != null) {
      num = outOfType.size();
      if (m_adjustForGraphicalRendering) {
        num--;
      }
    }

    return num;
  }

  /**
   * Attempt to get the incoming structure (as a header-only set of instances)
   * for the named incoming connection type. Assumes that there is only one
   * incoming connection of the named type. If there are zero, or more than one,
   * then null is returned
   *
   * @param connectionName the name of the incoming connection to get the
   *          structure for
   * @return the structure as a header-only set of instances or null if there
   *         are zero or more than one upstream connected steps producing the
   *         named connection, or if the upstream step can't tell us the
   *         structure, or if the upstream step can't represent the structure of
   *         the connection type as a set of instances.
   *
   * @throws WekaException if a problem occurs
   */
  @Override
  public Instances getIncomingStructureForConnectionType(String connectionName)
    throws WekaException {
    return getIncomingStructureForConnectionType(connectionName, null);
  }

  /**
   * Attempt to retrieve the structure (as a header-only set of instances) for
   * the named incoming connection type. Assumes that there is only one step
   * connected with the supplied incoming connection type.
   *
   * @param connectionName the type of the incoming connection to get the
   *          structure for
   * @param env the Environment to use
   * @return the structure of the data for the specified incoming connection, or
   *         null if the structure can't be determined (or represented as an
   *         Instances object)
   * @throws WekaException if a problem occurs
   */
  @Override
  public Instances getIncomingStructureForConnectionType(String connectionName,
    Environment env) throws WekaException {

    Environment toUse = env != null ? env : Environment.getSystemWide();
    if (getIncomingConnectedStepsOfConnectionType(connectionName).size() == 1) {
      return ((StepManagerImpl) getIncomingConnectedStepsOfConnectionType(
        connectionName).get(0)).getManagedStep()
        .outputStructureForConnectionType(connectionName, toUse);
    }

    return null;
  }

  /**
   * Attempt to get the incoming structure (as a header-only set of instances)
   * from the given managed step for the given connection type.
   *
   * @param sourceStep the step manager managing the source step
   * @param connectionName the name of the connection to attempt to get the
   *          structure for
   * @return the structure as a header-only set of instances, or null if the
   *         source step can't determine this at present or if it can't be
   *         represented as a set of instances.
   * @throws WekaException if a problem occurs
   */
  @Override
  public Instances getIncomingStructureFromStep(StepManager sourceStep,
    String connectionName) throws WekaException {
    return ((StepManagerImpl) sourceStep).getManagedStep()
      .outputStructureForConnectionType(connectionName);
  }

  /**
   * Log a message at the low logging level
   *
   * @param message the message to log
   */
  @Override
  public void logLow(String message) {
    if (m_log != null) {
      m_log.logLow(message);
    }
  }

  /**
   * Log a message at the basic logging level
   *
   * @param message the message to log
   */
  @Override
  public void logBasic(String message) {
    if (m_log != null) {
      m_log.logBasic(message);
    }
  }

  /**
   * Log a message at the detailed logging level
   *
   * @param message the message to log
   */
  @Override
  public void logDetailed(String message) {
    if (m_log != null) {
      m_log.logDetailed(message);
    }
  }

  /**
   * Log a message at the debugging logging level
   *
   * @param message the message to log
   */
  @Override
  public void logDebug(String message) {
    if (m_log != null) {
      m_log.logDebug(message);
    }
  }

  /**
   * Log a warning message
   *
   * @param message the message to log
   */
  @Override
  public void logWarning(String message) {
    if (m_log != null) {
      m_log.logWarning(message);
      m_log.statusMessage("WARNING: " + message);
    }
  }

  /**
   * Log an error
   *
   * @param message the message to log
   * @param cause the optional Throwable to log
   */
  @Override
  public void logError(String message, Throwable cause) {
    if (m_log != null) {
      m_log.log(message, LoggingLevel.ERROR, cause);
      m_log.statusMessage("ERROR: " + message);
    }
    if (m_executionEnvironment != null) {
      // fatal error - make sure that everything stops.
      m_executionEnvironment.stopProcessing();
    }
  }

  /**
   * Output a status message to the status area of the log
   *
   * @param message the message to output
   */
  @Override
  public void statusMessage(String message) {
    if (m_log != null) {
      m_log.statusMessage(message);
    }
  }

  /**
   * Log a message at the supplied logging level
   *
   * @param message the message to write
   * @param level the level for the message
   */
  @Override
  public void log(String message, LoggingLevel level) {
    if (m_log != null) {
      m_log.log(message, level, null);
    }
  }

  /**
   * Substitute the values of environment variables in the given string
   *
   * @param source the source string to substitute in
   * @return the source string with all known environment variables resolved
   */
  @Override
  public String environmentSubstitute(String source) {
    Environment toUse = Environment.getSystemWide(); // default system-wide

    if (getExecutionEnvironment() != null) {
      toUse = getExecutionEnvironment().getEnvironmentVariables();
    }

    String result = source;

    if (source != null) {
      try {
        result = toUse.substitute(source);
      } catch (Exception ex) {
        // ignore
      }
    }

    return result;
  }

  /**
   * Returns a reference to the step being managed if it has one or more
   * outgoing CON_INFO connections and the managed step is of the supplied class
   *
   * @param stepClass the expected class of the step
   * @return the step being managed if outgoing CON_INFO connections are present
   *         and the step is of the supplied class
   * @throws WekaException if there are no outgoing CON_INFO connections or the
   *           managed step is the wrong type
   */
  @Override
  public Step getInfoStep(Class stepClass) throws WekaException {
    Step info = getInfoStep();
    if (!(info.getClass() == stepClass)) {
      throw new WekaException("The managed step ("
        + info.getClass().getCanonicalName() + ") is not "
        + "not an instance of the required class: "
        + stepClass.getCanonicalName());
    }

    return info;
  }

  /**
   * Returns a reference to the step being managed if it has one or more
   * outgoing CON_INFO connections.
   * 
   * @return the step being managed if outgoing CON_INFO connections are present
   * @throws WekaException if there are no outgoing CON_INFO connections
   */
  @Override
  public Step getInfoStep() throws WekaException {
    if (numOutgoingConnectionsOfType(StepManager.CON_INFO) > 0) {
      return getManagedStep();
    }

    throw new WekaException("There are no outgoing info connections from "
      + "this step!");
  }

  /**
   * Finds a named step in the current flow. Returns null if the named step is
   * not present in the flow
   *
   * @param stepNameToFind the name of the step to find
   * @return the StepManager of the named step, or null if the step does not
   *         exist in the current flow.
   */
  @Override
  public StepManager findStepInFlow(String stepNameToFind) {
    Flow flow = m_executionEnvironment.getFlowExecutor().getFlow();

    return flow.findStep(stepNameToFind);
  }

  /**
   * Gets a prefix for the step managed by this manager. Used to uniquely
   * identify steps in the status area of the log
   *
   * @return a unique prefix for the step managed by this manager
   */
  public String stepStatusMessagePrefix() {
    String prefix =
      (getManagedStep() != null ? getManagedStep().getName() : "Unknown") + "$";

    prefix +=
      (getManagedStep() != null ? getManagedStep().hashCode() : 1) + "|";
    if (getManagedStep() instanceof WekaAlgorithmWrapper) {
      Object wrappedAlgo =
        ((WekaAlgorithmWrapper) getManagedStep()).getWrappedAlgorithm();
      if (wrappedAlgo instanceof OptionHandler) {
        prefix +=
          Utils.joinOptions(((OptionHandler) wrappedAlgo).getOptions()) + "|";
      }
    }

    return prefix;
  }

  /**
   * Returns true if the primary payload of the supplied data object is not
   * thread safe.
   *
   * @param data the data object to check
   * @return true if the primary payload is not thread safe
   */
  protected static boolean payloadNotThreadSafe(Data data) {
    // not specified or default is payload is thread safe
    return data.getPayloadElement(
      StepManager.CON_AUX_DATA_PRIMARY_PAYLOAD_NOT_THREAD_SAFE, false);
  }

  /**
   * Replaces the primary payload of the supplied data object with a serialized
   * copy of itself
   * 
   * @param data the data object for which to make a copy of the primary payload
   * @throws WekaException if a problem occurs
   */
  protected static void copyPrimaryPayload(Data data) throws WekaException {
    Object payload = data.getPrimaryPayload();
    try {
      SerializedObject o = new SerializedObject(payload);
      data.setPayloadElement(data.getConnectionName(), o.getObject());
    } catch (Exception ex) {
      throw new WekaException(ex);
    }
  }

  /**
   * Return true if the supplied connection name is an incremental connection.
   * Several built-in connection types are considered incremental: instance,
   * incremental_classifier, and chart. Clients can indicate that their custom
   * connection/data is incremental by setting the payload element
   * "CON_AUX_DATA_IS_INCREMENTAL" to true in their Data object.
   *
   * @param conn the name of the connection to check
   * @return true if the supplied connection name is an incremental connection
   */
  protected static boolean connectionIsIncremental(Data conn) {
    return conn.getConnectionName().equalsIgnoreCase(StepManager.CON_INSTANCE)
      || conn.getConnectionName().equalsIgnoreCase(
        StepManager.CON_INCREMENTAL_CLASSIFIER)
      || conn.getConnectionName().equalsIgnoreCase(StepManager.CON_CHART)
      || conn.getPayloadElement(StepManager.CON_AUX_DATA_IS_INCREMENTAL, false);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy