The core API provides the basic routines for reading, writing and navigating NeXus files. Operations are performed using a handle that keeps a record of its current position in the file hierarchy. All are read or write requests are then implicitly performed on the currently 'open' entity. This limits number of parameters that need to be passed to API calls, at the cost of forcing a certain mode of operation. It is very similar to navigating a directory hierarchy; NeXus groups are the directories, which can contain data sets and/or other directories.
The core API comprises the following functional groups:
General initialization and shutdown: opening and closing the file, creating or opening an existing group or dataset, and closing them.
Reading and writing data and attributes to previously opened datasets.
Routines to obtain meta-data and to iterate over component datasets and attributes.
Handling of linking and group hierarchy.
Routines to handle memory allocation. (Not required in all language bindings.)
The Fortran 90 interface is a wrapper to the C interface with nearly identical routine definitions. As with the Fortran 77 interface, it is necessary to reverse the order of indices in multidimensional arrays, compared to an equivalent C program, so that data are stored in the same order in the NeXus file.
Any program using the F90 API needs to put the following line at
the top (after the PROGRAM statement) :
Use the Table 1.1, “ Conversion of data types from C to F90 ” to convert from the C data types listed with each routine to the Fortran 90 data types.
Table 1.1. Conversion of data types from C to F90
| C | FORTRAN 90 |
|---|---|
int,
int
| integer |
char*
| character(len=*) |
NXhandle,
NXhandle*
| type(NXhandle) |
NXstatus
| integer |
int[]
| integer(:) |
void*
|
real(:)
or integer(:)
or character(len=*)
|
NXlink a,
NXlink* a
| type(NXlink) |
The parameters in Table 1.2, “
F90 parameters from NXmodule
used in defining variables
”,
defined in NXmodule, may be used in defining variables.
Table 1.2.
F90 parameters from NXmodule
used in defining variables
| Name | Description | Value |
|---|---|---|
NX_MAXRANK
| Maximum number of dimensions | 32 |
NX_MAXNAMELEN
| Maximum length of NeXus name | 64 |
NXi1
| Kind parameter for a 1-byte integer | selected_int_kind(2) |
NXi2
| Kind parameter for a 2-byte integer | selected_int_kind(4) |
NXi4
| Kind parameter for a 4-byte integer | selected_int_kind(8) |
NXr4
| Kind parameter for a 4-byte real | kind(1.0) |
NXr8
| Kind parameter for an 8-byte real | kind(1.0D0) |
Also see the doxygen documentation.[34]
This section includes installation notes, instructions for running NeXus for Java programs and a brief introduction to the API.
The Java API
for NeXus (jnexus) was implemented through the
Java Native Interface (JNI) to call on to the native C library.
This has a number of disadvantages over using pure Java, however
the most popular file backend HDF5 is only available using
a JNI wrapper anyway.
This implementation uses classes and native methods from NCSA's Java HDF Interface project. Basically all conversions from native types to Java types is done through code from the NCSA HDF group. Without this code the implementation of this API would have taken much longer. See NCSA's copyright for more information.
Documentation is old and may need revision.
For running an application with jnexus an recent Java runtime environment (JRE) will do.
In order to compile the Java API for NeXus a Java Development Kit is required on top of the build requirements for the C API.
Copy the HDF DLL's and the file
jnexus.dll to a directory in your path.
For instance C:\Windows\system32.
Copy the jnexus.jar to the place where
you usually keep library jar files.
In order to successfully run a program with
jnexus, the Java runtime systems needs
to locate two items:
The shared library implementing the native methods.
The nexus.jar file in order to find the Java classes.
The methods for locating a shared library differ
between systems. Under Windows32 systems the best method
is to copy the jnexus.dll and the HDF4, HDF5 and/or XML-library
DLL files into a directory in your path.
On a UNIX system, the problem can be solved in three different ways:
Make your system administrator copy the jnexus.so
file into the systems default shared library directory
(usually /usr/lib or /usr/local/lib).
Put the jnexus.so file wherever you see fit and
set the LD_LIBRARY_PATH environment variable to
point to the directory of your choice.
Specify the full pathname of the jnexus shared library on
the java command line with the
-Dorg.nexusformat.JNEXUSLIB=full-path-2-shared-library
option.
This is easier, just add the the full pathname to
jnexus.jar to the classpath when starting java.
Here are examples for a UNIX shell and the Windows shell.
Example 1.2. UNIX example shell script to start jnexus.jar
#!/sbin/sh java -classpath /usr/lib/classes.zip:../jnexus.jar:. \ -Dorg.nexusformat.JNEXUSLIB=../libjnexus.so TestJapi
Example 1.3. Windows 32 example batch file to start jnexus.jar
set JL=-Dorg.nexusformat.JNEXUSLIB=..\jnexus\bin\win32\jnexus.dll java -classpath C:\jdk1.5\lib\classes.zip;..\jnexus.jar;. %JL% TestJapi
The NeXus C-API is good enough but for Java a few adaptions of the API have been made in order to match the API better to the idioms used by Java programmers. In order to understand the Java-API, it is useful to study the NeXus C-API because many methods work in the same way as their C equivalents. A full API documentation is available in Java documentation format. For full reference look especially at:
The interface NeXusFileInterface first.
It gives an uncluttered view of the API.
The implementation NexusFile which gives more details about constructors and
constants. However this documentation is interspersed with information about
native methods which should not be called by an application programmer as they
are not part of the standard and might change in future.
See the following code example for opening a file, opening a vGroup and closing the file again in order to get a feeling for the API:
Example 1.4. fragment for opening and closing
// $Id: napi-java-prog1.java 554 2010-04-25 01:50:38Z Pete Jemian $
try{
NexusFile nf = new NexusFile(filename, NexusFile.NXACC_READ);
nf.opengroup("entry1","NXentry");
nf.finalize();
}catch(NexusException ne) {
// Something was wrong!
}
Some notes on this little example:
Each NeXus file is represented by a NexusFile object which
is created through the constructor.
The NexusFile object takes care of all file handles for you.
So there is no need to pass in a handle anymore to each
method as in the C language API.
All error handling is done through the Java exception handling mechanism. This saves all the code checking return values in the C language API. Most API functions return void.
Closing files is tricky. The Java garbage collector is
supposed to call the finalize method for each object it
decides to delete. In order to enable this mechanism,
the NXclose() function was replaced by
the finalize() method. In practice it seems
not to be guaranteed that the garbage collector calls the
finalize() method. It is safer to call
finalize() yourself in order to properly
close a file. Multiple calls to the finalize()
method for the same object are safe and do no harm.
Again a code sample which shows how this looks like:
Example 1.5. fragment for writing and reading
// $Id: napi-java-datarw1.java 554 2010-04-25 01:50:38Z Pete Jemian $
int idata[][] = new idata[10][20];
int iDim[] = new int[2];
// put some data into idata.......
// write idata
iDim[0] = 10;
iDim[1] = 20;
nf.makedata("idata",NexusFile.NX_INT32,2,iDim);
nf.opendata("idata");
nf.putdata(idata);
// read idata
nf.getdata(idata);
The dataset is created as usual with makedata() and opened
with putdata(). The trick is in putdata().
Java is meant to be type safe. One would think then that a
putdata() method would be required for each Java data type.
In order to avoid this, the data to write() is passed into
putdata() as type Object.
Then the API proceeds to analyze this object through the
Java introspection API and convert the data to a byte stream for writing
through the native method call. This is an elegant solution with one drawback:
An array is needed at all times. Even if only a single data value is
written (or read) an array of length one and an appropriate type
is the required argument.
Another issue are strings. Strings are first class objects in Java. HDF (and NeXus) sees them as dumb arrays of bytes. Thus strings have to be converted to and from bytes when reading string data. See a writing example:
Example 1.6. String writing
// $Id: napi-java-datarw2.java 554 2010-04-25 01:50:38Z Pete Jemian $
String ame = "Alle meine Entchen";
nf.makedata("string_data",NexusFile.NX_CHAR,
1,ame.length()+2);
nf.opendata("string_data");
nf.putdata(ame.getBytes());
And reading:
Example 1.7. String reading
// $Id: napi-java-datarw2.java 554 2010-04-25 01:50:38Z Pete Jemian $
String ame = "Alle meine Entchen";
nf.makedata("string_data",NexusFile.NX_CHAR,
1,ame.length()+2);
nf.opendata("string_data");
nf.putdata(ame.getBytes());
The aforementioned holds for all strings written as SDS content or as an
attribute. SDS or vGroup names do not need this treatment.
Let us compare the C-API and Java-API signatures of the
getinfo() routine (C) or method (Java):
Example 1.8. C API signature of getinfo()
/* $Id: frag-c-api-sig-getinfo.c 554 2010-04-25 01:50:38Z Pete Jemian $ */ /* C -API */ NXstatus NXgetinfo(NXhandle handle, int *rank, int iDim[], int *datatype);
Example 1.9. Java API signature of getinfo()
// $Id: frag-c-api-sig-getinfo.java 554 2010-04-25 01:50:38Z Pete Jemian $ // Java void getinfo(int iDim[], int args[]);
The problem is that Java passes arguments only by value, which means they cannot
be modified by the method. Only array arguments can be modified.
Thus args in the getinfo() method holds the
rank and datatype information passed in separate items in the C-API version.
For resolving which one is which, consult a debugger or the API-reference.
The attribute and vGroup search routines have been simplified
using Hashtables. The Hashtable returned by groupdir()
holds the name of the item as a key and the classname or the string SDS as the
stored object for the key. Thus the code for a vGroup search looks like this:
Example 1.10. vGroup search
// $Id: napi-java-inquiry1.java 554 2010-04-25 01:50:38Z Pete Jemian $
nf.opengroup(group,nxclass);
h = nf.groupdir();
e = h.keys();
System.out.println("Found in vGroup entry:");
while(e.hasMoreElements())
{
vname = (String)e.nextElement();
vclass = (String)h.get(vname);
System.out.println(" Item: " + vname + " class: " + vclass);
}
For an attribute search both at global or SDS level the returned Hashtable will hold the name as the key and a little class holding the type and size information as value. Thus an attribute search looks like this in the Java-API:
Example 1.11. attribute search
// $Id: napi-java-inquiry2.java 554 2010-04-25 01:50:38Z Pete Jemian $
Hashtable h = nf.attrdir();
Enumeration e = h.keys();
while(e.hasMoreElements())
{
attname = (String)e.nextElement();
atten = (AttributeEntry)h.get(attname);
System.out.println("Found global attribute: " + attname +
" type: "+ atten.type + " ,length: " + atten.length);
}
For more information about the usage of the API routines see the reference or the NeXus C-API reference pages. Another good source of information is the source code of the test program which exercises each API routine.
These are a couple of known problems which you might run into:
As the Java API for NeXus has to convert between native
and Java number types a copy of the data must be made
in the process. This means that if you want to read or
write 200MB of data your memory requirement will be 400MB!
This can be reduced by using multiple
getslab()/putslab() to perform data
transfers in smaller chunks.
Java.lang.OutOfMemoryException
By default the Java runtime has a low default value for
the maximum amount of memory it will use.
This ceiling can be increased through the -mxXXm
option to the Java runtime. An example:
java -mx512m ... starts the Java runtime
with a memory ceiling of 512MB.
The NeXus API for Java has a fixed buffer for file
handles which allows only 8192 NeXus files to be
open at the same time. If you ever hit this limit,
increase the MAXHANDLE define in
native/handle.h and recompile everything.
The following documentation is browsable online:
The Doxygen API documentation[35]
A verbose tutorial for the NeXus for Java API.
The API Reference.
Finally, the source code for the test driver for the API which also serves as a documented usage example.
IDL is an interactive data evaluation environment developed by Research Systems - it is an interpreted language for data manipulation and visualization. The NeXus IDL bindings allow access to the NeXus API from within IDL - they are installed when NeXus is compiled from source after being configured with the following options
Example 1.12. configure NeXus source to build IDL bindings
./configure --with-idlroot=/path/to/idl/installation --with-idldlm=/path/to/install/dlm/files/to
For further details see the README[37] for the NeXus IDL binding.
[31] C interface documentation: http://download.nexusformat.org/doxygen/html-c/
[32] C++ interface documentation: http://download.nexusformat.org/doxygen/html-cpp/