How To program in Java for SPIS-NUM
Some information on Java language itself and
its proper usage in SPIS-NUM is provided here.
The first paragraphs can be useful for
beginners in Java. The last ones shall be more useful for advanced programmers.
They may even not be needed even by intermediate users who only handle top
level Java objects (Plasma, VolDistrib…) in the highest-level
object Simulation (in
particular for some of the last considerations, other users don’t be scared,
you don’t need to care about that!).
A lot of documentation can be found on Java on
the SUN web site http://java.sun.com.
More specifically the following URL can be
useful:
-
a tutorial on Java basics: http://java.sun.com/docs/books/tutorial/java/TOC.html
-
the API documentation
(Application Program(ming) Interface): http://java.sun.com/j2se/1.3/docs/api/
or http://java.sun.com/j2se/1.4/docs/api/
Many other data (html and a few printable
books) can be found on SUN site and elsewhere over the Internet.
Traditional books can of course also be
purchased but should probably be restricted to a first linear reading to come
to grips with Java. Html documentation is much more efficient for later
reference (especially for the Java API, and SPIS-NUM API of course also!).
NB: when working with SPIS-NUM you’ll mostly
use:
-
SPIS-NUM API documentation (JavadDoc generated html documents in documentation folder)
-
Java language reference
documentation (http://java.sun.com/docs/books/tutorial/java/TOC.html
or your preferred book)
-
but very little Java API
since few Java libraries are used in SPIS-NUM (contrarily to most Java
developers, not working in scientific computing)
As in any Java code, the main source of
documentation is the Javadoc-generated html
documentation. The usage of this documentation is documented in the “How to
understand SPIS-NUM architecture” page (NUM
architecture.html). The generation of Javadoc
documentation is briefly described here. More can be found at http://java.sun.com/j2se/javadoc.
The basic idea is to write special comments
before any important code component, so that they can be handled by Javadoc to generate html documentation pages. These
comments:
-
are enclosed between /** and */,
hence considered as comments by Java compiler but differentiated by Javadoc from regular comments thanks to the second star in /**.
-
are generally used before
the following components to document them (a comment alway
refers to the component that follows the
comment):
-
class declarations (class
and file headers indeed)
-
class fields (class
variables)
-
class constructors
-
class methods
-
but not before lower level
components (method variables, local variables)
-
support Javadoc
tags starting with @, the main ones are:
-
at routine level: @param, @return, @see (the text following the tag
describes the method parameters or returned object, and generates an hyper-link
in the case of @see)
-
at class level: @author, @version
-
support many html
instructions (tables, links, etc. see e.g.the version
control table in each SPIS-NUM class header)
See the existing SPIS-NUM classes for examples,
and the javadoc generated documentation for the
results (../API/public/index.html)
The html can be generated from the source
through one of the following methods:
-
the Project/Generate Javadoc menu in Eclipse
-
the javadoc instruction in command line. Try e.g.
“javadoc -private -breakiterator
-d doc -version -author spis/*/*/*.java�.
The working directory, form which the sources
can be found is SpisNum/src. Consistently with Java
rules, sources belonging to the package spis.xx.yy
are in the directory spis/xx/yy (hence in SpisNum/src/spis/xx/yy).
As usual in Java, and in order to simplify the
integration in SPIS framework, all SPIS-NUM classes (*.class) (and sources (*.java)) are also grouped in a single jar file spis.jar. In this release you should have this spis.jar
file in SpisNum/src, and another copy of this spis.jar file located in SpisNum/build/Lib directory. This is the one executed by the code! Don’t forget to update
it after modification. See the “How to transfer my SPIS/NUM java modifications
from Eclipse to SPIS framework� page (NUM integration in
framework.html) to learn how to later integrate your modified SPIS-NUM Java
sources into SPIS.
A jar file contains tarred Java classes (and
possibly sources). It is very similar to a tar file. The sources can be extracted thanks to
the jar instruction (jar comes with Java Development
Kit JDK): “jar xvf spis.jar�.
You can of course edit SPIS-NUM Java sources
with any ASCII-file editor.
There is a much better way yet, which is using an Integrated Development Environment (IDE).
One of the most advanced one for Java is Eclipse (http://www.eclipse.org). It was
demonstrated during SPIS course at Kiruna (..\Courses/SPISNUM_old_2.95.pdf), and is strongly recommended as development
tool for SPIS-NUM. It offers the following very powerful features:
-
Source
editing
-
GUI
debugging
-
Syntax
correction (bad syntax is underlined in red while typing)
-
Automatic
completion (type ctrl-space)
-
Hyper-text
navigation (press ctrl and click on a class, method, class variable…)
-
“Javadoc compatible� (Java doc pop-ups when mouse is on a
variable, class, method…)
In practice you’ll have to build an Eclipse
project:
1.
Declare a new project, with
name e.g. SpisNum (you can leave the default location
workspace/MyProjectName or change it, but don’t use SpisNum/src or you’ll have difficulties in import)
2.
Import sources: in
File/Import… menu choose “File System�, then browse to
select SpisNum/src as the directory from where to
import. There, select the spis folder in src directory (possibly only filtering *.java files)
3.
Finally import lapack library: select your project then in the dialog box
generated by the Project/Properties menu choose Java Build Path / Libraries /
Add External JARs and browse to include the blas.jar, f2jutil.jar, lapack.jar and xerbla.jar files (currently available in SpisNum/src).
Eclipse should display no more errors and you
should be able to start coding and debugging in Eclipse.
The best way is to run SPIS-NUM under Eclipse debugger, which is very
powerful (similar to most debuggers).
Since Eclipse cannot be invoked within SPIS
framework, SPIS-NUM must be run independently of the framework.
For that, the meshes, local and global
parameters, which are normally passed by the framework to NUM
must be saved by the framework (in two files named globalParOut.gp and
localParOut.lp, the latter containing both the local parameters and the meshes) and
then loaded by SPIS-NUM (same files to be renamed globalParIn.gp and
localParIn.lp,) when run as standalone code within Eclipse:
-
Have globalParOut.gp and localParOut.lp files created:
-
run the framework: define your geometry, mesh, etc. and run the Solver. It
will create these files into you temporary directory (/tmp/tmpspis…
as displayed on the screen when launching SPIS). Don’t worry if ever the Solver
crashes during simulation, it creates these files before starting.
-
Copy and rename these files
(globalParIn.gp and localParIn.lp) in your Eclipse working directory (e.g. workspace/SpisNum)
-
Run SPIS-NUM under Eclipse
as a standalone code to debug your source:
-
Select the class containing
a main() method (mimicking SPIS framework): Top class in spis.Top.Top
package
-
Click the Run/Debug As/Java
Application menu => it starts (next times you can use F11 to “debug last�)
-
In the menu mimicking SPIS
framework you’ll have to successively:
-
Click at the bottom of the
Console window of the debug perspective where the menu is displayed, exactly where your input is expected
-
First choose “i�: import (the files globalParIn.gp and
localParIn.lp are read)
-
Then choose the simulation
you want to run: certainly SimulationFromUIParams (“sui�), or maybe LEO example (“el�), or GEO (“eg�, “eg2�), or your new simulation (add it to Top menu)…
-
Eventually time-integrate
the dynamics: “int�
You can debug your code when running it and
view your results through the debugger if it terminates without crashing (no
transfer of the results to the framework is possible at this level for
graphical plotting, the best it to execute your code again within the framework
when it no longer crashes, although a dump of the results into a file might be
implemented in the future to import t it in the framework, similarly to what is
done for meshes in the opposite direction).
As many other things, this way of working can
be modified in next versions.
Basic Object Oriented Programming (OOP) and
Java concepts were presented during SPIS course (6th SPINE meeting, http://spis.onecert.fr/spine/meeting/spineM6/index.html,
Kiruna, 15-17 March 2004) and these slides can be
found in this documentation (..\Courses/SPISNUM_old_2.95.pdf),
A few important features are recalled here
(with some extra ones w.r.t. the slides):
-
Java naming conventions (upper/lower case usage): in
class/methods/fields naming words are separated by upper case letters, but
methods/fields/instance names start with lower case contrarily to
class/constructor names. Example: MyClass myInstance = new MyClass(myPassedVariable);
-
Good OOP practice (not specific to Java): define private fields and public methods. When doing so, the
implementation of the class can be modified provided the public method
interface is unchanged. Many of the private fields can be accessed through
“getters� and “setters� (e.g. the getter: public ThatFieldType
getThatField() {return thatField;}). This rule is strictly
respected in SPIS-NUM and shall be enforced for extensions. The only (relative)
exceptions are:
-
Some fields are protected, not private, so as to be accessible from
classes in the same package (usually derived classes). A change in such a class
may thus request changes in classes of the same package.
-
A few default global data
(particle types, material parameters, sampling parameters, etc.) are
necessarily stored as class fields in Java. There looked to be no reason not to
address them directly. They were thus declared public. These are the static fields
of the classes Global, SpisDefaultPartTypes,
SpisDefaultMaterials, SpisDefaultSampling, etc.of package spis.Top.Default.
-
Variable passing: variables are passed by reference
(which is different from pointers or values). The same is true for affectation.
Consequences:
-
myInstance = oldInstance; does not actually copy the
object but the reference: myInstance is a reference to the same old object (reference can be considered as a
pointer here)
-
If you want to make an
actual copy of an object: use the copy constructor, which accepts as parameter
the old to-be-copied object ex myCopiedScalSurfField = new ScalSurfField(myOldScalSurfField);
Automated methods (like clone) must not be used because they cannot know
what must be copied (almost everything, most ScalSurfField
fields in that example) and what must not (the surface mesh this ScalSurfField “lives� on, to which the surface field simply points (the
pointer/reference must be copied, not the whole mesh!))
-
If a component of a passed
variable is modified the original is modified: myMethod(myObject) {myObject.someField
= otherFieldValue;} modifies
the original of the passed object
-
If a passed variable is
“re-affected�, the original is not modified: myMethod(myObject) {myObject = otherOject;}, or myMethod(myObject) {myObject = new MyClass();}, does not modify the original of the passed object because myObject = xx has the mere consequence
that the local myObject now refers to (points to) xx. The original is simply no longer referenced.
-
Consequence of the two
previous points: if you want to return a simple float result in the passed
variable of a method/subroutine (no the returned object of a method/function)
don’t modified a passed float x, it won’t work, pass float[] x and modify x[0].
-
Result sub-types: a method that returns an
object as a result of its action can either chose itself the object subtype or
let the code calling it make that choice. Example: a sampling routine can
return a basic particle list PartList or a more detailed particle list RichPartList,
the latter being a derived class of the former. If the result is returned as a
modification of a passed object (of PartList or RichPartList type here), the sub-type is chosen by the caller (public void sample(PartList pl, …) method, which cannot
reallocate the object pl to another subtype, or the link is lost, see section above). If the it
is returned as the returned object of the method (public PartList sample(…) method, which would be a function, not a
subroutine in a Fortran vocabulary), the sub-type is chosen within the method.
Either of these possibilities can be better suited to a situation or another.
In the sampling example above, both choices are offered. The particle list
subtype can be imposed by the caller (e.g. to generate a list of the same
subtype as the list it must be injected into (this list is a plasma source)) by
using the public void sample(PartList pl, float dt) method of SurfDistrib class (“subroutine�).
It can also be left to the sampling method (e.g. to let it choose its most
accurate/efficient way to do the sampling, since any type is supported by the
caller, that will for instance compute SC interactions correctly whatever the
sub-type) by using the public PartList sample(float dt) method of SurfDistrib class (“function�).
-
Getters and setters –
(improper) usage of getters to modify an object field: as stated above, class
fields are private (or protected) and accessed by public “getters� and
“setters�, public XxxType
getXxx() and public void setXxx(yyy). Getters are much more used
than setters because fields are often defined in the constructor or as a result
of a method invocation. If a private field is accessed by the corresponding
public getter, it can be tempting to modify it and consider that the
modification is taken into in account in the containing class. Example: Table t; float[] array = t.getValues(); array[i] = 10.0f; It can be tempting because in most cases it will work. But this is not
a correct may of programming because the reason why the float[] field values is private is that it can
be implemented differently in some cases (e.g. storing only non-zero values, it
is the case for matrices), in which case the array returned by the getter is a
temporary array built by the getter. Modifying it does not modify the original
data in such a case. The right way of programming is to use a setter after the
modification: t.setValues(array); Â in the previous example. If ever this
particular implementation of the array only stores non-zero values, the setter
will properly condense the modified expanded array in the specific condensed
storage (maybe not very efficiently on this example but this is another story).
If the full expanded array is really stored, invoking the setter will only
induce a small cost since it will only result in a reference copy (onto
itself).