Child pages
  • Integrating a Non-Java Program As An Algorithm

Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Wiki Markup
h3. Introduction

This tutorial describes how to use CIShell's Algorithm Development Wizard to integrate a compiled program into CIShell as a Static Executable algorithm.  Although it uses an example program  written in C++, the steps taken to integrate programs written in other  languages are not significantly different.

h3. Prerequisites

Similarly to [Practical Java Algorithm Developmen|CISHELL:Practical Java Algorithm Development]t, we recommend that you read CIShell Basics and run through the [Hello World Tutorial|CISHELL:Creating a Hello World Java Algorithm] if you are not already familiar with basic CIShell development. You will need to have Eclipse and the CIShell Java Algorithm Wizard installed, as described in[Environment Setup|CISHELL:Setting up the Development Environment]. The algorithm produced in this tutorial requires the [Network Workbench|http://nwb.slis.indiana.edu/download.html] environment to be executed.

If you plan on following this tutorial step by step  and integrating the example C+\+ program, you should have a solid  understanding of the C+\+ language and how to compile for your target  platforms as well.

h3. Section Table of Contents

{toc}



h3. Creating a Non-Java Program

The example non-Java program we will be integrating in this tutorial  is written in C+\+ and will combine three files and output them to a  fourth, new file.  The names of all four files will be specified as  command-line arguments.  These arguments are:
* A user-chosen file of any type.
* A Network Workbench network file selected off of the Data Manager.
* The name of a plain text "platform" file.
* The name of the output file.

The contents of the platform file should  describe the user's platform (i.e. "Win32" or "linux.x86").  Note that  this file is only meant to demonstrate the use of related files in the  wizard (link), and thus it is not meant to be customizable by the  end-user.  As you will see in later sections, that functionality will be  reflected in various aspects of the project setup.

The output file will contain the copied contents of  the end-user-chosen file, the Network Workbench network file the  end-user selected on the Data Manager, and the platform (from the  platform file).  This (file name) will be "hard coded" in the sense that  the end-user will not be able to customize it.

This program will read the contents of the  end-user-chosen file, the NWB file selected off of the Data Manager, and  the platform file.  It will then output the contents of all three files  to a new file.  This program has basic error handling and user  notifications as well.  The source code for this program can be obtained  either from the [Example C+\+ Program Source Code|http://cishell.org/?n=DevGuide.NonJavaAlgorithmTutorial#ExampleProgramSourceCode] section or the [Resources|http://cishell.org/?n=DevGuide.NonJavaAlgorithmTutorial#Resources] section.

Once the program has been written (or copied and  pasted), it must be compiled for all of the platforms you wish to  support.  If you wish to compile the code yourself, we recommend using  the GCC family of compilers for C and C+\+ programs to maintain  cross-platform compatibility.  ([MinGW|http://www.mingw.org/] is a good choice for Windows.)  Otherwise, you may download the download the compiled code in the [Resources|http://cishell.org/?n=DevGuide.NonJavaAlgorithmTutorial#Resources] section.

For the sake of this tutorial, call the program NonJavaAlgorithmWizardExample (source file NonJavaAlgorithmWizardExample.cpp), and compile it for at least one platform.

h3. Creating the Plugin Project

Now that your  program is compiled for all of the platforms you wish to support (or you  have obtained the appropriate resources), it is time to integrate the  necessary files into an Eclipse plugin.  Once you have opened Eclipse,  go to File->New->Project..., and expand the CIShell group of wizards.
Figure 1: *File->New->Project...*
In the _New Project_ window, select _Executable (command line)/non-Java Algorithm Project_, and press *Next >*.
Figure 2: Available CIShell algorithm projects.
The first page of the  wizard is for specifying the name for your Eclipse project.  As a matter  of convention, we usually make the _Project name_, _Bundle Name_, and _Bundle Symbolic Name_ identical for CIShell plugins.  For the sake of this tutorial, enter _example.nonjava.algorithm_ and click *Next >*.
Figure 3: Creating a new project.
The second page is the _Bundle Properties_ page, which lets you enter a _Bundle Name_, _Bundle Symbolic Name_, and _Bundle Version_.  As mentioned before, make the _Bundle Name_ and _Bundle Symbolic Name_ the same as your _Project name_ (which, in our case, is _example.nonjava.algorithm_), leave the _Bundle Version_ at _0.0.1_, and click *Next >*.
Figure 4: Filling in the Bundle Properties.
The third page is the _Executable Files_ page, which is where the _Executable Name_ and all executable files are specified.

The name of your program (minus the file extension if  you use Windows) should be identical across all platforms.  (In our case  for this tutorial, it would be _NonJavaAlgorithmWizardExample{_}_.exe_ in Windows and just _NonJavaAlgorithmWizardExample_ in Linux and MacOSX.)  The '''Executable Nam'e field on this page should also be identical to the name of your program.

If you have any platform-independent files that your program will always need to access, you can use the _Choose Common Files_ file selection widget inside the _Platform: Common to All_ section.
Figure 5: The _Choose Common Files_ file selection widget inside the _Platform: Common to All_ section.
This widget allows you to specify as many common files  as you want.  To add another file selection field, choose a file in the  last file selection field of the widget.  The first file selection  field has a *Clear?* button that will clear the file selection.  All other file selection fields have a *Remove?* button that will remove its respective file selection field.

Each of the other sections specifies a platform and allows you to choose the executable file (via the _Choose Executable File_ file selection widget) and related files (via the _Choose Related Files_ file selection widget) for that platform.  Note that each _Choose Related Files_ widget behaves the same way as the _Choose Common Files_ widget under the _Platform: Common to All_ section.

If you have not already, compile your program for at  least one platform.  For all of the platforms you compiled your program  for, choose the appropriate executable files and create a file named _platform.txt_ that contains the name of the platform.  In our case, we have a _platform.txt_ that contains the text "I'm on Windows\!" and another platform.txt that contains the text "I'm on Linux\!".  Click *Next >.*
Figure 6: The _Executable Name_ has been specified, and the Executable Files and their Related Files have been selected.
The fourth page is the _Project Properties_ page, which allows you to specify various metadata about your  algorithm, including how it is placed in the menu system.  For this  tutorial, choose to have your algorithm on the menu (by checking the *'On the Menu check box), and specify* *{_}Preprocessing{_}* *as the* *{_}Menu Path{_}{*}*.  If you want, you can fill in the metadata fields, but it is not necessary for this tutorial.* Click Next >'''.
Figure 7: The algorithm will be placed on the _Preprocessing_ menu.
The fifth page is the _Algorithm Parameters_ page.  It allows you to specify what input parameters are needed to execute your algorithm from the end-user.  You can use the *Add* button to add as many input parameters as needed.  There are several  different types of input parameters, including strings, numbers, files,  and directories.  When the algorithm is executed by the end-user, this  list of input parameters is used to create a GUI for the end-user to  input appropriate data.
Figure 8: The _Algorithm Parameters_ page.
The _ID_ field for each input parameter is used as a key to obtaining the values provided by the user.  Each _ID_ should be unique and should only contain alphanumeric characters.  The _Label_ field is what is displayed to the end-user, and it may contain any character.  The _Description_ field describes to the end-user what the field is used for.  The _Type_ field specifies what type of data is valid for the input parameter.  If  a default value is appropriate (i.e. the input parameter is not a file  or directory the end-user chooses), the _Default_ field stores the default value for the input parameter.  Finally, if the type of data is a number type (i.e. _Integer_, _Long_, _Double_, etc . . .), the _Minimum Value_ and _Maximum Value_ fields can be used to specify constraints on what the end-user may input.

For our example algorithm, we will require one input  parameter, which will correspond to the user-specified file (mentioned  earlier in this tutorial).

Click the *Add button*, and give the input parameter the following values: *Unique ID:* _user_file_ *Label:* _Choose a File_ *Input Type:* _File_ *Description:* _The contents of this file will be copied to the output file._
Figure 9: Adding the input parameter.
Figure 10: The _Algorithm Parameters_ page with the input parameter specified.
Click *Next >*.

The sixth page is the _Input and Output Data_ page.  It allows you to specify the input data that your algorithm  requires off of the Data Manager (in Network Workbench) and the output  data that your algorithm outputs to the Data Manager after successful  execution.

The example algorithm in this tutorial expects one item off of the Data Manager, so click the *Add* button under the _Input Data_ section to add a new input data item.  For the _Mime Type_, input _text/nwb_.
Figure 11: Adding the input data item.
The example algorithm in this tutorial also outputs one item on to the Data Manager, so click the *Add* button under the _Output Data_ section to add a new output data item.  Give the output data item the following values: *File Name:* _output.txt_ *Label:* _Example Non-Java Algorithm Output File_ *Data Type:* _Text_ *Mime Type:* _text/plain_
Figure 12: Adding the output data item.
Figure 13: The _Input and Output Data_ page with the input and output data items specified.
The seventh page is the _Template String_ page.  The template string tells CIShell what command-line string to use when invoking your compiled program.   It can contain placeholders that correspond to either values specified  by the end-user (as input parameters) or the files chosen from the Data  Manager by the end-user (as input data items).  A placeholder is  specified in the template string by the text \_$

{code}
{placeholder}{code}


_, where placeholder is either the \_ID_ of an input parameter or _inFile\[input file number\]_.   If you recall from earlier in this tutorial, we specified that our  algorithm will accept one input parameter, which has the id of _user_file_, so the corresponding placeholder in a template string would be \_$

{code}
{user_file}{code}


_.  In the previous page, we specified an input data item of type \_text/nwb_ and an output data item with the name _output.txt_.  Our input data item has the corresponding template string placeholder \_$

{code}
{inFile\[0\]}\_.

{code}


The example program expects command-line arguments of the form: _user_file nwb_file platform_file output_file_ Click the first item in the _Template String Placeholders_ table (which should be the _Choose a File..._ entry), and then click the *Insert* button.  The text
\_
{code}


"${user_file}"\_ 

{code}


(quotes included) should appear in the _Template String_ field.
Figure 14: The first template string placeholder has been inserted.
Repeat this process again, except this time select the second item in the _Template String Placeholders_ _table (which should be the text/nwb File_ entry).  After that, append the text _"platform.txt"_ and _"output.txt"_ (quotes included) to the template string.
Figure  15: The final template string, containing the input parameter  placeholder; the input data item placeholder; the platform file; and the  fourth argument the example program expects, which the name of the file  to output to.
The eighth and final page is the _Source Code Files_ page.  It lets you choose a single file for your source code, if you wish to include your source code in the CIShell algorithm.  Most likely, your source code will consist of multiple  files, so you will need to archive them into a single file first.  For  this tutorial, however, the .cpp source file can be chosen since it is  the only one involved in compiling the example program.
Figure 16: The .cpp source code file has been chosen.
You should now have a new project in your Eclipse workspace.
Figure 17: The new project in the Eclipse workspace.

h3. Building the Project

Now that you have an Eclipse project for your algorithm, the next  step is to build the project.  Fortunately, it is just a matter of  running the _build.xml_ Ant script that was created with your project.  Right-click on the _build.xml_ file and select *Run As->Ant Build* to build the project.
Figure 18: Building the Eclipse project.
Your project should build successfully, and you should see output in your console that reflects this.

h3. Exporting the Project

Now that  your project has been built, it can be exported as a plugin for Network  Workbench.  Refresh your workspace.  (You can do this by right-clicking  on your project in the _Package Explorer_ and selecting *Refresh*.)  You should see a new _build_ directory.  Expand it.  Inside of it should be an out directory and a file called _projectName_bundleVersion.jar_, where _projectName_ and _bundleVersion_ were specified in the wizard.  (For this tutorial, the file would be called _example.nonjava.algorithm_0.0.1.jar_.)  Right-click on this file, and select *Export...*
Figure 19: Exporting the built Network Workbench plugin.
The _Export_ window should open.  Select *General->File System*, and click *Next >*.
Figure 20: Exporting the built Network Workbench plugin.
The  next page will allow you to select the files you want to export and  where on your file system to export them.  On the left hand side, select  the build directory, and make sure the appropriate jar file (i.e. _example.nonjava.algorithm_0.0.1.jar_) on the right hand side is checked.  Both of these should be done by default.

Select the _plugins_ directory inside your Network Workbench installation (e.g. _C:\Program Files\NWB\plugins_), and then click *Finish*.   (Note: If this plugin already exists in your Network Workbench  installation, you will be prompted to overwrite the already-existing  file.  Just click *Yes*.)
Figure 21: Exporting the built Network Workbench plugin.
Once  your algorithm plugin has been exported and you have loaded Network  Workbench, your algorithm will be on the menu that you specified.  When  you run the algorithm, a window should open that displays the file  selection input parameter you specified on the _Input Parameters_ page.

h3. Additional Information

Since development is almost always an iterative process, you will  probably want to modify your algorithm project after creating it through  the wizard.  This section will explain the various components of  non-Java algorithm projects in Eclipse and how you may want to modify  them.

Let's start with the file _gui.xml_ in the root directory of your project.  Notice the _OCD_ section.  The name here should match your project name (_projectName_), and the id should be _projectName.gui_.  Each _AD_ element inside this section corresponds to an input parameter for the GUI that the end-user sees when running your algorithm.

For this example, there should be only one _AD_,  and its attributes should match what you entered earlier in this  tutorial.  You can change the name and description attributes without  affecting anything else in your algorithm, but if you change the id, you  will need to reflect that change in your template string (which we will  get to).  If you notice the default and how its contents are _"file:"_, file and directory fields are actually _String_ fields (_type="String"_) with _"file:"_ and _"directory:"_ prefixes, respectively.

Now take a look at the _Designate_ section.  The _pid_ should match your project name as a matter of convention, but does not have to.  However, the _Object ocdref_ value inside this section must match the _OCD_ id specified above.

Open the _manifest.properties_ file.  This is where the _Bundle Name_ (_Bundle-Name_), _Bundle Symbolic Name_ (_Bundle\-_{_}SymbolicName_), and _Version_ (_Bundle-Version_) properties from the wizard ended up.  Again, as a matter of convention, the _Bundle Name_ and _Bundle Symbolic Name_ properrties should match your project name.

You will almost never need to touch the rest of the files in this directory nor the files in the _l10n_ and _lib_ directories.  The _src_ directory is where your source code ended up, so you can import additional files to it for your source code if you wish.

Expand the _ALGORITHM_ directory.  This is  where the sub-directories for the different platforms are located.  Any  common files (to all platforms) chosen on the _Choose Executable Files_ page ended up in the _default_ directory.  For the specific platforms, both the executable files and  related files ended up in their respective directories.  (For example,  the Windows executable file and the _platform.txt_ file that we chose as its related file ended up in the _win32_ directory.)  If you wished to change the sets of files for the various  platforms, these directories are where you would want to do that.  Keep  in mind that the executable files themselves must all have the same base  name (i.e. _NonJavaAlgorithmWizardExample_ for Linux, _NonJavaAlgorithmWizardExample{_}_.exe_ for Windows, etc.).

Open the _config.properties_ file.  This file contains data on how your program works with CIShell.  (CIShell is the framework that Network Workbench is based on.  Although we used  it in this tutorial, this process of creating algorithm plugins will  work for any CIShell-based application.)  The _executable_ property must match your base executable file name (as specified on the _Choose Executable Files_ page in the wizard).

The _template_ property is the template string specified in the wizard.  Notice that it starts with \_"${executable}"_, even though we did not specify that in the wizard.  That is replaced by the \_executable_ property.  The template string is actually a command-line string that  gets executed, which is why the executable is specified first (to invoke  your program).  That is also why everything is wrapped in sets of  quotes (in case spaces are involved).

Notice that no input data is specified here, but output data is (the _outFile_ properties).  The input (and output) data must be files for non-Java  algorithms, and as such the only metadata relevant to it is the expected  mime-type of each input data item.  The mime types for all of your  input data items are specified in the _service.properties_ file, which we will get to. _outFile_ can be thought of as an array of output data items, indexed into via C-style bracket notation (i.e. _outFile\[0\]_).  Setting an _outFile_ to a file name declares the output data item.  The label of each _outFile_ is what is displayed for the output data item in the Data Manager (in Network Workbench), and the _type_ tells the Data Manager what icon to use.  The mime-type must also be specified for each _outFile_, but like the input data, that is done in _service.properties_.

Finally, open up _service.properties_.  The _service.pid_ property here must match the one specified in the _Designate_ section in your _gui.xml_ file.  The _in_data_ and _out_data_ properties are the mime-types of the input and output data items mentioned above.  They are merely comma-separated lists.  The _menu_path_ defines where your algorithm should be placed in the menu in Network Workbench, and the _label_ property corresponds to the label in the menu.  All of the other properties are metadata.

h3. Example C+\+ Program Source Code#include <exception>

{code}


\#include <iostream>
\#include <fstream>


\#define PROGRAM_NAME "NonJavaAlgorithmWizardExample"
\#define EXPECTED_ARGUMENT_COUNT 5
\#define USER_FILE_ARGUMENT_INDEX 1
\#define NWB_FILE_ARGUMENT_INDEX 2
\#define PLATFORM_FILE_ARGUMENT_INDEX 3
\#define OUTPUT_FILE_ARGUMENT_INDEX 4

std::string readUserFile(const std::string& userFileName)
throw(std::exception);
std::string readNWBFile(const std::string& nwbFileName) throw(std::exception);
std::string readPlatformFile(const std::string& platformFileName)
throw(std::exception);
std::string readFileContents(std::ifstream& inputFileStream);

void outputCombinedContentsToFile(const std::string& userFileContents,
const std::string& nwbFileContents,
const std::string& platformFileContents,
const std::string& fileName)
throw(std::exception);

int main (int argumentCount, char\* arguments\[\]) {
if (argumentCount < EXPECTED_ARGUMENT_COUNT) {
        std::cerr << "You must provide " << EXPECTED_ARGUMENT_COUNT - 1;
        std::cerr << " arguments to the program." << std::endl;
        std::cerr << "Expected format:" << std::endl;
        std::cerr << "user_file nwb_file platform_file output_file";
        std::cerr << std::endl;
    } else {
// Process the end-user-specified file.

std::string userFileName = arguments[USER_FILE_ARGUMENT_INDEX|USER_FILE_ARGUMENT_INDEX];
std::string userFileContents;

try {
            userFileContents = readUserFile(userFileName);

            std::cout << "Successfully read the file you specified \'";
            std::cout << userFileName << "\'." << std::endl;
        } catch (std::exception& readUserFileException) {
            std::cerr << "There was an error reading your file \'";
            std::cerr << userFileName << "\': \"";
            std::cerr << readUserFileException.what() << "\"" << std::endl;

            return 1;
        }

// Process the NWB file.

std::string nwbFileName = arguments[NWB_FILE_ARGUMENT_INDEX|NWB_FILE_ARGUMENT_INDEX];
std::string nwbFileContents;

try {
            nwbFileContents = readNWBFile(nwbFileName);

            std::cout << "Successfully read the NWB file you selected off of ";
            std::cout << "the Data Manager (\'" << nwbFileName << "\').";
            std::cout << std:: endl;
        } catch (std::exception& readNWBFileException) {
            std::cerr << "There was an error reading the NWB file \'";
            std::cerr << nwbFileName << "\': \"";
            std::cerr << readNWBFileException.what() << "\"" << std::endl;
        }

// Process the platform file.

std::string platformFileName = arguments[PLATFORM_FILE_ARGUMENT_INDEX|PLATFORM_FILE_ARGUMENT_INDEX];
std::string platformFileContents;

try {
            platformFileContents = readPlatformFile(platformFileName);

            std::cout << "Successfully read the platform file \'";
            std::cout << platformFileName << "\'." << std::endl;
        } catch (std::exception& readPlatformFileException) {
            std::cerr << "There was an error reading the platform file \'";
            std::cerr << platformFileName << "\': \"";
            std::cerr << readPlatformFileException.what() << "\"" << std::endl;

            return 1;
        }

/\*
* Combine the user-specified file contents and the platform file into
* the user-specified output file.
\*/

std::string outputFileName = arguments[OUTPUT_FILE_ARGUMENT_INDEX|OUTPUT_FILE_ARGUMENT_INDEX];

try {
            outputCombinedContentsToFile(userFileContents,
                                         nwbFileContents,
                                         platformFileContents,
                                         outputFileName);

            std::cout << "Successfully wrote the combined contents to the ";
            std::cout << "file \'" << outputFileName << "\'." << std::endl;
        } catch (std::exception& outputCombinedContentsToFileException) {
            std::cerr << "There was an error outputting the combined contents";
            std::cerr << " of the file you specified and the platform file ";
            std::cerr << "to the file \'" << outputFileName << "\': \"";
            std::cerr << outputCombinedContentsToFileException.what();
            std::cerr << "\"" << std::endl;
        }
}

return 0;
}

std::string readUserFile(const std::string& userFileName)
throw(std::exception) {
std::ifstream userFileStream(userFileName.c_str(), std::ifstream::in);

if (\!userFileStream.is_open()) {
        throw std::ios_base::failure("Unable to open user file for reading.");
    }

std::string userFileContents = readFileContents(userFileStream);
userFileStream.close();

return userFileContents;
}

std::string readNWBFile(const std::string& nwbFileName) throw(std::exception) {
std::ifstream nwbFileStream(nwbFileName.c_str(), std::ifstream::in);

if (\!nwbFileStream.is_open()) {
        throw std::ios_base::failure("Unable to open NWB file for reading.");
    }

std::string nwbFileContents = readFileContents(nwbFileStream);
nwbFileStream.close();

return nwbFileContents;
}

std::string readPlatformFile(const std::string& platformFileName)
throw(std::exception) {
std::ifstream platformFileStream(platformFileName.c_str(),
std::ifstream::in);

if (\!platformFileStream.is_open()) {
        throw std::ios_base::failure(
            "Unable to open platform file for reading.");
    }

std::string platformFileContents = readFileContents(platformFileStream);
platformFileStream.close();

return platformFileContents;
}

std::string readFileContents(std::ifstream& inputFileStream) {
std::string fileContents;

while (inputFileStream.good()) {
        fileContents += inputFileStream.get();
    }

return fileContents;
}

void outputCombinedContentsToFile(const std::string& userFileContents,
const std::string& nwbFileContents,
const std::string& platformFileContents,
const std::string& fileName)
throw(std::exception) {
std::ofstream outputFileStream(fileName.c_str(), std::ofstream::out);

if (\!outputFileStream.is_open()) {
        throw std::ios_base::failure(
            "Unable to open output file for writing.");
    }

outputFileStream << "User file contents:" << std::endl;
outputFileStream << userFileContents << std::endl;

outputFileStream << "NWB file contents:" << std::endl;
outputFileStream << nwbFileContents << std::endl;

outputFileStream << "Platform:" << std::endl;
outputFileStream << platformFileContents << std::endl;
}

{code}


h3. Resources

[Attach:NonJavaAlgorithmWizardExample.zip|http://cishell.org/wiki/uploads/DevGuide/NonJavaAlgorithmWizardExample.zip]