Save and Load Your Internal Representation

Now try the following: execute the node, save, close and re-open the workflow. Your node is still executed but when you open the view no data is available. This is because your internal representation, i.e. the bins, is not saved automatically. To ensure that your internal representation can be stored and loaded, the KNIME framework provides two methods in the NodeModel: loadInternals and saveInternals. The following explains how to implement these methods for our numeric binner node.

  1. Create a ModelContent object. To the ModeContent you can add the raw types of Java, such as int, double, String, etc. Also DataCells and subconfigs can be stored. Subsequently, the NodeSettings can be written to XML with saveToXML and loaded with the static method loadFromXML.
  2. If you have a DataTable, DataArray or similar as your internal model you can use the convenient static method:
    		DataContainer.writeToZip(DataTable yourTable, File zipFile);
    
  3. If neither of the above-mentioned methods fit you can serialize your internal model and write it to file.

Since the serialization of objects is slow and prone to errors, we use the ModelContent approach to store our internal model. Since a numeric bin is not a raw type, it cannot be stored directly by ModelContent. However, each NumericBin consists only of raw types. Moreover, it is enough to store the contained row IDs, because the visual representation can be restored from this information: the size of a drawn bin depends on the width and height of the panel (which is evaluated in the paint method) and the number of contained rows. We provide two methods to let each NumericBin save and load itself from the ModelContent and afterwards store the ModelContents in the main ModelContent in the NodeModel. First of all each NumericBin must provide methods to save itself to and load itself from ModelContent:

	/**
     * Adds the IDs of the contained rows to the settings. This is sufficient in order 
     * to later on restore the visual representation, since that only depends on
     * the dimension of the panel and the number of contained rows per bin.
     * 
     * @param modelContent the model content object to save to.
     */
    public void saveTo(final ModelContentWO modelContent) {
        DataCell[] cellArray = new DataCell[m_containedRowIds.size()]; 
            m_containedRowIds.toArray(cellArray);
        modelContent.addDataCellArray(CFG_KEY_CELLS, cellArray);
    }

    /**
     * Loads the contained row IDs.
     *  
     * @param modelContent
     * @throws InvalidSettingsException
     */
    public void loadFrom(final ModelContentRO modelContent) 
        throws InvalidSettingsException{
        DataCell[] cellArray = modelContent.getDataCellArray(CFG_KEY_CELLS);
        m_containedRowIds.addAll(Arrays.asList(cellArray));
    }

Again, we need an internal key to identify the stored field - in this case the CFG_KEY_CELLS. It only has to be unique in this class, as each object receives its own ModelContent object. Now we can save and load our bins in the NodeModel's saveInternals and loadInternals methods. In the saveInternals we get the directory to store our files. We create a new ModelContent object and for each bin we create a sub model content which is passed to the bin. The bin itself writes the necessary information in the sub model content. Afterwards we create a new file in the given directory, create an output stream and let the main model content write itself to XML.

	/**
     * 
     * @see org.knime.core.node.NodeModel#saveInternals(java.io.File, 
     * org.knime.core.node.ExecutionMonitor)
     */
    protected void saveInternals(final File internDir,
            final ExecutionMonitor exec) throws IOException,
            CanceledExecutionException {
       // create the main model content
       ModelContent modelContent = new ModelContent(INTERNAL_MODEL);
       for (int i = 0; i < m_bins.length; i++) {
           // for each bin create a sub model content
           ModelContentWO subContent = modelContent.addModelContent(
                   NUMERIC_BIN + i);
           // save the bin to the sub model content
           m_bins[i].saveTo(subContent);
       }
       // now all bins are stored to the model content
       // but the model content must be written to XML
       // internDir is the directory for this node
       File file = new File(internDir, FILE_NAME);
       FileOutputStream fos = new FileOutputStream(file);
       modelContent.saveToXML(fos);
    }

The loading of the internal model works accordingly. Create the file and an input stream and let the main model content load from the XML file. Then fetch the sub model content for every bin and let each bin load itself from this sub model content. Add the bin to your field.

	/**
     * 
     * @see org.knime.core.node.NodeModel#loadInternals(java.io.File, 
     * org.knime.core.node.ExecutionMonitor)
     */
    protected void loadInternals(final File internDir,
            final ExecutionMonitor exec) throws IOException,
            CanceledExecutionException {
        m_bins = new NumericBin[m_numberOfBins.getIntValue()];
        File file = new File(internDir, FILE_NAME);
        FileInputStream fis = new FileInputStream(file);
        ModelContentRO modelContent = ModelContent.loadFromXML(fis);
        try {
            for (int i = 0; i < m_numberOfBins.getIntValue(); i++) {
                NumericBin bin = new NumericBin();
                ModelContentRO subModelContent = modelContent
                        .getModelContent(NUMERIC_BIN + i);
                bin.loadFrom(subModelContent);
                m_bins[i] = bin;
            }
        } catch (InvalidSettingsException e) {
            throw new IOException(e.getMessage());
        }
    }

When you now try again to execute, save, close and re-open the workflow, you will see, that the view displays the desired information.