package de.mpicbg.knime.knutils;
import org.knime.core.data.DataTableSpec;
import org.knime.core.node.*;
import org.knime.core.node.defaultnodesettings.SettingsModel;
import org.knime.core.node.port.PortObject;
import org.knime.core.node.port.PortType;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
/**
* A warpper of NodeModel which implements a convenience API to manage node settings.
*
* @author Holger Brandl
*/
public abstract class AbstractNodeModel extends NodeModel {
protected final NodeLogger logger = NodeLogger.getLogger(this.getClass());
// flag is used to test a new implementation of the settings list (as hashmap, to retrieve back settings from the list)
protected final boolean useNewSettingsHashmap;
// old implementation to store setting
private List<SettingsModel> allSettings = new ArrayList<SettingsModel>();
// new implementation to store settings
protected HashMap<String, SettingsModel> modelSettings = new HashMap<String, SettingsModel>();
protected AbstractNodeModel() {
super(1, 1);
this.useNewSettingsHashmap = false;
}
public AbstractNodeModel(int numInputs, int numOutputs) {
super(numInputs, numOutputs);
this.useNewSettingsHashmap = false;
}
public AbstractNodeModel(int numInputs, int numOutputs, boolean useNewSettingsHashmap) {
super(numInputs, numOutputs);
this.useNewSettingsHashmap = useNewSettingsHashmap;
}
public AbstractNodeModel(PortType[] inPorts, PortType[] outPorts) {
super(inPorts, outPorts);
this.useNewSettingsHashmap = false;
}
public AbstractNodeModel(PortType[] inPorts, PortType[] outPorts, boolean useNewSettingsHashmap) {
super(inPorts, outPorts);
this.useNewSettingsHashmap = useNewSettingsHashmap;
}
public static PortType[] createPorts(final int numPorts, final int... optionalPortsIds) {
return createPorts(numPorts, BufferedDataTable.TYPE, BufferedDataTable.class, optionalPortsIds);
}
public static PortType[] createPorts(final int numPorts, PortType portType, Class<? extends PortObject> portDataClass, final int... optionalPortsIds) {
PortType[] portTypes = new PortType[numPorts];
Arrays.fill(portTypes, portType);
if (optionalPortsIds.length > 0) {
for (int portId : optionalPortsIds) {
if ((portId - 1) < numPorts) {
portTypes[portId - 1] = new PortType(portDataClass, true);
}
}
}
return portTypes;
}
/**
* add settings to a list
*
* @param settingModel
* @deprecated
*/
protected void addSetting(SettingsModel settingModel) {
allSettings.add(settingModel);
}
/**
* ================================================================================
* implements an alternative to the array list of model settings
* advantage: settings can be retrieved by name and do not have to be stored again as member of the child class
*
* @param settingName
* @param settingsModel
*/
protected void addModelSetting(String settingName, SettingsModel settingsModel) {
modelSettings.put(settingName, settingsModel);
}
protected SettingsModel getModelSetting(String settingName) {
return modelSettings.get(settingName);
}
/**
* ================================================================================
*/
@Override
protected void reset() {
// TODO Code executed on reset.
// Models build during execute are cleared here.
// Also data handled in load/saveInternals will be erased here.
}
@Override
/** A fake implementation of configure which is supposed to be overriden by extending classes. */
protected DataTableSpec[] configure(final DataTableSpec[] inSpecs)
throws InvalidSettingsException {
// TODO: check if user settings are available, fit to the incoming
// table structure, and the incoming types are feasible for the node
// to execute. If the node can execute in its current state return
// the spec of its output data table(s) (if you can, otherwise an array
// with null elements), or throw an exception with a useful user message
if (getNrOutPorts() == 0)
return new DataTableSpec[0];
else
return inSpecs;
}
@Override
protected void saveSettingsTo(final NodeSettingsWO settings) {
if (!useNewSettingsHashmap) {
for (SettingsModel uiProperty : allSettings) {
uiProperty.saveSettingsTo(settings);
}
} else {
for (SettingsModel uiProperty : modelSettings.values()) {
uiProperty.saveSettingsTo(settings);
}
}
}
@Override
protected void loadValidatedSettingsFrom(final NodeSettingsRO settings)
throws InvalidSettingsException {
boolean debug = true;
if(debug) nodeSettingsToLog(this.getClass());
if (!useNewSettingsHashmap) {
for (SettingsModel uiProperty : allSettings) {
try {
uiProperty.loadSettingsFrom(settings);
} catch (InvalidSettingsException ise) {
String message = "Could not load property '" + uiProperty.toString() + "'of node " + this.getClass().getSimpleName();
setWarningMessage(message);
logger.warn(message + ". \nPlease re-execute the node to get rid of this problem!");
}
}
} else {
for (SettingsModel uiProperty : modelSettings.values()) {
try {
uiProperty.loadSettingsFrom(settings);
} catch (InvalidSettingsException ise) {
String message = "Could not load property '" + uiProperty.toString() + "'of node " + this.getClass().getSimpleName();
setWarningMessage(message);
logger.warn(message + ". \nPlease re-execute the node to get rid of this problem!");
}
}
}
}
@Override
protected void validateSettings(final NodeSettingsRO settings)
throws InvalidSettingsException {
for (SettingsModel uiProperty : allSettings) {
try {
uiProperty.validateSettings(settings);
} catch (InvalidSettingsException ise) {
// logger.warn("Could not validate property '" + uiProperty.toString() + "' of node " + this.getClass().getSimpleName());
}
}
}
@Override
protected void loadInternals(final File internDir,
final ExecutionMonitor exec) throws IOException,
CanceledExecutionException {
// System.err.println("t");
// TODO load internal data.
// Everything handed to output ports is loaded automatically (data
// returned by the execute method, models loaded in loadModelContent,
// and user settings set through loadSettingsFrom - is all taken care
// of). Load here only the other internals that need to be restored
// (e.g. data used by the views).
}
@Override
protected void saveInternals(final File internDir,
final ExecutionMonitor exec) throws IOException,
CanceledExecutionException {
// System.err.println("t");
// TODO save internal models.
// Everything written to output ports is saved automatically (data
// returned by the execute method, models saved in the saveModelContent,
// and user settings saved through saveSettingsTo - is all taken care
// of). Save here only the other internals that need to be preserved
// (e.g. data used by the views).
}
public String getFlowVariable(String flowVarName) {
try {
return peekFlowVariableString(flowVarName);
} catch (Throwable t) {
// don't do anything
}
try {
return "" + peekFlowVariableInt(flowVarName);
} catch (Throwable t) {
// don't do anything
}
try {
return "" + peekFlowVariableDouble(flowVarName);
} catch (Throwable t) {
// don't do anything
}
// we have not found a flow-variable for the given name
return null;
}
/**
* debug method to push all model settings (name+type) to the log
*
* @param nodeClass
*/
public void nodeSettingsToLog(Class<? extends AbstractNodeModel> nodeClass) {
StringBuilder logString = new StringBuilder();
if(this.useNewSettingsHashmap) {
logString.append(nodeClass.toString() + " SETTINGS:\n");
for(String setting : modelSettings.keySet()) {
SettingsModel model = modelSettings.get(setting);
logString.append("\t" + setting + " (" + model.getClass().getName() + ")\n");
}
} else
logString.append(nodeClass.getName() + " does not yet use the hashmap for settings.");
logger.info(logString);
}
}