University of Leicester
Department of Physics and Astronomy
Third/Fourth Year Computing Workshop
Object-Oriented Programming

Dr. R. Willingale

Sep 17, 2004

Contents

1  Books and reference material
2  Recapitulation and jargon
3  Concepts in object-oriented programming
4  Fortran 90, C, C++ and Java
5  Problems and programming
6  A suitable problem for OOP
7  Using Java on Irix
8  Getting started with Java
    8.1  Task - application First
9  A walk through the First program
    9.1  Task - class Proton
10  Expressions, control and exceptions
    10.1  Task - application Second
11  A walk through the Second program
    11.1  Task - class Vector
    11.2  Task - scope, this and super
12  Abstract classes and interfaces
    12.1  Task - application Third
    12.2  Task - class Annulabound
13  Graphics
    13.1  Task - application Fourth
14  Applets
    14.1  Task - applet Fifth
15  Writing your own Java Program
    15.1  Task - interface ComplexSet
    15.2  Task - class Mandelbrot
    15.3  Task - class RenderSet
    15.4  Task - application Sixth
    15.5  Task - class Julia
16  Surface quality in the ray tracing problem
    16.1  Task - class Detector
    16.2  Task - defining a source, a mirror and a detector
17  Completing the ray tracing problem
    17.1  Task - class PlotLocal
    17.2  Task - application Seventh
18  Introducing interactive graphics
    18.1  Task - class SurfaceQM
    18.2  Task - using class SurfaceQM
    18.3  Task - applet Eighth
19  Ray tracing spherical surfaces
    19.1  Task - class SphereQM, application Ninth
20  Further interaction
    20.1  Task - applet Tenth
    20.2  Task - class Choice
21  Threads and multi-tasking
    21.1  Task - application Eleventh
    21.2  Task - class SlideSurfaceQM
    21.3  Task - wobbling the source and mirror simultaneously
22  Using the classes available
    22.1  Task - application Twelfth
23  Into the unknown
    23.1  Task - Cellular Automata
    23.2  Task - Recusive Triangles
24  Quick Java reference
    24.1  Primitive types
    24.2  Character escape sequences
    24.3  Arithmetic operators
    24.4  Relational and conditional operators
    24.5  Logical operators
    24.6  Bitwise operators
    24.7  class Math methods
    24.8  Repetition and control
    24.9  class String

1  Books and reference material

We shall be using Java as an example of a programming language which embodies object-oriented syntax. The emphasis will be on the techniques of object-oriented programming rather than features specific to Java. There are many, many books on the wonders of Java. Here is a selection:

There are many useful links on the WWW which provide excellent documentation and information on Java. For example the index of documentation provided by Sun Microsystems

http://java.sun.com/docs/
http://java.sun.com/docs/books/tutorial/

We shall be running Java under UNIX (or rather the SGI version of UNIX called IRIX). A quick reference for UNIX commands can be found at

http://www.le.ac.uk/physics/jdr/code/stuhelp/irix/index.html

2  Recapitulation and jargon

Before we launch into object-oriented programming it will be a good idea to review what you already know about programming from experience with Fortran 90 in the first and second year. At the same time we will introduce some jargon which is needed to describe what is going on.

A Fortran program consists of a set of ordered instructions. This is called ``imperative'' programming. The sequence of these instructions is most important.

``Control structures'', loops and ifs etc., are used to determine the flow through the instructions.

Values of numbers and more complicated data manipulated by the program are stored in ``variables''. Such variables are given names within the program. Variables can be updated as the program proceeds. This updating or changing of a variable is called assignment.

The actual flow depends on the ``state'' of the program. The state is determined by the values of variables in the program at a given time.

Variables have a ``type'', integer, real, complex etc.. As the types available become more complicated then it is important that the compiler performs type checking to avoid illegal expressions involving mixed types.

Groups of instructions can be wrapped up into ``procedures'' with a simple well defined interface. This process is known as ``abstraction'' because we are highlighting the essential details of something (in the form of an interface) and hiding the nitty-gritty details (the sequence of instructions required). Procedures like Fortran subroutines provide abstraction over instructions.

Abstraction is closely related to the concept of ``encapsulation'' such that things have a well defined inside and outside. Again Fortran subroutines perform encapsulation. The inner workings of a subroutine are protected from the outside. The only contact with the rest of the program is the interface which is defined by a set of arguments. Such ``encapsulation'' often helps to free the software designer and programmer from the complexities of implementation.

Fortran 90 is an example of a ``procedural'' programming language. The method of procedural programming requires the programmer to draw a flow diagram (now very much out of fashion but still most useful) indicating the steps required for the computation and the data or variables required for each step. The flow diagram shows how various data elements pass through a series of operations and this flow defines the procedure or procedures required. In Fortran 90 the key programming elements are functions or subroutines, modules or procedures which do things. The programmer decides which data elements must be passed to these modules to carry out the algorithm or sequence of operations required.

In Fortran 90 you can define data structures. These are complicated data types which comprise many components welded together and refered to by a single name. This is called ``data abstraction'', a process in which a simple name is used to refer to and hide the unnecessary nitty-gritty details of a complicated entity.

Programming with heavy use of data structures is sometimes referred to as ``data-oriented'' programming.

The concept of a ``data object'' is central to ``Object-Oriented'' Programming (OOP). It involves a further level of abstraction and encapsulation over and above Fortran subroutines and Fortran structures. A data object is an abstraction which encapsulates both the instructions AND the data. At present this will seem to be just words but when you have completed the workshop you should have a better idea of what this means.

3  Concepts in object-oriented programming

Object-Oriented Programming (OOP) is a different way of programming not a different way of computing. At the bottom level what the machine actually does when it is running the program is likely to be almost the same whether procedural or object-oriented programming were used. So the two approaches are just a different way of telling the computer what to do. To switch from a procedural style to an object-oriented style you must stop thinking about subroutines, functions and variables and start thinking about data objects and the way in which they interact. The syntax of OOP provides a uniform way to describe the data structures AND the operations required on elements of those data structures.

Just as a flow diagram is a natural way of describing a procedural style so a Venn diagram or hierarchical tree diagram is more appropriate for OOP. The idea is to group together related functions and data so that they can be described in an economical way. This is achieved by ``abstraction'' and ``encapsulation'' of both instructions and state. What you do is wrap up functions or operations with collections of data. This ``abstraction'' and ``encapsulation'' is described by a hierarchical ``class'' structure.

This is present in a primitive and latent form in Fortran 90 and most other languages. All languages handle variables and some of these variables are numbers. All numbers can be added and subtracted and so on, but in practice they are divided into two classes REAL and INTEGER. These are sub-divided further into SINGLE and DOUBLE precision depending on the number of bits used to represent them. Furthermore some of these numbers can be designated constants, variables that in fact should never change and cannot, therefore, appear on the left hand side of an assignment.

            variables
                |
            --------------------
            |                  |
         numbers           characters 
            |               
        ----------------    
        |              |  
      real          integer
        |              |
    --------        -------
    |      |        |     |
 single  double   short  long

In an object-oriented language such hierarchical structures can be defined by the programmer to construct very complicated data objects in an efficient way. The hierarchy should be dictated by the operations or functions which are to be performed on the data components.

A hierarchical structure is by no means unique and the art of successful OOP is to invent the best class structure for the problem in hand. It is often the case that separate hierarchical trees overlap and the overall class structure can become rather complicated. For example a fundamental class concept in all programming is the array. You can have arrays of almost any sort of data object. For example, a complex number is a two element array consisting of two real numbers.

In order to make an object-oriented syntax work efficiently we must employ ``polymorphism'' for naming operations or functions. The word is derived from the Greek for ``many forms'' and the concept is already present in Fortran 90 and other procedural languages and is prevalent in mathematics. For example we can refer to the addition of two integers, two real numbers, two complex numbers or even two vectors. In each case the word ``addition'' is used to describe a different set of operations which are related. Similarly we can take the sine function of a real number or a complex number or even all the elements of a vector. Again the word sine is used to describe different operations that are mathematically related. We may write SIN(A) for all these where the operation depends on the type of object A. This is polymorphism. Those of you who have some experience with C++ may have come across ``operator overloading''. This is also a form of polymorphism.

A final element which is essential to making object-oriented syntax efficient is ``inheritance''. This too is present in a primitive form in Fortran 90. For example the real and imaginary part of a complex number inherit the properties of real numbers. In object-oriented code all the objects of a particular class which are extensions of a more primitive class will inherit the properties of that more primitive class. This provides an enormous gain in efficiency and reliability because once a particular class of properties is defined it can be incorporated into new and more complicated classes with the minimum of effort and without the possibility of error. Because the functions are embedded in the class structure these are inherited as well. Class definitions further down the tree structure actually inherit code which is defined and present above them in the tree structure.

In summary OOP makes use of ``encapsulation'', ``polymorphism'' and ``inheritance'' all of which are present in Fortran 90. However what Fortran 90 does not have is a syntax which presents all three of these to the programmer in a flexible way.

The concepts of ``class'', ``type'' and ``instance'' are important when describing OOP. An ``instance'' is a particular occurrence of a data object ``type''. At run time it exists in the computer as a collection of real code and data. A data object ``type'' is a definition which can be used to create an instance of a data object. Data types can be arranged in a hierarchical structure of classes so a ``class'' may in fact be a type but it may be an abstract idea which groups together a number of types. For example in Fortran an array is a ``class'' of variable but it is not a ``type'' since you can't have an ``instance'' of just an array, it has to be an array of something. On the other hand an array of integers is a ``type'' of variable. An integer scalar is both a ``class'' and a ``type''. Finally an integer declared in some subroutine is an ``instance'' of the integer ``type''.

4  Fortran 90, C, C++ and Java

Fortran 90 offers one of the best scientific programming environments that has been honed over more than 20 years to provide the tools required by physicists and scientists to perform computations or formula transformations as the original derivation of the word Fortran suggests. Fortran 90 is an excellent example of a procedural programming language.

C++ is probably the most widely used object-oriented programming language. It is an extension of the popular C programming language developed from 1985 to 1991 and provides the three key elements described above. C is a low level procedural language and C++ adds on object-oriented syntax. Unfortunately reasonable familiarity with C is required to provide an understanding of the ++ bits. It would be impossible to give a comprehensive C and C++ course in the time available for a 3rd/4th year workshop.

Java was developed by Sun Microsystems in the period 1990-1995 and the first proper release, version 1.0, was in 1996. It is a kind of rationalisation of the ++ bits of C++. It is entirely based on OOP syntax and is uncluttered by pointers and other complications present in C. It also includes a more user friendly implementation of arrays like Fortran rather than the awkward definitions of arrays available in C. Java implementations are interpreter based rather than compiler based because this makes it platform neutral and ideal for use on networks like the World Wide Web. Because of this the code executes relatively slowly. However with the speed of modern machines this is not a problem and almost certainly compiler implementations or extensions of Java will become the norm in the near future. Java is easier to use and designed as a higher level language than C++ although most of the syntax is borrowed directly from C++. Like Fortran 90 it is the result of evolution in programming and is probably the best attempt yet at providing an all-purpose high level interpreter.

5  Problems and programming

The professional programmer should not force a language to fit a particular problem but choose a particular language which is well suited to the problem in hand. Unfortunately things are not always so clear cut. However if you want to become a good programmer you should consider the following:

Language syntax comes in three basic flavours; procedure-oriented, data-oriented and object-oriented.

Computational problems come in a large spectrum of flavours; assembler/low-level, system/compiler writing etc., windows systems, data systems, communications, data logging, scientific computation, the list is very large and there is no obvious unique sequence. Some problems may require a lot of development time to solve but will execute very quickly. Others will be easy to code but will take forever to execute.

If the problem you want to solve is important and big enough it is often worth the effort of learning a new language, or to learn some more of a language you already know a little about, rather than struggling with a familiar but inappropriate syntax.

OOP is not the answer to everything. Defining complicated data objects in an efficient and elegant way may not be the crux of a problem. You should choose your language and your programming style to suit the problem. However to make this choice in an informed way you need to know what the various options are about. This course aims to give you some idea of what OOP is about so that you can make the correct choice and use it when appropriate.

6  A suitable problem for OOP

The most common way of learning object-oriented programming and indeed using OOP in anger is in the development of windows systems. If you browse over the shelves in the local bookshop you will find large tomes concerned with windows programming and the Microsoft Foundation Class (MFC) library or similar topics. These are daunting. They often claim that OOP makes such programming less complicated and much easier but glancing through the many sections and sub-sections will probably convince you otherwise. However, it is the case that object-orientation is a very good way of providing tools for the solution to a particular type of problem. This is analagous to the way subroutines and functions in the NAG library can provide the tools for a procedural solution to many numerical scientific problems.

For this workshop we have chosen a more scientific programming example to try and introduce the main aspects of OOP. The idea is to perform particle or ray tracing through some instrument. The instrument might be a telescope or spectrometer and the particles might be photons or electrons or whatever. We will try to write code which can be adapted easily to many possible configurations in the same way that the MFC library is designed to provide the basic tools for windows programming.

Suppose you have an instrument which consists of various elements, polished mirror surfaces, stops and apertures etc.. Each element has a defined geometry and defined limits. We can set up a simple hierarchy:

                       surface geometry
                              |
              --------------------------------------
              |               |                    |
           plane            sphere        surface of revolution
                                                   |
                                          ---------------------
                                          |        |          |
                                    cylinder   paraboloid  hyperboloid

The elements will also be characterised by some surface quality:

                    surface quality
                          |
          -------------------------------------------------------
          |               |                  |                  |
        smooth          rough             grating            emitter
                          |                  |                  |
                     ----------          ----------        -----------
                     |        |          |        |        |         |
                 Gaussian  fractal  periodic  aperiodic  parallel  diffuse

Finally the rays or particles can be similarly arranged:

                particle
                   |
            -----------------
            |               |
          photon         electron

None of the above is unique, the arrangment could be different, and extra complexity could be added. Finding the best hierarchical description for the objects is the main problem with OOP. It may be that you have to try various possibilities before you find the best approach.

The underlying procedure which we want to follow is:

1)    generate a ray or particle - position, direction, energy
            |
           \ / 
            |
2)    search for intersection of ray with surface - new position
            |
           \ / 
            |
3)    reflect, scatter or absorb particle - new position, direction, energy
            |
           \ / 
            |
4)      if absorbed go to 1), if not absorbed go to 2)

These steps would be repeated for a maximum number of injected particles.

Steps 1) to 2) or 4) to 2) involve the propogation of the particle. This may be influenced by changing refractive index or electric and magnetic fields.

Step 2) depends on the geometry of the surfaces.

Step 3) depends on the surface quality and the type of particle.

7  Using Java on Irix

In order to use Java on the University Irix system you should include the directory /usr/java/bin in your PATH environment variable. This is best done by editing your .profile file so that the correct PATH gets set every time you log on to the machine.

The line you need in your .profile file should look something like

export PATH=$PATH:/usr/java/bin:

The Java compiler is called javac and expects source files with the extension .java

harrier$ javac First.java

This creates a file First.class which contains the machine independent Java bytecodes. You must then use the Java interpreter to run your program.

harrier$ java First

No extension is required on the file name since it expects the default .class produced by the compiler. Of course the name of the file, First in the above example, should reflect the class or classes contained in the file.

In order that the files you create with Java don't get mixed up with other files you might have we suggest that you create a new directory to work in during this course. So create a new directory called java under your home directory and change the default to this directory.

harrier$ cd             ensure that you are in your home directory
harrier$ mkdir java     create a new directory for the workshop
harrier$ cd java        select the new directory as default

8  Getting started with Java

We are going to use the particle tracing problem outlined above as a vehicle to introduce Java. The following code is a simple implementation of stage 1) in which particles are generated. You will see that it looks very different from a Fortran 90 program. In fact it looks much more like a C or C++ program but EVERYTHING in Java is ``encapsulated'' in a class.

class ElementaryParticle
{
    public long pnum;
    public double[] position = {0.0,0.0,0.0};
    public double[] direction = {0.0,0.0,0.0};
    private static long nextpnum = 0;
    ElementaryParticle()
    {
        pnum=++nextpnum;
    }
    public void println()
    {
        System.out.println("particle number "+pnum);
        System.out.print("    position ");
        System.out.print(position[0]+","+position[1]+","+position[2]);
        System.out.println();
        System.out.print("    direction ");
        System.out.print(direction[0]+","+direction[1]+","+direction[2]);
        System.out.println();
    }
}
class Photon extends ElementaryParticle
{
    public double wavelength;
    Photon(double phwl)
    {
        wavelength=phwl;
    }    
    public void println()
    {
        super.println();
        System.out.println("    particle type: photon");
        System.out.println("    wavelength "+wavelength);
    }
}    
class Electron extends ElementaryParticle
{
    public double velocity;
    static final double charge=-1.60e-19;
    Electron(double elve)
    {
        velocity=elve;
    }
    public void println()
    {
        super.println();
        System.out.println("    particle type: electron");
        System.out.println("    velocity "+velocity);
    }
}    
class First
{
    public static void main(String[] args)
    {
        System.out.println("Running first program");
        Electron el1 = new Electron(55.4);
        el1.direction[0]=0.3;
        el1.direction[1]=0.2;
        el1.direction[2]=Math.sqrt(1.0-Math.pow(el1.direction[0],2)-
        Math.pow(el1.direction[1],2));
        Photon ph1 = new Photon(10.9);
        el1.println();
        ph1.println();
    }
}

8.1  Task - application First

Type the above program verbatim into a file called First.java using your favourite editor. Then you can use the Java compiler javac and interpreter java as described above to run the program. Note that Java (and Unix) are case sensitive so, for example, photon would not be the same as Photon in the above. The output using the machine harrier should be:

harrier$ javac First.java
harrier$ java First
Running first program
particle number 1
    position 0.0 0.0 0.0
    direction 0.3 0.2 0.9327379053088815
    particle type: electron
    velocity 55.4
particle number 2
    position 0.0 0.0 0.0
    direction 0.0 0.0 0.0
    particle type: photon
    wavelength 10.9
harrier$ 

When carrying out this and subsequent tasks keep a record of what you are doing in your notebook including date, task number, listings of your source code, notes on how it works and a record of results and failures.

Task 1 completed on:   demonstrator:

9  A walk through the First program

The declaration of each class starts with the words ``public class'' followed by a name for the class. The rest of the declaration appears between curly braces {}. In Java such braces are used to group things together to form a scope. Each line or separate statement within the {} is terminated by a semicolon. The ends of lines are irrelevant, it is the semicolon that matters. The layout of the code is optional but one similar to the above makes in easier to read and understand.

Each class declaration can contain:

Note that the declaration of the class is only that. It is not creating an actual so called instance of the class. It is equivalent to saying what a REAL variable consists of in Fortran rather than creating a particular REAL variable. However, since the declaration of a class may contain methods, code for these methods may be compiled when you declare a class.

The access declared for classes, fields, methods and interfaces can be public, private, protected or package.

If the access in omitted (blank) then package is assumed. The package access and Packages are not dealt with here but for the purposes of this workshop they are essentially public. The use of access is usually only important if you are writing code for use by other programmers.

Look at the class ElementaryParticle declared above. The first four lines in the declaration declare fields. Each consists of

    access type name;

for example:

    public long pnum;

The primitive data types available are boolean (true or false), char (16-bit Unicode 1.1 character), byte (8-bit signed), short (16-bit integer signed), int (32-bit integer signed), long (64-bit integer signed), float (32-bit floating point), double (64-bit floating point).

Arrays are declared by adding [] after the type:

    public double[] position = {0.0,0.0,0.0};

In this case the following sequence enclosed in curly brackets initialises the array elements and defines the number of elements. This declaration could have been:

    public double[] position = new double[3];
    position[0]=0.0;
    position[1]=0.0;
    position[2]=0.0;

The word new creates the field concerned. Note that the array index runs from 0 through to n-1. In the above example the fields position and direction will be created separately for every instance of the class. This will become clearer a little later on.

The declaration of nextpnum is rather different. Now the field is private and static. This means that it is not created separately for every instance of the class. There will only be one unique version of this field (it is static and could be described as a class variable) and it can only be accessed by the ElementaryParticle class (it is private).

    private static long nextpnum = 0;

Next comes the declaration of a constructor for the class

    ElementaryParticle()
    {
        pnum=++nextpnum;
    }

The constructor has exactly the same name as the class itself, in this case ElementaryParticle. The parentheses () can contain arguments for the constructor but in this case there is none. Everytime a new instance of the class is created this constructor will be evoked. In this case the ++ operator will increase the value of the static field nextpnum by 1 and then the assignment (=) will assign that value to the field pnum. If the ++ operator had been placed after the field name nextpnum then the assignment would be made before the incrementation. This constuctor gives each particle created a unique running identification number. You can now see why the field nextpnum was declared static.

The last thing to be declared within the class ElementaryParticle is a method. The first line has the form

    access returntype name()

Here the returntype specifies the type or class of the data returned by the method when it completes which in this case is void (i.e. nothing is returned). The parentheses () can contain arguments but in this case there is none.

    public void println()
    {
       System.out.println("particle number "+pnum);
       System.out.print("    position ");
       System.out.print(position[0]+","+position[1]+","+position[2]);
       System.out.println();
       System.out.print("    direction ");
       System.out.print(direction[0]+","+direction[1]+","+direction[2]);
       System.out.println();
    }

The method println here produces a summary of the ElementaryParticle fields (data values). The method System.out.println() resides in class System and subclass out. The dots between the words are the way Java references elements within the class structure. Because of ``encapsulation'' all methods are referenced in this way. The argument of println and print (no new line) is just a string constructed from components using the + operator to join the elements together. That completes the declaration of the class ElementaryParticle.

The next two declarations are subclasses of the class ElementaryParticle called Photon and Electron. The declarations start:

public class Photon extends ElementaryParticle

The extends ElementaryParticle tells the compiler that we are declaring a subclass of some previously declared superclass, in this case ElementaryParticle. Otherwise the declaration takes a similar form and contains field declarations, a constructor and methods.

The field charge is qualified with the word final

    static final double charge=1.60e-19;

This is the way you declare named constants in Java equivalent to PARAMETERs in Fortran. Again the word static ensures there is only one copy of this field. The word final tells the compiler that the value is constant. Such a constant is created (compiled) during the declaration of a class rather than when a particular instance of the class is created. Constants like pi, the mass of the electron etc. should be declared like this.

The constructors in Photon and Electron have an argument declared as double

        Photon(double phwl)
        {
            wavelength=phwl;
        }

The method println extends the declaration already present in the superclass ElementaryParticle. The word super.println() refers to this declaration. However println is now augmented by an extra statement to print out the wavelength or velocity. This is an example of inheritance plus polymorphism. As you will see the behaviour of the method println depends on the context.

Finally we come to the declaration of the class First which contains the method that is the program. This must always be called main and declared public static and void.

    public static void main(String[] args)
    {
    ...
    }

Note that main() always has a single argument declared as String[] which is an array of words or strings separated by spaces. This can be used to pick up command line arguments from the system. For example the command line

$java Runit one two

would provide öne" for args[0] and "two" for args[1]. Such words can be used to control the main method at run time.

Instances of the classes Electron and Photon are declared and created by

        Electron el1 = new Electron(55.4); // create electron

The word new creates the instance. Electron(55.4) evokes the constructor for the class Electron which initialises the wavelength to 55.4. The name of the instance of the class in this case is el1.

The method main() above uses two Math methods Math.sqrt() and Math.pow() to take the square root and raise to a power (2 in this case). Again note all methods are encapsulated in a class which in this case is the Math class.

        el1.direction[0]=0.3;
        el1.direction[1]=0.2;
        el1.direction[2]=Math.sqrt(1.0-Math.pow(el1.direction[0],2)-
        Math.pow(el1.direction[1],2));

The fields within classes are referenced using the same syntax as for methods, the instance name followed by dot and field name. These are the Java equivalent of variables.

The method println is evoked for the two instances of Photon and Electron, el1 and ph1. Again ``encapsulation'', ``polymorphism'' and ``inheritance'' are at work. The method println is ``encapsulated'' in both el1 and ph1 the particles created. In both cases println has inherited code from the superclass ElementaryParticle. Yet println performs differently for electrons and photons. This is Object-Oriented Programming!

Three methods for putting comments into Java code are available

/** blar blar */              This can be picked up by the tool javadoc.
/* blar blar */               This can extend over many lines.
// blar blar                  This lasts until the end of the current line.

The /** form is designed to provide self documentation of classes and should be used just before the beginning of a class declaration to describe the class and its intended use. The /* form can be used for long comments explanations and the // form for short notes. Always include comments to explain what is going on in the code especially if you are having to think hard about what it is you are writing.

9.1  Task - class Proton

Edit your First.java file to include the declaration of a new subclass for the Proton and create an instance of a Proton in the program First. At the same time you should add some comments to the code to describe what is going on. Make sure you know the following before you proceed to the next stage:

Task 2 completed on:   demonstrator:

10  Expressions, control and exceptions

Although everything in Java is wrapped up in classes methods are written using a syntax very similar to C which in turn includes all of the important statement forms of Fortran 90. Our Second program includes examples of the commonly used statements. In order to code our chosen problem we need to perform vector arithmetic and vector manipulation so our Second program declares a Vector class and Particle class to do this.

The Vector class is:

/** 3 component vector manipulation and arithmetic */
class Vector
{
    public double[] cmpts =new double[3];    
    Vector(double x, double y, double z)
    {
        cmpts[0]=x;
        cmpts[1]=y;
        cmpts[2]=z;
    }
    Vector()
    {
        cmpts[0]=0.0;
        cmpts[1]=0.0;
        cmpts[2]=0.0;
    }
    public Vector add(Vector a)
    {
        /* add vectors */
        Vector r = new Vector();
        for(int i=0; i<3; i++)
        {
            r.cmpts[i]=cmpts[i]+a.cmpts[i];
        }
        return r;
    }
    public Vector subtract(Vector a)
    {
        /* subtract vectors */
        Vector r = new Vector();
        for(int i=0; i<3; i++)
        {
            r.cmpts[i]=cmpts[i]-a.cmpts[i];
        }
        return r;
    }
    public Vector scale(double a)
    {
        /* multiply by scalar */
        Vector r = new Vector();
        for(int i=0; i<3; i++)
        {
            r.cmpts[i]=cmpts[i]*a;
        }
        return r;
    }
    public double dot(Vector a)
    {
        /* dot product */
        double r = 0.0;
        for(int i=0; i<3; i++)
        {
            r=r+cmpts[i]*a.cmpts[i];
        }
        return r;
    }
    public Vector normalise()
    {
        /* normalise to direction cosines */
        double sums = this.dot(this);
            Vector r = new Vector();
        if(sums!=0.0)
        {
            r=this.scale(1.0/Math.sqrt(sums));
                return r;
        }
        else
        {
            throw new RuntimeException(
            "Attempt to normalise null vector");
        }
    }
    public Vector cross(Vector a)
    {
        /* cross product */
        Vector r = new Vector();
        r.cmpts[0]=cmpts[1]*a.cmpts[2]-cmpts[2]*a.cmpts[1];
        r.cmpts[1]=cmpts[2]*a.cmpts[0]-cmpts[0]*a.cmpts[2];
        r.cmpts[2]=cmpts[0]*a.cmpts[1]-cmpts[1]*a.cmpts[0];
        return r;
    }
    public void print()
    {
        System.out.print(cmpts[0]+","+cmpts[1]+","+cmpts[2]);
    }
    public void println()
    {
        System.out.println(cmpts[0]+","+cmpts[1]+","+cmpts[2]);
    }
}

The Particle class is:

/** Simple representation of a particle as a ray */
public class Particle
{
    public Vector position; 
    public Vector direction;
    public double energy;
    Particle(Vector pos, Vector dir)
    {
        position=pos;
        direction=dir.normalise();
        energy=1.0;
    }
    public void println()
    {
        System.out.println("Particle");
        System.out.print("        position ");
        position.print();
        System.out.print(" direction ");
        direction.print();
        System.out.println(" energy "+energy);
    }
}

The program Second then uses the Vector and Particle classes.

/** Second Java Program */
class Second
{
    public static void main(String[] args) 
    {
        System.out.println("Running Second program");
    // Create vectors and particles
        Vector v1 = new Vector(1.0,2.0,3.0);
        Vector u1 = new Vector(2.0,3.0,4.0);
        Particle r1 = new Particle(v1,u1);
        Vector v2 = new Vector(1.5,2.5,3.5);
        Vector u2 = new Vector(-2.1,8.1,4.1);
        Particle r2 = new Particle(v2,u2);
    // Do some vector arithmetic
        Vector sum = v1.add(v2);
        Vector sub = v1.subtract(v2);
        double dot  = r1.direction.dot(r2.direction);
        Vector cross = r1.position.cross(r2.position);
        double mag1 = Math.sqrt(r1.direction.dot(r1.direction));
        double mag2 = Math.sqrt(r2.position.dot(r2.position));
    // List result of vector addition
        v1.print();
        System.out.print(" add ");
        v2.print();
        System.out.print(" = ");
        sum.println();
    // List result of vector subtraction
        v1.print();
        System.out.print(" subtract ");
        v2.print();
        System.out.print(" = ");
        sub.println();
    // List result of dot product
        r1.direction.print();
        System.out.print(" dot ");
        r2.direction.print();
        System.out.println(" = "+dot);
    // List result of cross product
        r1.position.print();
        System.out.print(" cross ");
        r2.position.print();
        System.out.print(" = ");
        cross.println();
    // List calculated vector magnitudes
        System.out.println("magnitude of direction particle 1 = "+mag1);
        System.out.println("magnitude of position particle 2 = "+mag2);
    }
}

10.1  Task - application Second

Type the Vector and Particle classes into separate files Vector.java and Particle.java and compile them using javac. Then type the class Second program into a file called Second.java and compile and run it in the same way as before. Note that if you are going to use classes (like Vector and Particle) in subsequent programs it is best to put the source for each class in a separate file called < classname > .java The results should look like:

harrier$ javac Second.java
harrier$ java Second
Running Second program
1,2,3 add 1.5,2.5,3.5 = 2.5,4.5,6.5
1,2,3 subtract 1.5,2.5,3.5 = -0.5,-0.5,-0.5
0.371391,0.557086,0.742781 dot -0.225364,0.869261,0.439996 = 0.727376
1,2,3 cross 1.5,2.5,3.5 = -0.5,1,-0.5
magnitude of direction particle 1 = 1.0
magnitude of position particle 2 = 4.55522
harrier$ 

It should be noted that the Java API contains a class Vector. However this API class is a dynamically growable array and does not perform the operations usually associated with a vector in mathematics.

Task 3 completed on:   demonstrator:

11  A walk through the Second program

To start with take a look at the class Second which contains the main program that demonstrates the vector arithmetic.

The first section creates the Vector objects v1, u1, v2 and u2. The first constructor for the Vector class has three double arguments which are the components of the vector. A Particle consists of two vectors which define the origin of the ray and its direction and therefore the constructor takes two Vector arguments. So we have lines like

    Particle r1 = new Particle(v1,u1);

As before the lefthand side of the assignment declares an object called r1 of type Particle. The righthand side including the word new actually creates the object. The components which make up the ray are passed as arguments to the constructor of the class Particle.

The next section performs some vector arithmetic. Note that all the methods are evoked using the variable name. The field or method we require is encapsulated within a class so the dot syntax is used to find the correct component. For example the dot product of two vectors is evoked in the line

    double dot  = r1.direction.dot(r2.direction);

The first vector in the dot product is the field r1.direction and the second vector is r2.direction. The method is evoked as r1.direction.dot(). This will seem strange at first but it is the essence of Object-Oriented programming style. Methods that do things are always evoked using the name of some data object. There is an intimate relationship between the actions and the data. In this particular case the result, which must be a scalar, is returned as a double by the method concerned.

The rest of the Second program lists the results. Notice that the calculated value of the magnitude of the direction of r1 is listed as 1 but the direction of the Particle variable r1 was created as

    Vector u1 = new Vector(2.0,3.0,4.0);

The direction of a Particle is always normalised so that the components represent the direction cosines and the direction vector is always a unit vector. This normalisation takes place automatically in the constructor of the Particle class. Take a look at the definition of class Particle. It contains the constructor definition

    Particle(Vector pos, Vector dir)
    {
        position=pos;
        direction=dir.normalise();
    }

The normalise method is evoked for the direction everytime we create an instance of a Particle object.

The Vector class has two constuctors. The first with three double arguments and the second with no arguments. When Java decides which constructor or method to use it checks the name AND the list of arguments to make sure that they match. In this case a Vector can be created either from three double components or no components. In the latter case the components are automatically set to 0.0. This form of the constuctor is used in the method definitions within the Vector class. Using different forms of constructor for a class to provide different levels of default behaviour is a very common device in Java code.

We can now turn our attention to the code within the methods which perform the vector arithmetic. These are all declared within the class Vector. The methods add, subtract, scale and dot all contain definite loops. For example the dot product contains

        for(int i=0; i<3; i++)
        {
            r=r+cmpts[i]*a.cmpts[i];
        }

This is the equivalent of a Fortran do loop. The loop counter is declared as integer and initialized to zero by int i=0; and the test for the continuation of the loop is i<3;. Finally the increment to be applied on each pass is given by i++. Because the ++ is after the i the increment of 1 will be applied after the i has been used in the loop. So the loop will be executed for 3 passes with i=0,1,2 which picks out all the components of the vector. The lines executed in the loop are enclosed in {}. Actually if there is only one statement executed as in this case these brackets are not required since they are only used to group statements together. Notice that cmpts[i] refers to the ith component of ``this'' vector and a.cmpts[i] refers to the ith component of the argument vector a.

In each method the result is declared with the appropriate type, for example in dot we have

    double r = 0.0;

At the end of the method this is returned as the value of the method using

    return r;

In the case of cross for example the result is a Vector so we have

    Vector r = new Vector();
    .
    .
    return r;

In all cases we have chosen to create a returned value rather than change the original vector. For example scale and normalise could have performed the arithmetic and then overwritten the original component values but for consistency and ease of use this technique was not adopted.

You will see that normalise includes an if-else structure and something rather strange

    public Vector normalise()
    {
        /* normalise to direction cosines */
        Vector r = new Vector();
        double sums = this.dot(this);
        if(sums!=0.0)
        {
            r=this.scale(1.0/Math.sqrt(sums));
        }
        else
        {
            throw new RuntimeException(
            "Attempt to normalise null vector");
        }
        return r;
    }

The if() {} else {} is very similar to IF-ELSE-ENDIF blocks in Fortran. The brackets after the if contain an expression which returns a boolean (logical) value. When this is true the clause contained in {} immediately after the if() will be executed. If false the else {} clause will be executed. In the above case there is a problem if the sum of the squares of the vector elements (calculated using the dot product) is zero. It is then impossible to normalise the length of the vector to unity and we must ``throw an exception''. In this case the exception avoids division by zero. After an exception is ``thrown'' it can be ``caught'' by some other code which may deal with the problem. The RuntimeException class thrown is ubiquitous and will automatically be caught by the system.

In more complicated circumstances you may want to catch the exception yourself and take remedial action. This will be described in more detail later.

11.1  Task - class Vector

Read through all the methods in class Vector to make sure you understand how they work. How would you use them to:

Modify Second.java so that it attempts to normalize a null vector to see what effect the exception has.

Task 4 completed on:   demonstrator:

11.2  Task - scope, this and super

Look at how the word ``this'' is used to refer to the name of the current class/object and ``super'' is used to refer to the parent class. The way in which data fields (variables) can be declared and used within different scopes can be confusing. What is the output from the following code? In the clause

    while (logical) { code };

the code within the scope of the while will be executed repeatedly until the logical returns false. The code will not be executed at all if the logical is false at the start of the first pass.

Type the program into the file Var.java and run it to see if you are right.

class MyClass
{
        int var;               // field in MyClass (1st var)
        MyClass(int var)       // 2nd var not the same as MyClass.var
        {
                this.var =var; // makes the two var's equal
        }
}
class MySecondClass extends MyClass
{
        int var=1;             // 3rd var different from super.var
        MySecondClass(int var) // 4th var not this.var
        {
                super(var);
        }
        void mymethod()
        {
                while (var<10)
                {
                        int var =5; // 5th var within scope of while only
                        this.var++;
                        System.out.println(var+" "+this.var+" "+super.var);
                }
        }
}
class Var
{
        public static void main(String[] args)
        {
                MySecondClass a = new MySecondClass(9);
                a.mymethod();
        }
}

You can avoid such confusion by sensible choice of field names.

Task 5 completed on:   demonstrator:

12  Abstract classes and interfaces

It is often the case that when you start programming in OOP the top level classes like Particle above are difficult to define in detail. They may declare a general structure but must lack details of the implementation. For example we will now try and set up declarations for another part of our tracing problem, the surface elements. We know that all surfaces will be defined by certain fields. However at the moment we have no idea about the details of how we are going to find the intersection point of a particle path (ray) with a particular surface, a sphere say. In fact we know that this calculation is likely to be very different depending on the type of surface concerned.

Each surface will be defined using some reference datum consisting of a position vector, the surface normal at that position and a reference axis tangential to the surface at that position. The third reference axis can always be generated from the other two using the cross product. If we take the reference position, presumed to be actually on the surface, then we can always define some local 2-dimensional coordinate system to describe positions on the surface with an origin at the reference position. The edge or boundaries of the surface can then be described by some line or lines in this local coordinate system. This is illustrated in figure 1.

fig_surf.gif
Figure 1: Definition of a surface

Note that in general the surface may be curved and that the normal to the surface at some arbitary position is not necessarily parallel to the reference normal.

We will start with the boundaries in local surface coordinates. These may be rectangular, circular or some other fancy shape. In the ray-particle tracing we shall want to know whether or not an intersection point with some surface shape is inside or outside the surface boundary. It will also be useful to generate random positions on the surface within the boundary for creating particles. We can define an abstract entity in Java known as an interface.

/** General boundary on a 2-d surface */
interface Bounds
{
    boolean within(double[] coord); // intersection within surface limits?
    double[] ranpos(); // random position within limits
    void setseed(long seed); // set seed of random number generator
    void println(); // list details of boundary
}

An interface behaves like a class in many ways but you can't have an instance of an interface, it is a purely abstract thing. An interface can only contain implicitly abstract methods. That is methods which have no specified implementation. They can also contain static final fields, that is universal constants. An interface can be ``implemented'' in a class. For example here follows two possible boundary definitions in the form of classes:

import java.util.Random
/** Rectangular boundary on a 2-d surface */
class Rectbound implements Bounds
{
    public double xmin; // minimum value along reference axis
    public double xmax; // maximum value along reference axis
    public double ymin; // minimum value perpendicular to reference axis
    public double ymax; // maximum value perpendicular to reference axis
    public Random ran = new Random();
    Rectbound(double x1, double x2, double y1, double y2)
    {
        xmin=x1;
        xmax=x2;
        ymin=y1;
        ymax=y2;
    }
    public boolean within(double[] coord)
    {
        return (coord[0]>xmin && coord[0]<xmax && coord[1]>ymin &&
        coord[1]<ymax);
    }
    public double[] ranpos()
    {
        double[] pos=new double[2];
        pos[0]=ran.nextDouble()*(xmax-xmin)+xmin;
        pos[1]=ran.nextDouble()*(ymax-ymin)+ymin;
        return pos;
    }
    public void setseed(long seed)
    {
        ran.setSeed(seed);
    }
    public void println()
    {
        System.out.println("Rectangular boundary");
        System.out.print("    x-range "+xmin+","+xmax);
        System.out.println(" y-range "+ymin+","+ymax);
    }
}
/** Circular boundary on a 2-d surface */
class Circbound implements Bounds
{
    public double rads; // square of radius
    public Random ran = new Random();
    Circbound(double r)
    {
        rads=r*r;
    }
    public boolean within(double[] coord)
    {
        return (coord[0]*coord[0]+coord[1]*coord[1])<rads;
    }
    public double[] ranpos()
    {
        double[] pos=new double[2];
        double r=Math.sqrt(rads);
        do
        {
            pos[0]=ran.nextDouble()*r*2-r;
            pos[1]=ran.nextDouble()*r*2-r;
        }
        while( (pos[0]*pos[0]+pos[1]*pos[1]) >= rads);
        return pos;
    }
    public void setseed(long seed)
    {
        ran.setSeed(seed);
    }
    public void println()
    {
        System.out.println("Circular boundary");
        System.out.println("    radius "+Math.sqrt(rads));
    }
}

Notice the first line of each class declaration states that the class implements the Bounds interface. Therefore each class contains definitions of the methods declared in the Bounds interface. Study these class definitons carefully and make sure you understand how they work. The && operator performs a logical AND and Random is a class in Java's utilities which provides sequences of random numbers. Having created an instance of the Random class (called ran in the above code) then each member of the random sequence can be returned using ran.nextDouble() which is in the range 0.0 to 1.0 by default. The construct

    do { code } while (logical);

is a loop which will repeat code until the logical is false. Note this form of indefinite loop always executes the code at least once.

When a class implements an interface it generally provides a definition for ALL the methods declared in the interface. If it doesn't then the implementing class must be declared abstract.

Interfaces can be extended in the same way as classes except they can extend more than one interface. This is called multiple inheritance. Java does not allow multiple inheritance of classes so a class can only extend one superclass. However a class can implement more than one interface. So the use of interfaces is the way in which Java incorporates multiple inheritance.

An interface is purely abstract and as such it forms no more than a sort of contract. Any class which references an interface enters into that contract. We will now consider the definition of a Surface class:

/** General surface geometry*/
abstract class Surface
{
    public Vector refpos; // reference position on surface (origin)
    public Vector refnorm; // reference normal at origin
    public Vector refaxis; // reference axis at origin
    public Vector intpos =new Vector(0.0,0.0,0.0); // intersection position
    public double intdist=0.0; // intersection distance (along ray)
    public double[] intcoord={0.0,0.0}; // intersection coordinates
    Bounds edge; // Boundary class
    Surface(Vector pos, Vector norm, Vector axis, Bounds bds)
    {
        refpos=pos;
        refnorm=norm.normalise();
        refaxis=axis.normalise();
        edge=bds;
    }
    public void println()
    {
        System.out.print("    pos ");
        refpos.print();
        System.out.print(" norm ");
        refnorm.println();
        System.out.print("    axis ");
        refaxis.println();
        edge.println();
    }
    abstract protected void ptoc(); // position to coordinates
    abstract protected void ctop(); // coordinates to position
    abstract protected boolean intersect(Particle p); // Find intersection
    abstract protected Vector getran(); // get random position vector
}    

Note that this is declared as abstract. This is because it the definition of four abstract methods, ptoc(), ctop(), intersect(Particle p) and getran(). The word protected means these methods can only be accessed from within classes that extend the class. In other words they are meant to be implemented only from within such classes. The method ptoc() is expected to convert from a position on the surface expressed as a 3-component vector to a 2-d coordinate pair in the local surface coordinate system (the dashed grid on figure 1). ctop() should convert from a 2-d coordinate pair to a 3-component vector. The method intersect(Particle p) finds the intersection position vector of a particle (ray) with the surface and getran() gets a random position vector within the boundary set. None of these methods has an implementation (executable statements) and so an instance of a surface cannot be created. In the class Surface we have no idea how the methods in Bounds or the methods declared abstract are going to work. We must extend Surface by further class definitions to specify ``real'' surfaces that can have an instance. However the neat thing is that all such surfaces will inherit the fields and methods declared in Surface. Here is an example of the definition of a Plane surface class.

/** Plane surface */
class Plane extends Surface
{
    Plane(Vector pos, Vector norm, Vector axis, Bounds bds)
    {
        super(pos,norm,axis,bds);
    }
    protected void ptoc()
    {
    // Find y reference axis in plane
        Vector yaxis=refnorm.cross(refaxis);
    // Vector from origin on plane to intersection
        Vector vp=intpos.subtract(refpos);
    // Calculate local x and y
        intcoord[0]=vp.dot(refaxis);
        intcoord[1]=vp.dot(yaxis);
    }
    protected void ctop()
    {
    // Find y reference axis in plane
        Vector yaxis=refnorm.cross(refaxis);
    // Vector from origin on plane to intersection
        Vector vp=refaxis.scale(intcoord[0]);
        vp=vp.add(yaxis.scale(intcoord[1]));
    // Find position of intersection
        intpos=refpos.add(vp);
    }
    protected boolean intersect(Particle p)
    {
        // Check energy of particle
       if(p.energy==0.0)
            return false;
        // Cos of angle between plane normal and particle direction
        double a = refnorm.dot(p.direction);
        if(a==0.0)
        {
        // Particle parallel with surface, no intersection
            return false;
        }
        else
        {
        // Distance and direction to point on plane
            Vector dr=refpos.subtract(p.position);
            double s=dr.dot(dr);
            s=Math.sqrt(s);
            dr=dr.normalise();
        // Cos of angle between line to point on plane and norm
            double g=refnorm.dot(dr);
        // Find intersection on plane
            intpos=p.position.add(p.direction.scale(s*g/a));
        // Calculate distance along ray to plane
            Vector rr=intpos.subtract(p.position);
            intdist=rr.dot(rr);
            intdist=Math.sqrt(intdist);
        // Calculate local x and y
            ptoc(); 
        // Check within limits
            return edge.within(intcoord);
        }
    }
    public Vector getran()
    {
        intcoord=edge.ranpos();
        ctop();
        return intpos;
    }
    public void println()
    {
        System.out.println("Planar surface");
        super.println();
    }
}

Note that the constructor includes an argument of type Bounds. This is assigned to the instance edge and subsequently the within() method from the Bounds interface is referenced using edge.within(intcoord). Any instance of Plane will be constructed using a class that implements Bounds. It is then that the contract set by the Bounds interface is honoured. At last all the abstract methods in Bounds and Surface will have an implementation.

Figure 2 illustrates the vector arithmetic implemented in the intersect() method to find where a particle hits a plane.

fig_inters.gif
Figure 2: Intersection of a particle with a plane surface

We can now write a class Third to exercise the class Surface.

/** Third Java Program */
class Third
{
    public static void main(String[] args)
    {
        System.out.println("Running Third program");

        Vector org = new Vector(2.0,2.0,3.0);
        Vector dir = new Vector(1.0,0.0,0.0);
        Particle p1 = new Particle(org,dir);

        Rectbound rec = new Rectbound(-1,2,-3,6);
        Circbound cir = new Circbound(3.0);

        Vector pos = new Vector(1.5,2.5,3.5);
        Vector norm = new Vector(1.0,0.0,0.0);
        Vector axis = new Vector(0.0,1.0,0.0);
        Plane s1 = new Plane(pos,norm,axis,rec);
        Plane s2 = new Plane(pos,norm,axis,cir);

        boolean hit1 = s1.intersect(p1);
        boolean hit2 = s2.intersect(p1);

        p1.println();
        s1.println();
        s2.println();

        if(hit1)
        {
            System.out.println("intersection");
            System.out.print("    position ");
            s1.intpos.println();
            System.out.println("    distance "+s1.intdist);
            System.out.println("    coordinates "+s1.intcoord[0]+
            ","+s1.intcoord[1]);
        }

        if(hit2)
        {
            Vector rr=new Vector(0.0,0.0,0.0);
            System.out.println("random positions in circle");
            for(int i=0;i<10;i++)
            {
                rr=s2.getran();
                System.out.print("    ");
                rr.println();
            }
        }
    }
}

12.1  Task - application Third

Type the Third program in and try it out. To save typing you can copy the Bounds, Rectbound, Circbound, Surface and Plane source code from

/disk/s/zrw/under/java/Bounds.java
/disk/s/zrw/under/java/Rectbound.java
/disk/s/zrw/under/java/Circbound.java
/disk/s/zrw/under/java/Surface.java
/disk/s/zrw/under/java/Plane.java

You will need to compile Bounds.java, Rectbound.java, Circbound.java, Surface.java and Plane.java after you have copied them. The results should look like:

harrier$ java Third
Running Third program
Particle

        position 2.0,2.0,3.0 direction 1.0,0.0,0.0 energy 1.0
Planar surface
        pos 1.5,2.5,3.5 norm 1.0,0.0,0.0
        axis 0.0,1.0,0.0
Rectangular boundary
        x-range -1.0,2.0 y-range -3.0,6.0
Planar surface
        pos 1.5,2.5,3.5 norm 1.0,0.0,0.0
        axis 0.0,1.0,0.0
Circular boundary
        radius 3.0
intersection
    position 1.5,2.0,3.0
    distance 0.5
    coordinates -0.5,-0.5
random positions in circle
    1.5,2.31694,1.84626
    1.5,3.28978,4.23799
    1.5,1.3789,3.95061
    1.5,2.11345,3.55555
    1.5,1.6541,1.16536
    1.5,3.89111,4.2566
    1.5,0.391311,5.61832
    1.5,4.88745,3.4916
    1.5,0.624179,2.51002
    1.5,2.30628,2.37661

You should now get an inkling of the power of Object-oriented syntax. All the effort put into designing the class and interface structure begins to pay off when you start to construct more complicated programs. You can create any number of objects that implement Bounds as in the lines

    Rectbound rec = new Rectbound(-1,2,-3,6);
    Circbound cir = new Circbound(3.0);

Each instance, rec and cir above, has its own copy of the fields and has access to an implementation of the methods in Bounds. Similarly the lines

    Plane s1 = new Plane(pos,norm,axis,rec);
    Plane s2 = new Plane(pos,norm,axis,cir);

create instances of the Surface class. These in turn can be passed as arguments whenever an instance of the Surface class is required. We can implement many forms of Bounds and Surface and they will all behave in exactly the same way, sharing code and data whenever possible. Remember the instance names, like ``s1'' and ``s2'' above, encapsulate both the fields (data) and methods (procedures) so that providing we know what is encapsulated and we understand the arguments required then it is almost guaranteed that the methods will work correctly.

Problems can arise. Suppose you were presented with the class third without being told what the classes Vector, Particle, Surface and so on, did. Then things would not be so transparent. Object-Oriented code can be neat, reliable but incomprehensible. When you design a class structure to solve some problem you should spare a thought for programmers who might want to use or extend it.

Task 6 completed on:   demonstrator:

12.2  Task - class Annulabound

Create a file Annulabound.java defining a new class of boundary called Annulabound which descibes an annulus on the surface. This will require an inner and outer radius. At the same time modify the Third.java class to test out your new boundary class.

Task 7 completed on:   demonstrator:

13  Graphics

One of the main reasons Java has been so successful (so far) is that it was designed for use with Graphics on the Internet. In fact the first releases were incorporated into the Netscape and HotJava World Wide Web browsers.

The classes, interfaces and associated methods which provide the functionality required for graphics and many other higher level tasks constitute what is known as the Java Application Programming Interface or Java API. The particular part of the API we shall concentrate on is the Abstract Window Toolkit or AWT. We shall also touch on the related applet package. An applet is a program written in Java to run within a Java-compatible web browser such at Netscape or HotJava.

If we can plot out results from the ray tracing we shall be able to check and develop the code more quickly. The following Fourth class and associated classes provides a simple test of the Surface class developed in the Third program. The Java API provides classes which can plot graphics, images, text and so on in windows. To start with we shall be using the AWT class Canvas. This is a simple way of providing a blank window component which can be used for plotting graphs etc.. Unfortunately all the graphics methods provided in the API are based on pixelated graphics system in which the coordinates are integer pixel positions. It is more natural for physicists to work with real variables so I've written a simple class Figure with extends Canvas and provides plotting utilites that look a bit like PGPLOT that you used in the Fortran 90 workshops. You don't need to know the details of the inner workings of class Figure but you do need to know the public methods which it provides.

public class Figure extends Canvas
{
    // the constructor is the same as for Canvas
    Figure()

    // set the Canvas to uniform background colour
    public void clear()

    // set the current plotting colour
    public void setColor(Color c)

    // set the plotting window on the canvas.
    // The default is 0.1 to 0.9 in both axes, leaving room for annotation
    // around the edge. Position 0.0,0.0 is the bottom left.
    public void setWindow(double xlo, double xhi, double, ylo, double yhi)

    // define the real variable space of the plotting window.
    // The origin is in the bottom left with x increasing to the right and y
    // increasing up the screen. mtype is used to define the type of mapping
    // "lin" for linear, "xlog" for x logarithmic y linear, "ylog" for
    // x linear y logarithmic and "xylog" for both axes logarithmic.
    // "scale" for linear with the same scaling for both axes.
    public void mapping(String mtype, double xlo, double xhi, double ylo,
    double yhi)

    // draw an ellipse top left at x,y of given width and height.
    public void drawOval(double x, double y, double width, double height)

    // draw a filled rectangle top left at x,y of given width and height.
    public void drawRect(double x, double y, double width, double height)

    // draw a line from x1,y1 to x2,y2
    public void drawLine(double x1, double y1, double x2, double y2)

    // draw lines joining n x[] and y[] points to approximate a curve
    public void drawCurve(int n, double[] x, double[] y)

    // draw closed polygon joining n x[] and y[] points 
    public void drawPolygon(int n, double[] x, double[] y)

    // draw a line histogram for n points x[] and y[]
    public void drawHistogram(int n, double[] x, double[] y)

    // draw a box around mapped surface within Canvas window
    public void box()

    // set the current pixel position
    public void setPixelPos(double x, double y)

    // get the current pixel position
    public double[]  getPixelPos()

    // get next pixel position in raster. The raster starts at the top
    // left hand corner. When the bottom right hand corner is reached the
    // raster is complete, false is returned and the current pixel position is
    // reset to the start of the next raster.
    public boolean nextPixelPos()

    // draw the pixel at the current pixel position using current
    // drawing colour.
    public void drawPixel()
}

In order to produce a graph or picture you must extend the Figure class and write a method called paint(). This is illustrated in the following Picture and Graph class definitions.

class Picture extends Figure
{
    public void paint(Graphics g)
    {
        double xmin=-3.0;
        double xmax=9.0;
        double ymin=-3.0;
        double ymax=9.0;
        mapping("lin",xmin,xmax,ymin,ymax);

        Rectbound rec = new Rectbound(-1,2,-3,6);
        Circbound cir = new Circbound(3.0);
        rec.ran.setSeed(1111);
        cir.ran.setSeed(2222);

        Vector pos = new Vector(1.5,2.5,3.5);
        Vector norm = new Vector(0.0,0.0,1.0);
        Vector axis = new Vector(1.0,0.0,0.0);

        Plane s1 = new Plane(pos,norm,axis,rec);
        Plane s2 = new Plane(pos,norm,axis,cir);

        Vector rr=new Vector(0.0,0.0,0.0);

        setColor(Color.red);
        for(int i=0;i<1000;i++)
        {
            rr=s1.getran();
            drawOval(rr.cmpts[0],rr.cmpts[1],0.0,0.0);
        }
        setColor(Color.green);
        for(int i=0;i<1000;i++)
        {
            rr=s2.getran();
            drawOval(rr.cmpts[0],rr.cmpts[1],0.0,0.0);
        }

        setColor(Color.blue);
        drawLine(xmin,2.5,xmax,2.5);
        drawLine(1.5,ymin,1.5,ymax);

        setColor(Color.black);
        box();
    }
}

class Graph extends Figure
{
    public void paint(Graphics g)
    {
        double xmin=-3.0;
        double xmax=9.0;
        double ymin=-3.0;
        double ymax=9.0;
        mapping("lin",xmin,xmax,ymin,ymax);
        
        setColor(Color.green);
        drawOval(1.5,2.5,6.0,6.0);

        Rectbound rec = new Rectbound(-1,2,-3,6);
        double[] x = new double[4];
        double[] y = new double[4];
        x[0]=0.5;
        y[0]=-0.5;
        x[1]=3.5;
        y[1]=-0.5;
        x[2]=3.5;
        y[2]=8.5;
        x[3]=0.5;
        y[3]=8.5;
        setColor(Color.red);
        drawPolygon(5,x,y);

        setColor(Color.blue);
        drawLine(xmin,2.5,xmax,2.5);
        drawLine(1.5,ymin,1.5,ymax);

        setColor(Color.black);
        box();
    }
}

In order to display these plots you need to create an instance of the class Frame and then add the Figure components to the Frame. The following class Fourth does this.

import java.awt.*;

public class Fourth extends Frame
{
    public Fourth(String title)
    {
    super(title);
    }

    public static void main(String args[])
    /* main method to execute Fourth as an application */
    {
    System.out.println("Fourth start");

    Fourth app=new Fourth("Fourth Java");
    app.setSize(600,300);

    Picture picture = new Picture();
    Graph graph = new Graph();
    GridLayout gridlay = new GridLayout(1,2);

    app.setLayout(gridlay);
    app.add(picture);
    app.add(graph);
    app.show();

    System.out.println("Fourth stop");
    }
}

The line

import java.awt.*;

makes available all the necessary classes in the AWT package.

The constructor for class Fourth simply puts a title at the top of the displayed window. The main() method is the program which java will execute. The program starts by creating an instance of Fourth (extended from Frame) and then sets it's size (in pixels)

    Fourth app=new Fourth("Fourth Java");
    app.setSize(600,300);

It may seem strange that the main() method has to create an instance of Fourth since main() is encapsulated in the class Fourth. However a method can be called without an instance of the encapsulating class being present although it is usually the case that such an instance exists. Next we must create instances of Picture and Graph.

        Picture picture = new Picture();
        Graph graph = new Graph();

The instance of the class gridLayout is used to specify how the Figures are to be packed into the Frame. (1,2) means 1 row, 2 columns.

        GridLayout gridlay = new GridLayout(1,2);

The following lines set up the grid layout and add the Figures to the Frame.

    app.setLayout(gridlay);
    app.add(picture);
    app.add(graph);
    app.show();

Note all these methods are encapsulated in app which is the name of the instance of the Fourth (Frame).

13.1  Task - application Fourth

Study Graph and Picture to see how they use the methods defined in the class Figure. You don't need to worry about the details of how the methods in Figure work. Figure, in turn, uses the plotting methods provided by the AWT in the class java.awt.Graphics. Copy classes Figure, Picture and Graph from

/disk/s/zrw/under/java/Figure.java
/disk/s/zrw/under/java/Picture.java
/disk/s/zrw/under/java/Graph.java

Type the class Fourth into the file Fourth.java and try it out. You will need to compile Figure.java, Picture.java and Graph.java as well. When you want to remove the graphics window produced use the winops kill option.

Task 8 completed on:   demonstrator:

14  Applets

If you wish to display graphics using Java from a World Wide Web (WWW) browser or, indeed, use Java for programming WWW applications, you must use a different form of Java program, the applet. The java.applet package contains the Applet class which is an extension of the java.awt.Panel class. A Panel is like the java.awt.Frame we used in the Fourth class above. It is a container in which we can display graphical components. Here is an applet version of the Fourth program above.

import java.awt.*;
import java.applet.Applet;

public class Fifth extends Applet
{
    GridLayout gridlay = new GridLayout(1,2);
    Picture picture;
    Graph graph;

    public void init()
    {
        System.out.println("Fifth.init");
        picture=new Picture();
        graph=new Graph();
        setLayout(gridlay);
        add(picture);
        add(graph);
    }

    public void destroy()
    {
        System.out.println("Fifth.destroy");
    }
}

The line at the top

import java.applet.Applet;

is required to make the Applet class available. The Fifth class defined extends the Applet class and includes just two methods, init() and destroy(). There is now no main() method. The code which creates the instance of the Fifth (Applet extension of panel) class is inside the WWW browser or viewing program. The init() method is evoked by the driving program just after the applet Panel is created and the destroy() method is evoked to clean up just before the applet is completed. In the init() method the Panel methods setLayout() and add() don't require a prefix since they refer to this instance of the class (which has already been created). This is different from the main() method in the Fourth application in which the instance of the Fourth class was created as app within the main() method itself. If you are using threads (a thread is a sequence of steps a bit like a mini program) then there are three other methods that can be included in the Applet class to control what is going on, start(), run() and stop(). This will be discussed later.

You cannot use the java interpreter to run the applet since there is no main() method. Instead you must write an HTML (Hyper Text Markup Language) file to run the applet. The minimum required is something like

<title>Fifth Java</title>
<hr>
<applet code="Fifth.class" width=600 height=300>
</applet>
<hr>

The <title> tag defines the text which will appear at the top of the browser window. <hr> draws a horizontal line across the browser page so that you can see the area occupied by the applet Panel. The width and height parameters specifiy the size of the Panel in pixels.

14.1  Task - applet Fifth

Type the applet code into a file called Fifth.java and compile it with javac in the usual way. Then type the HTML code into a file called Fifth.html. You can run the program either by opening the HTML file from within the netscape browser or by using the appletviewer utility

harrier$ appletviewer Fifth.html

The viewer is useful because it logs on your screen the files it is opening so you can see how the java program is executed. When the applet is up and running you can control it using the pull-down menu available in the top left of the screen. Use the quit command to return to the normal shell prompt.

Task 9 completed on:   demonstrator:

15  Writing your own Java Program

It is now time for you to try and write a Java program (with a little help). We are going to look at a programming task which is nothing to do with ray tracing so that you start to get a feel for how you can approach different problems using OOP. The idea is to plot a picture of a complex set, for example a Julia set or the Mandelbrot set.

First of all we need to define what such a set is! A complex set is a set of points (usually an infinite number of points) on the complex plane (i.e. in an Argand diagram). Associated with a complex set is some formula which is used to decide whether or not a point on the complex plane (i.e. a complex number) is in the set. The most interesting complex sets are produced by some iterative formula and are fractal in nature. You don't need to know this but the Mandelbrot set is a set of Julia Sets.

A picture or rendering of such a set can be produced by using the appropriate formula over a raster or grid of points that cover some portion of the complex plane. If a point is found to be in the set then we colour the pixel at that point black and if it is not in the set we colour the pixel at that point white. The character and accuracy of the rendering depends on the formula used to define membership of the set.

So where do we start with OOP? What we probably need is a class that defines a particular type of complex set, a class to render (plot) a complex set and finally an applicaton class, with a main() method, which creates an instance of the set and an instance of the rendering. The essence of any rendering of a complex set is a procedure (or method since we are using Java) which determines whether a complex number z is in the set or not. So we can start by defining an interface that declares a method of the form

        boolean inset(double[] z)

This is expected to return true if the complex number z is in the set and false if not in the set.

15.1  Task - interface ComplexSet

Create an interface called ComplexSet (in a file called ComplexSet.java) which declares such a method. Also declare a void println() method which is intended to list details of the complex set. You can use the interface Bounds above as the model for this interface.

The ComplexSet interface is intended to be implemented by any class which defines a complex set. The purpose of the interface is to force the programmer to provide exactly the same form of methods for every set coded. It also means that the programmer can write and compile code which uses the methods declared in the interface before the interface has been implemented.

Task 10 completed on:   demonstrator:

15.2  Task - class Mandelbrot

Next we need a formula which defines the Mandelbrot set. The simplest is as follows. Take a complex number z=(xst,yst) where xst is the real part and yst is the imaginary part. Then repeatedly calculate a new complex number using the formulae

        xnew=xold*xold-yold*yold-xst
        ynew=2*xold*yold-yst

Start with xold=xst and yold=yst and after each iteration calculate the modulus squared of the result

        r=xnew*xnew+ynew*ynew

Repeat this until

        r>rescape     or iteration number > maxiter

where rescape is of order 1.e8 (the square of 1.e4) and maxiter is about 100. If r is greater than the escape radius during iteration then the point z is not in the set, otherwise it is in the set.

Write a class Mandelbrot (in a file called Mandelbrot.java) that implements the interface ComplexSet using the above iterative scheme. Include a constructor that allows you to set the rescape and maxiter values when you create an instance of the class. Use the println() method to list out the name of the set and these values. You might use the class Rectbound above as a model for this class. You can use a loop of the form

        for(int i=0;i<maxiter;i++)
        { 
            ...
            if(r > rescape)
                 return false;
            ...
        }
        return true;

to perform the iteration where the if() will terminate the definite loop prematurely in cases where the point has escaped.

Task 11 completed on:   demonstrator:

15.3  Task - class RenderSet

Now we come to the problem of plotting out the raster of points. This can be done by a class similar to Picture or Graph above, using the methods provided by the Figure class. The heart of the class required is the paint() method which needs to look like the following.

    public void paint(Graphics g)
    {
        setWindow(0.0,1.0,0.0,1.0);
        mapping("scale",x0,x1,y0,y1);
        setColor(Color.black);
        box();
        while(nextPixelPos())
        {
             if(set.inset(getPixelPos()))
             {
                  drawPixel();
             }
        }
    }

The setWindow() method redefines the plotting window (to the maximum in this case). The mapping() method sets up a mathematical coordinate system over the window. In this case this defines the area of the complex plane covered by the plot. The ßcale" option ensures that the scaling of the x-axis (real-axis) and y-axis (imaginary axis) is the same. The setColour() and box() methods select a drawing colour and put a frame around the defined window. The method nextPixelPos() selects the next pixel in the raster returning false if the raster is complete. The set.inset() method determines whether or not the current pixel position (in mathematical coordinates) returned by getPixelPos(), is a member of the set or not. Finally the drawPixel() method colours the current pixel.

Write a class called RenderSet in file RenderSet.java which extends Figure and plots a picture of a complex set. The data fields in the class must include

        private ComplexSet set;

and x0, x1, y0, y1 for the mapping. You should write a constructor for the class that initializes the set and the mapping ranges. This will look something like the constructor in the Surface class above.

Task 12 completed on:   demonstrator:

15.4  Task - application Sixth

Finally we require a Sixth class with a main() method to actually plot the result. Write a Sixth class to plot a picture of the Mandelbrot set. You can use the Fourth or Fifth programs above as a model. You should use a frame size of at least a few hundred pixels in each axis and a mathematical coordinate range -1.0 to 2.0 and -1.5 to 1.5 for the real and imaginary axes to include the complete set. So your main() method should contain lines like:

        app.setSize(500,500);
        Mandelbrot Cset = new Mandelbrot();
        RenderSet picture = new RenderSet(Cset,-1.0,2.0,-1.5,1.5);

Task 13 completed on:   demonstrator:

15.5  Task - class Julia

A Julia set is defined by a very similar iterative scheme. The only difference is that the start position in the iteration is replaced by a complex constant cs=(cr,ci):

        xnew=xold*xold-yold*yold+cr
        ynew=2*xold*yold+ci

The iteration still starts with xold=xst and yold=yst.

Write a Julia class in file Julia.java which implements the ComplexSet interface. This will require a few trivial changes to the Mandelbrot class already written. Your constructor should include the constant cs. Modify your Sixth.java file to plot a Julia set rather than the Mandelbrot set. My favourite Julia set has cs=(-0.74543,0.11301) and requires a range (-2.0,2.0,-1.5,1.5).

You will notice that the pictures you get of the complex sets are rather indistinct. If you try plotting a smaller area of the complex plane at the boundary of the set greater complexity is revealed. In fact, however far you try to zoom in on the boundary, more complexity becomes apparent.

There are other properties of such sets which can be exploited to provide more revealing renderings. In particular there is an algorithm for approximating the distance to the set from points which are not in the set and there is another algorithm for calculating the radius of a circle centred on points not in the set which is guaranteed not to intersect the set. Both these could be included as methods in the ComplexSet interface.

Using these other properties the renderings can be more than just black and white pictures. Sets can be rendered as pictures of 3-D surfaces of one form or another. Thus the RenderSet class could be modified/extended to produce different types of plot.

The structure imposed by OOP in Java makes enhancements and modifications like these rather easy.

Note that we could have chosen to declare ComplexSet above as an abstract class rather than an interface. In this particular application in makes no difference. The advantage of interfaces is that a class can implement more than one interface and an interface can extend more than one interface. i.e interfaces provide Java with multiple inheritance.

Task 14 completed on:   demonstrator:

16  Surface quality in the ray tracing problem

So far we have looked at the geometry and boundaries of surfaces and the intersection of rays or particles with the surfaces. We now need to consider the scattering, absorption and emission from the surfaces. This depends on the surface quality.

The best way to incorporate this aspect is to modify our abstract definition of a surface to include surface properties. We can do this by extending the Surface class to a new SurfaceQ class. The extended definition below includes a surface normal at the intersection point, a Squality interface which is passed to the object as a constructor argument and new methods natp(), scatter(Particle p) and emit().

/** Surface geometry and quality*/
abstract class SurfaceQ extends Surface
{
        public Vector intnorm; // normal at intersection set by method natp()
				// natp() will depend on surface type
        Squality qual; // surface properties
        SurfaceQ(Vector pos, Vector norm, Vector axis, Bounds bds, Squality sqa)
        {
                super(pos,norm,axis,bds);
                qual=sqa;
        }
        public void println()
        {
                super.println();
                qual.println();
        }
        abstract protected void natp(); // find normal at position
        public boolean scatter(Particle p)
        {
                return qual.scatter(this,p);
        }

        public Particle emit()
        {
                return qual.emit(this);
        }
}        

The surface quality interface is given below. The scatter method has arguments SurfaceQ and Particle since the scattering or absorption depends on both the interacting components. The emit method only has a SurfaceQ argument to determine the direction of emission. The properties of particles emitted will depend on the implementation of the interface.

/**
 * surface quality responsible for scattering, absorption and emission of
 * particles
 */
interface Squality
{
        /** scatter or absorb particle */
        boolean scatter(SurfaceQ s,Particle p); 
        Particle emit(SurfaceQ s); // emit particle
        void println(); // list details of surface quality
}

Now we can implement the Squality interface to create a Mirror, Laser (parallel source beam) and Detector class.

/** Perfect mirror surface quality */
class Mirror implements Squality
{
        public boolean scatter(SurfaceQ s, Particle p)
        {
            // Find reflection angle at intersection position
            double cosa=p.direction.dot(s.intnorm);
            // In this case reflection angle equals incidence angle
            double cosb=cosa;
            double cosab=cosa+cosb;
            // find change in direction
            Vector change = s.intnorm.scale(cosab);
            // apply change, normalise and overwrite original direction 
            Vector rdir=p.direction.subtract(change);
            p.direction=rdir.normalise();
            // update origin of particle
            p.position=s.intpos;
            return true;
        }
        public Particle emit(SurfaceQ s)
        {
            throw new RuntimeException(
            "Mirror.emit: attempt to emit from a mirror surface");
        }
        public void println()
        {
              System.out.println("perfect mirror surface");
        }
}

The geometry of the reflection used in the scatter() method is illustrated in figure 3.

fig_scat.gif
Figure 3: Refection from a plane surface

/** Parallel beam source */
class Laser implements Squality
{
        public boolean scatter(SurfaceQ s, Particle p)
        {
             throw new RuntimeException(
             "Laser.scatter: attempt to scatter from Laser");
        }
        public Particle emit(SurfaceQ s)
        {
            // emission along surface normal
            Vector pos = s.getran();
            Vector dir = s.intnorm;
            return new Particle(pos,dir);
        }
        public void println()
        {
              System.out.println("parallel beam source");
        }
}

16.1  Task - class Detector

Write the Detector class in file Detector.java which implements Squality in a similar way to Laser and Mirror above. The scatter() method should set the particle energy to zero and return false indicating that the particle has been absorbed (i.e. detected). The emit() method should throw a RuntimeException since we don't want our detector to emit particles, and the println() method should just list "perfect detector" or some similar text. You will need the SurfaceQ and Squality class files to compile your Detector class. Copy them from

/disk/s/zrw/under/java/SurfaceQ.java
/disk/s/zrw/under/java/Squality.java

Finally we must create a new PlaneQ surface class to reflect the changes in the Surface class. Note we cannot extend the Plane class since this extended the Surface rather than the SurfaceQ class. We need to modify the constructor and the println() method and include a natp() method as shown below. We also need to change the intersect() method so that it scatters the particle if the intersection is in range. The rest remains as it was in the Plane class.

/** Plane surface with quality */
class PlaneQ extends SurfaceQ
{
    PlaneQ(Vector pos, Vector norm, Vector axis, Bounds bds, Squality sqa)
    {
            super(pos,norm,axis,bds,sqa);
    }
    
    ...

    public void natp()
    {
            intnorm=refnorm;
    }

    ...
    
    protected boolean intersect(Particle p)
    {
            // Check energy of particle
                   if(p.energy==0.0)
    ...

            // Check within limits
                if(edge.within(intcoord))
                {
                    scatter(p);
                    return true;
                }
                else
                    return false;
    ...
    }

    public void println()
    {
            System.out.println("Planar surface with quality");
            super.println();
    }
}

Task 15 completed on:   demonstrator:

16.2  Task - defining a source, a mirror and a detector

Study these definitions carefully. Write sections of code in your notebook to:

Task 16 completed on:   demonstrator:

17  Completing the ray tracing problem

The classes which define the components of the ray tracing problem are now complete but we still need some way of tracing particles or rays through a sequence of components. For example a simple optical bench set up might consist of a source, mirror and detector as shown in figure 4.

fig_test.gif
Figure 4: Test bench arrangement for ray tracing

What we need is an abstract class which can ray trace this sort of generic optical bench. The RayTrace class below does this.

/** Ray tracing of a set of surface components */
abstract class RayTrace
{
    public int ncomponent; // number of surface components
    public int nray; // number of rays to be traced

    public SurfaceQ[] component; // surface components to be ray traced
    public double xpos[][]; // xpos, ypos and zpos are the intersection
    public double ypos[][]; // points along the ray
    public double zpos[][]; 
    public double xloc[][]; // xloc and yloc are the local coordinates
    public double yloc[][]; // of the intersection points on components
    public int npos[]; // number for positions for a particular ray

    Particle p; // the particle or ray traced

    RayTrace(int nc, int nr) // constructor to set up the arrays etc.
    {
        ncomponent=nc;
        nray=nr;
        component = new SurfaceQ[ncomponent];
        xpos = new double[nray][ncomponent];
        ypos = new double[nray][ncomponent];
        zpos = new double[nray][ncomponent];
        xloc = new double[nray][ncomponent];
        yloc = new double[nray][ncomponent];
        npos = new int[nray];
    }

    public void trace() // perform ray tracing through components
    {
        for(int i=0; i<nray; i++)
        {
            int j=0;
            p = component[j].emit();
            do
            {
                xpos[i][j]=component[j].intpos.cmpts[0];
                ypos[i][j]=component[j].intpos.cmpts[1];
                zpos[i][j]=component[j].intpos.cmpts[2];
                xloc[i][j]=component[j].intcoord[0];
                yloc[i][j]=component[j].intcoord[1];
                j++;
                npos[i]=j;
            }
            while((j<ncomponent) && (component[j].intersect(p)));
        }
    }
}

There is no undefined (abstract) method in RayTrace but it is declared abstract because the components to be traced are declared but not defined. This is the opposite situation to an interface in which only abstract methods and final static fields may appear. The method RayTrace.trace() is rather naive in that the sequence or order of components traced is fixed. However this is good enough to solve a large number of ray tracing problems. A more sophisticated tracing method could always be added later by extending the RayTrace class.

The RayTrace class uses arrays of arrays, for example

    public double zpos[][]; 
    ...
        zpos = new double[nray][ncomponent];

In this case the individual arrays are type double but you can have arrays of arrays of any type of data object. The number of arrays is given by the leftmost index, [nrays] in this case. The size of the individual arrays can be variable but in this case it is fixed, [ncomponent].

We can define a particular set of components by creating a new class which extends RayTrace. The following TestBench class sets up the bench shown in figure 4.

/** Simple test of ray tracing */
public class TestBench extends RayTrace
{
    TestBench(int nr)
    {
        super(3,nr);

        Laser las = new Laser();
        Vector lpos = new Vector(0.0,0.0,0.0);
        Vector ldir = new Vector(1.0,0.0,0.0);
        Vector lref = new Vector(0.0,1.0,0.0);
        Circbound lbeam = new Circbound(1.0);
        component[0] = new PlaneQ(lpos,ldir,lref,lbeam,las);

        Mirror mir = new Mirror();
        Vector mpos = new Vector(10.0,0.0,0.0);
        Vector mdir = new Vector(-1.0,-1.0,0.0);
        Vector mref = new Vector(0.0,0.0,1.0);
        Rectbound mlim = new Rectbound(-2.0,2.0,-2.0,2.0);
        component[1] = new PlaneQ(mpos,mdir,mref,mlim,mir);

        Detector det = new Detector();
        Vector dpos = new Vector(10.0,-10.0,0.0);
        Vector ddir = new Vector(0.0,1.0,0.0);
        Vector dref = new Vector(1.0,0.0,0.0);
        Rectbound dlim = new Rectbound(-3.0,3.0,-3.0,3.0);
        component[2] = new PlaneQ(dpos,ddir,dref,dlim,det);
    }
}

The following class PlotRaysXY can be used to plot the rays out. The details of the rays are contained in the RayTrace instr object which is an argument of the constructor.

import java.awt.*;
/** Plot rays from ray tracing in XY plane */
public class PlotRaysXY extends Figure
{
    double xmin;
    double xmax;
    double ymin;
    double ymax;
    RayTrace instrument;

    PlotRaysXY(RayTrace instr, double xlo, double xhi, double ylo, double yhi)
    {
        instrument=instr;
        xmin=xlo;
        xmax=xhi;
        ymin=ylo;
        ymax=yhi;
    }

    public void paint(Graphics g)
    {
        mapping("scale",xmin,xmax,ymin,ymax);
        box();
        setColor(Color.red);
        for(int i=0; i<instrument.nray; i++)
        {
            drawCurve(instrument.npos[i],instrument.xpos[i],instrument.ypos[i]);
        }
    }
}

17.1  Task - class PlotLocal

Write a PlotLocal class which plots the intersection points of all the rays for a specified surface component. This class will be very similar to PlotRaysXY above. Pass the index of the surface component to the class as an argument of the constructor. Check that this argument is not out of range, i.e. not less than zero or greater than the highest component index. Remember Java array indices run from 0 to N-1.

The start of the constructor should look something like

    PlotLocal(RayTrace instr, int ic,
    double xlo, double xhi, double ylo, double yhi)
    {
         instrument=instr;
         icom=ic;
         ...

The heart of the paint() method should contain a loop like:

        for(int i=0; i<instrument.nray; i++)
        {
            if((instrument.npos[i]-1)>=icom)
                drawOval(instrument.xloc[i][icom],instrument.
                yloc[i][icom],0.0,0.0);
        }

You will need the RayTrace.java class file to compile your PlotLocal class. You can copy this from

/disk/s/zrw/under/java/RayTrace.java

Task 17 completed on:   demonstrator:

17.2  Task - application Seventh

Now, at long last, we can try out the complete ray tracing of a simple sequence of surfaces. The following Seventh class does just that.

import java.awt.*;
public class Seventh extends Frame
{
    public Seventh(String title)
    {
        super(title);
    }

    public static void main(String args[])
    /* main method to execute Seventh as an application */
    {
        System.out.println("Seventh start");
        Seventh app=new Seventh("Seventh Java");
        app.setSize(800,400);

        TestBench bench = new TestBench(100);
        bench.trace();

        PlotRaysXY rays = new PlotRaysXY(bench,-5.,15.,-15.,5.);
        PlotLocal dets = new PlotLocal(bench,2,-3.,3.,-3,3.);
        GridLayout gridlay = new GridLayout(1,2);

        app.setLayout(gridlay);
        app.add(rays);
        app.add(dets);
        app.show();
        System.out.println("Seventh stop");
    }
}

Copy your Fourth.java file to create Seventh.java, edit it to look like the above, compile it and try it out. You will need the following files in addition to the others you have already copied.

/disk/s/zrw/under/java/Mirror.java
/disk/s/zrw/under/java/Laser.java
/disk/s/zrw/under/java/PlaneQ.java
/disk/s/zrw/under/java/TestBench.java
/disk/s/zrw/under/java/PlotRaysXY.java

You should have written the Detector.java and PlotLocal.java files yourself. There is quite a lot going on in the Seventh program although the encapsulation of OOP makes it look deceptively simple. When you run the program you should get two pictures, one of the rays traced and one of the detected distribution. Refer back to section 6 and check how the Seventh program implements our original description of the ray tracing problem.

Task 18 completed on:   demonstrator:

18  Introducing interactive graphics

Java was developed to provide interactive graphics and a Graphical User Interface (GUI) for use on the WWW and in similar environments. The AWT includes all the usual components you would expect, buttons, menus, scroll bars etc.. Here we will use buttons to introduce some of the principles involved in programming a GUI.

We are going to include code which provides a way of shifting and rotating surface components in the ray tracing. This will require some form of joy stick that is constucted using buttons arranged in a square within a Panel. A Panel is a convenient AWT component that can be added to an Applet window or a application Frame. The following code sets up a class JoyStick.

import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

public class JoyStick extends Panel
{
    JoyStick(ActionListener al, String label)
    {
        Button b1 = new Button("LU");
        Button b2 = new Button("U");
        Button b3 = new Button("RU");
        Button b4 = new Button("L");
        Button b5 = new Button(label);
        Button b6 = new Button("R");
        Button b7 = new Button("LD");
        Button b8 = new Button("D");
        Button b9 = new Button("RD");
        b1.setActionCommand("LU");
        b2.setActionCommand("U");
        b3.setActionCommand("RU");
        b4.setActionCommand("L");
        b5.setActionCommand("Stop");
        b6.setActionCommand("R");
        b7.setActionCommand("LD");
        b8.setActionCommand("D");
        b9.setActionCommand("RD");
        b1.addActionListener(al);
        b2.addActionListener(al);
        b2.addActionListener(al);
        b3.addActionListener(al);
        b4.addActionListener(al);
        b5.addActionListener(al);
        b6.addActionListener(al);
        b7.addActionListener(al);
        b8.addActionListener(al);
        b9.addActionListener(al);
        GridLayout gridbut = new GridLayout(3,3);
        setLayout(gridbut);
        add(b1);
        add(b2);
        add(b3);
        add(b4);
        add(b5);
        add(b6);
        add(b7);
        add(b8);
        add(b9);
    }
    public double[] getJoyStick(ActionEvent ev)
    {
        String ob = ev.getActionCommand();        
        double dd=1.0;
        double shift[] = new double[2];
        if(ob.equals("LU"))
        {
            shift[0]=-dd;
            shift[1]=dd;
        }
        if(ob.equals("U"))
        {
            shift[0]=0.0;
            shift[1]=dd;
        }
        if(ob.equals("RU"))
        {
            shift[0]=dd;
            shift[1]=dd;
        }
        if(ob.equals("L"))
        {
            shift[0]=-dd;
            shift[1]=0.0;
        }
        if(ob.equals("Stop"))
        {
            shift[0]=0.0;
            shift[1]=0.0;
        }
        if(ob.equals("R"))
        {
            shift[0]=dd;
            shift[1]=0.0;
        }
        if(ob.equals("LD"))
        {
            shift[0]=-dd;
            shift[1]=-dd;
        }
        if(ob.equals("D"))
        {
            shift[0]=0.0;
            shift[1]=-dd;
        }
        if(ob.equals("RD"))
        {
            shift[0]=dd;
            shift[1]=-dd;
        }
        return shift;
    }
}

Each button for the joy stick is created in the constructor JoyStick(). For example

        Button b1 = new Button("LU");
        ...
        b1.setActionCommand("LU");
        ...
        b1.addActionListener(al);

The text string "LU" will be the label on the button. The setActionCommand("LU") method sets the text string which will be returned as the command when the button is pressed and the addActionListener(al) method sets the object which will handle the button event. In JoyStick this object is passed as an argument of the constructor. The central button is labelled with a string passed as an argument of the constructor so that the whole joystick can be given a suitable name. This central button produces a "Stop" action command and a zero shift which could be used to toggle something. The GridLayout class is then used to arrange the buttons in a 3 by 3 square.

The method getJoyStick(ActionEvent ev) gets a shift up-down and left-right from the joy stick. This method will be evoked from within the code that handles the event generated when a button is hit. In this case this method is expected to be in object al as set by the addActionListener() method. The assignment

        String ob = ev.getActionCommand();        

gets the command string for the event. This will be the string set by the setActionCommand() method as described above.

Next we need a way of moving the surface elements. We can do this for all surfaces if we introduce a new class SurfaceQM which extends SurfaceQ.

/** Surface geometry, quality and movement*/
abstract class SurfaceQM extends SurfaceQ
{
    SurfaceQM(Vector pos, Vector norm, Vector axis, Bounds bds,
    Squality sqa)
    {
       super(pos,norm,axis,bds,sqa);
    }

    // tilt the surface normal
    // delta[0] is rotation in radians about reference axis (assumed small)
    // delta[1] is rotation in radians about other axis (assumed small)
    public void tilt(double delta[])
    {
        // calculate other reference axis vector
        Vector other = refnorm.cross(refaxis);
        // apply tweek along reference axis and other
        Vector dref = refaxis.scale(delta[1]);
        Vector doth = other.scale(delta[0]);
        // add tweek to the original normal and create unit vector
        refnorm=refnorm.add(dref.add(doth));
        refnorm=refnorm.normalise();
        // now find new reference axis
        other = refnorm.cross(refaxis);
        refaxis=other.cross(refnorm);
        refaxis=refaxis.normalise();
    } 

    // shift the surface reference position
    public void shift(double delta[])
    {
    ...
    }

    // rotate about the surface normal
    // delta in radians (assumed small)
    public void rotate(double delta)
    {
    ...
    }
}

The method tilt() uses two small rotation angles in radians (provided by the JoyStick as you will see) and changes the direction of the mirror normal as shown in figure 5.

fig_tweek.gif
Figure 5: Geometry employed by the tilt() method.

The methods shift() and rotate() are intended to translate the reference position and rotate the surface about the normal as shown in figure 6.

fig_shrot.gif
Figure 6: Geometry employed by the shift() and rotate() methods.

18.1  Task - class SurfaceQM

Create a new class SurfaceQM as described above and implement the shift() and rotate() methods as indicated in figure 6.

In order to use the SurfaceQM class we need new versions of the PlaneQ, RayTrace, TestBench, PlotLocal and PlotRaysXY classes.

Task 19 completed on:   demonstrator:

18.2  Task - using class SurfaceQM

Create new classes PlaneQM, RayTraceM, TweekBench, PlotLocalM and PlotRaysXYM that use the SurfaceQM class in place of the SurfaceQ class.

Task 20 completed on:   demonstrator:

18.3  Task - applet Eighth

We will now bring the JoyStick and TweekBench together in an Applet which will tilt the mirror in our test bench set up.

import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.applet.Applet;
public class Eighth extends Applet implements ActionListener
{
    JoyStick tweek;
    TweekBench bench;
    PlotLocalM plotlocal;
    PlotRaysXYM plotrays;

    public void init()
    {
        System.out.println("Eighth start");
        GridLayout gridlay = new GridLayout(2,2);
        setLayout(gridlay);

        bench = new TweekBench(100);
        plotlocal = new PlotLocalM(bench,2,-3.,3.,-3,3.);
        plotrays = new PlotRaysXYM(bench,-5.,15.,-15.,5.);
        tweek = new JoyStick(this,"mirror tilt");

        add(plotrays);
        add(plotlocal);
        add(tweek);
    }
    public void actionPerformed(ActionEvent ev)
    {
        double delta[] = tweek.getJoyStick(ev);
        delta[0]=-delta[0]*0.01;
        delta[1]=-delta[1]*0.01;
        bench.component[1].tilt(delta);
        bench.trace();
        plotrays.repaint();
        plotlocal.repaint();
    }
}

Note that the Eighth class implements the ActionListener interface. The Applet window contains three components, two plotting panels plotrays and plotlocal, and a JoyStick panel called tweek. The JoyStick(this,"mirror tilt") constructor passes this, i.e. the object Eighth, as the ActionListener object. The line

        bench.trace();

performs a ray tracing of 100 rays.

If a button is hit with the mouse then an event is generated and the Applet code passes this event to the actionPerformed() method. This method is required by the ActionListener interface. The actionPerformed() method gets the delta associated with the event using tweek.getJoyStick(ev), scales the delta to give suitable angles in radians, tilts the mirror using the tilt method of the mirror (component[1] in our test bench) and traces another 100 rays. Finally it repaints the pictures.

Copy the Eighth program and JoyStick code from

/disk/s/zrw/under/java/Eighth.java
/disk/s/zrw/under/java/JoyStick.java

Compile the code in the usual way and create an Eighth.html file to run the applet and try it out with the appletviewer. Make sure you understand how the event handler for a button is set up and how the event generated, when a button is hit with the mouse, is handled. Write down the sequence of methods that is evoked.

By adding a relatively simple extension to our SurfaceQ class, to create the new SurfaceQM class, we have introduced the possibility of tilting, shifting and rotating ALL surfaces which we create within the ray tracing. Unfortunately adding extra functionality like this can mean that other code has to be modified. This is why it is important that you try and plan all the functionality you require at the start. The power of OOP is only realised if the appropriate effort is expended in the planning/design phase.

Task 21 completed on:   demonstrator:

19  Ray tracing spherical surfaces

Ray tracing is much more interesting if the reflecting surfaces are curved so we shall now implement the simplest curved surface, a sphere. All that is required is a new class, SphereQM, which extends SurfaceQM in the same way that PlaneQM does. One extra parameter, the radius, is required. All the others are the same as for the plane surface.

The constructor of our SphereQM class must save the radius. This radius will subsequently be used by methods to find the centre of the sphere, subtracting the reference normal scaled by the radius from the reference position.

      centre=refpos.subtract(refnorm.scale(radius));

The intersect(Particle p) method must be changed to find the intersection of a ray (particle) with a sphere. The geometry required is shown in figure 7.

fig_sphere.gif
Figure 7: Intersection of a ray with a sphere.

The distance s and direction from the origin of the ray, p.position, to the centre can be found. The cosine of the angle alpha can then be calculated from the dot product of this vector and p.direction. As the distance d changes so the distance v varies as a quadratic in d given by the cosine rule

      v**2 = d**2 - 2.d.s.cos(alpha) + s**2

For intersection we require this to be equal to the square of the radius. Therefore if we solve the quadratic when v=radius we can find the two intersection points. We must be careful to cater for cases when there are no intersection points (complex roots) and only one intersection point (perfect square). Having found these points we must determine which point is nearest the p.position in a positive direction along the ray and which is also within the boundary defined on the surface.

The conversion from a position vector on the surface to local coordinates must be performed by the ptoc() method and the inverse conversion from local coordinates to a position vector by the method ctop(). The easiest approach is to project the position vector into the tangent plane as shown in figure 8.

fig_tangent.gif
Figure 8: Projection of position vector into tangent plane.

The distance dp is given by

       dp=radius(1-cos(beta))

The cosine of beta can be found by taking the dot product of the reference normal and the unit vector from the centre to the intersection position. If this cosine is negative then the intersection point is at a great circle distance > 90 degrees from the reference position and dp will be greater than the radius. Such positions should be given a local coordinate which is suitably large and therefore beyond any defined boundary. The position on the tangent plane can be found by adding a refnorm vector scaled by dp to the intersection position. Then the vector a can be found by taking the difference between this vector and the reference position. Finally the local x and y positions can be calculated by projecting the a vector onto the refaxis and other axis using the dot product. The inverse is achieved in similar fashion.

So ptoc() requires the following steps:

Similarly ctop() requires:

The natp() method which calculates the normal at the intersection point is easy.

      centre=refpos.subtract(refnorm.scale(radius));
      intnorm=intpos.subtract(centre);
      intnorm=intnorm.normalise();

When you are debugging your code you may find it useful to include System.out.println() calls within the above methods so that you can see the intermediate results of your calculations.

19.1  Task - class SphereQM, application Ninth

Write a SphereQM class as outlined above above using PlaneQM as a template. Don't forget to modify the println() method to list the radius and centre. Create a new SphereBench class (by editing the TestBench class) which uses a spherical mirror instead of a plane mirror. Finally create a Ninth.java program by editing the Seventh.java file to test your new SphereQM class. Try and choose a radius of curvature so that rays are brought to a focus on the detector of our test bench.

Because the mirror is set at 45 degrees to the beam the focus suffers from spherical distortion and the focussing is astigmatic. The best focus for meridional rays is very different to the best focus for sagittal rays.

Although the geometry is a little tricky you can now see that adding a new surface type is relatively easy. Very little new code has to be produced and the new surface type integrates into all the other software without any need for modifications. This is the advantage of OOP.

Task 22 completed on:   demonstrator:

20  Further interaction

In addition to Buttons there are a number of other AWT GUI components which provide interaction; Checkboxes, Choices, Dialogs, Lists, Menus, Scrollbars, Scroll Panes and TextFields.

Suppose we require some form of sliding control which can be used to position the detector on our test bench. This can be provided using a combination of a Scrollbar and TextField. The interactive control required is shown in the class Slider defined below.

import java.awt.*;
import java.awt.event.*;

public class Slider extends Panel
{
    Scrollbar slide;
    Label label;
    TextField text;
    int max=1000;
    int block=10;
    double smin;
    double scale;


    Slider(AdjustmentListener al, String title, double sm, double sx)
    {
        smin=sm;
        scale=(sx-sm)/((double)(max));
        slide = new Scrollbar(Scrollbar.HORIZONTAL);
        slide.setMaximum(max+block);
        slide.setBlockIncrement(block);
        slide.addAdjustmentListener(al);
        label = new Label(title, Label.CENTER); 
        text = new TextField(String.valueOf((float)smin)+" ",10);

        GridLayout gridsl = new GridLayout(3,1);
        setLayout(gridsl);
        add(label);
        add(text);
        add(slide);
    }
    public double value(AdjustmentEvent e)
    {
        double val = ((double)e.getValue())*scale+smin;
        text.setText(String.valueOf((float)val+" "));
        return val;
    }
}

The Slider consists of 3 horizontal areas, a text Label (just to give the thing a title), a TextField used to indicate the numerical position of the Slider and a ScrollBar used to adjust the Slider.

The first 2 arguments of the constructor are similar to those of the JoyStick and define the object which is going to act as a listener for events from the ScrollBar and the title used in the Label. The other arguments, double sm, double sx are used to define the range of floating point numbers associated with the Slider control.

A ScrollBar actually works with an integer range, set to max+block in the constructor above and an integer step size, block, which defines how much the device shifts when using the mouse. The block is added to the max value in the setMaximum(max+block) method so that the floating point range ends up as exactly sm to sx.

The line

        text = new TextField(String.valueOf((float)smin)+" ",10);

creates and initializes the TextField to the minimum value of the Slider. The method String.valueOf() generates a String which represents the floating point value. The (float) cast has been used to limit the number of significant figures used. The 10 defines the minimum width of the TextField.

The line

        slide.addAdjustmentListener(al);

sets the object al as the listener for the ScrollBar control. The object al must implement the interface AdjustmentListener and must therefore include a method called

    public void adjustmentValueChanged(AdjustmentEvent e)

The remaining lines in the constructor simply add the Label, TextField and ScrollBar to the Panel using a 3 line grid.

The method value(AdjustmentEvent e) is used to calculate the position of the Slider, converting from the integer value returned by e.getValue() method into a floating point number. This double value is converted to a String and inserted in the TextField and returned as the value of the method. It is to be expected that the method value() will be evoked by the adjustmentValueChanged() method.

We can put the Slider class to work by modifying the Eighth program above. The following indicates the additional code required.

import java.awt.*;
import java.awt.event.*;
import java.applet.Applet;
public class Tenth extends Applet implements ActionListener,
                                             AdjustmentListener
{
    ...
    SphereBench bench;
    Slider slip;
    ...
    public void init()
    {
        ...
        Panel sub = new Panel();
        GridLayout subgrid = new GridLayout(4,1);
        sub.setLayout(subgrid);
        ...
        bench = new SphereBench(100);
        ...
        slip = new Slider(this,"detector y position",-15.,-5.0);
        ...
        sub.add(slip);
        add(sub);
    }
    ...
    public void adjustmentValueChanged(AdjustmentEvent e)
    {
        double newpos = slip.value(e);
        double oldpos = bench.component[2].refpos.cmpts[1];
        double delta[] = new double[3];
        delta[0] = 0.0;
        delta[1] = newpos-oldpos;
        delta[2] = 0.0;
        bench.component[2].shift(delta);
    }
}

You can now see how the adjustmentValueChanged() method is set up. When the ScrollBar in the Slider is moved with the mouse this method is evoked and the slip.value(e) call gets the new position of the slide (and also updates the TextField). The shift required is then calculated and the detector component of the bench shifted by the appropriate amount.

20.1  Task - applet Tenth

Create a Tenth.java program by editing the Eighth.java file using the code indicated above and try out the Slider control.

You should now have a good idea of how the event handling model of the Java AWT works. All the interactive components that create events are handled in a similar fashion.

A Choice component provides an easy way of choosing between options using a pull-down menu. Suppose we want to be able to switch between a plane mirror and spherical mirror on our optical bench. This can be done using a Choice.

Task 23 completed on:   demonstrator:

20.2  Task - class Choice

Lookup how to use the Choice component in the java.awt at

http://java.sun.com/products/jdk/1.1/docs/api/packages.html

Modify the Tenth.java program to allow you to switch between a plane mirror and a spherical mirror. It is probably easiest if you create a new bench class called ChoiceBench say, which defines both a plane and spherical mirror.

Task 24 completed on:   demonstrator:

21  Threads and multi-tasking

A thread is a series of steps executed in sequence, so the procedural programs you are familiar with are single threads. Actually more than one thread is active even when you are running the simple Java programs described above but all the threads bar your program are hidden in the background. When running an Applet the init() method is called by the driving thread and thus init() constitutes the visibly active thread (at least at the start). When running an Application the main() method assumes this role.

Java provides facilities so that the programmer can create, start and stop a number of asynchronous threads to implement multi-tasking. These threads can then run simultaneously or at least in a fashion that appears to be simultaneous because, of course, the different threads are time sharing the same processor in most machines.

Threads are represented by instances of the class Thread. This class can be used in two ways. You can extend the Thread class and override the default run() method. The new class is then used by creating an instance of the class and calling the Thread start() method.

class MyThread extends Thread
{
    ...
    public void run()
    {
         ... \\ code here to do whatever is required of the thread
    }
    ...
}
...
MyThread t = new MyThread();
t.start();

Alternatively you can use the Runnable interface that declares one abstract method run(). In this case you must create a class that implements the Runnable interface and which therefore includes a run() method. Your class is then wrapped up (or Threaded if you want) in a Thread by creating an instance of a Thread, passing an instance of your class as a constructor argument. This avoids problems of multiple inheritance when you require your class to be both threaded and an extension of another class.

class MyThreadedClass implements Runnable
{
    ...
    public void run()
    {
        ... \\ code here to do whatever is required of the thread
    }
    ...
}
...
Thread t = new Thread(new MyThreadedClass());
t.start();

Suppose we want to trace rays through our simple optical bench while at the same time we want to tilt the mirror around continuously in a cyclic fashion. We can define a new class which continuously wobbles a surface and lists the position of the tilt.

import java.awt.*;
public class WobbleSurfaceQM extends Panel implements Runnable
{
    SurfaceQM surface;
    TextField text;
    static final double rtod = 57.2957795131;
    double amplitude;
    int maxstep = 10;

    public WobbleSurfaceQM(String title, SurfaceQM s, double ampdeg)
    {
        surface = s;
        amplitude = ampdeg/rtod;
        Label label = new Label(title+" (degrees)", Label.CENTER);
        text = new TextField("0.0 ",10);
        GridLayout gridlay = new GridLayout(2,1);
        setLayout(gridlay);
        add(label);
        add(text);
    }

    public void run()
    {
        double delta[] = new double[2];
        delta[0]=0.0;
        delta[1]=0.0;
        double step=amplitude/(double)(maxstep);
        int i=0;
        int ic=1;
        double theta;
        while(true)
        {
            try
            {
                 Thread.sleep(100);
            }
            catch (InterruptedException ignore)
            {
            }
            i=i+ic;
            if(i==maxstep)
            {
                 ic=-1;
            }
            if(i==-maxstep)
            {
                 ic=1;
            }
            delta[0]=step*(double)ic;
            surface.tilt(delta);
            theta=rtod*step*(double)i;
            text.setText(String.valueOf(theta)+" ");
        }
    }
}

The constructor sets up a Label and TextField within a Panel and the run() method wobbles the surface back and forth by a few degrees continuously updating the TextField to give the value of the tilt in degrees. It is intended that an instance of WobbleSurfaceQM is run as a thread. The Thread.sleep(100) method causes the thread to be marked as not re-schedulable for a number of milliseconds (100 in this case). This permits other threads to start running. It must be called within a try-catch block because a sleeping thread may be interrupted by an active thread. Usually no exception is thrown and the try{} clause will be completed, the thread will rest for at least 100 milliseconds and will resume after the catch(){} clause. The use of Thread.sleep() slows down the execution of the tilting loop and will allow the ray tracing loop (in the main thread) to execute faster.

We can use the class WobbleSurfaceQM in a program as follows:

import java.awt.*;
public class Eleventh extends Frame
{
    public Eleventh(String title)
    {
        super(title);
    }

    public static void main(String[] args)
    {
        Eleventh app=new Eleventh("Eleventh Main");
        app.setSize(600,600);
        GridLayout gridlay = new GridLayout(2,2);
        app.setLayout(gridlay);

        TweekBench bench = new TweekBench(2);
        WobbleSurfaceQM wobble = new WobbleSurfaceQM("mirror wobble",
        bench.component[1],5.0);
        PlotLocalM plotlocal = new PlotLocalM(bench,2,-3.,3.,-3,3.);
        PlotRaysXYM plotrays = new PlotRaysXYM(bench,-5.,15.,-15.,5.);

        app.add(plotrays);
        app.add(plotlocal);
        app.add(wobble);
        app.show();

        Thread t = new Thread(wobble);
        t.start();

        System.out.println("Starting Eleventh Main loop");
        while(true)
        {
                try
                {
                    Thread.sleep(100);
                }
                catch (InterruptedException ignore)
                {
                }
                bench.trace();
                plotrays.repaint();
                plotlocal.repaint();
        }
    }
}

21.1  Task - application Eleventh

Edit the Fourth.java program to create Eleventh.java and copy class WobbleSurfaceQM from

/disk/s/zrw/under/java/WobbleSurfaceQM.java

There is one further important consideration. In the Eleventh program above it is possible that the thread that is tracing and plotting the rays could be accessing surface data fields while the thread that is wobbling the surface is modifying the same data fields. In such a case we need to lock the data fields while they are being modified so that other threads can't use them during that time. This can be done using the synchronized statement which has the form:

        synchronized(object reference - data field)
        {
             code to be executed
        }

While the code embodied by the scope of the statement is being executed the Java scheduler will ensure that no other thread can access the object reference. For example we can lock the refnorm data field in our tilt() method:

        // add tweek to the original normal and create unit vector
        synchronized(refnorm)
        {
            refnorm=refnorm.add(dref.add(doth));
            refnorm=refnorm.normalise();
        }

You should modify the tilt(), rotate() and shift() methods in class SurfaceQM so that the surface data fields are locked while they are being modified. Actually if you don't include this modification the code will probably run correctly because the probability of a clash causing a problem is very small.

Compile the program and try all this out.

Task 25 completed on:   demonstrator:

21.2  Task - class SlideSurfaceQM

Create a new class SlideSurfaceQM which performs a similar function to WobbleSurfaceQM but shifts a surface back and forth along the direction of the normal. Modify the Eleventh program to use class SlideSurfaceQM with a spherical mirror.

Task 26 completed on:   demonstrator:

21.3  Task - wobbling the source and mirror simultaneously

Modify the Eleventh program so that both the source and the mirror wobble simultaneously!

Once again you can see that adding extra functions to OOP code is very easy providing the original design of the classes was carefully thought out.

In the above we have not worried about any synchronisation problems between threads and we have only locked data fields using the synchronized statement. If you have a problem in which the method in one thread relies on the completion of the method from another thread then you can synchronize the threads rather than just locking data fields. All you have to do is include the word synchronized in the declaration lines of the methods which must be mutually locked when running. Only threads which are in the same class can be declared synchronized.

Task 27 completed on:   demonstrator:

22  Using the classes available

If you have spent some time designing and testing a new set of interacting classes then you can produce quite sophisticated programs combining the power of your code with the classes in the API. For example we can produce a point like source of rays using a SphereQM surface and Laser Squality. This is equivalent to passing a parallel (laser) beam through a diverging (concave) lens.

22.1  Task - application Twelfth

Create a class PointBench in which the parallel beam source is replaced by a point source. You can use the class TweekBench as a starting point.

You will have noticed that the layout of components provided by class GridLayout is rather inflexible. A prettier result can be achieved using class GridBagLayout. Details of how to use this class can be found at:

http://java.sun.com/products/jdk/1.1/docs/api/java.awt.GridBagLayout.html
http://java.sun.com/docs/books/tutorial/uiswing/layout/gridbag.html

Create a program Twelfth.java by editing Eleventh.java using PointBench in place of TweekBench and GridBagLayout instead of GridLayout.

In all the applications above we have not provided a proper way of stopping the application and returning to the shell prompt. You can do this using a button which returns the ``quit'' command string (or something similar of your choice). In the actionPerformed() method which services the buttons you should execute the System.exit() method, for example:

    ...
    String ob = ev.getActionCommand();
    ...
    if(ob.equals("quit"))
    {
        System.out.println("quitting application");
        System.exit(0);
    }
    ...

The argument of the System.exit() call is the returned status which is set to zero (OK) above.

Task 28 completed on:   demonstrator:

23  Into the unknown

If you have worked your way through all the tasks above you will have a good idea of what Object-Oriented Programming involves. Here are a few tips to help you design your own OO programs in Java.

In this workshop we have placed the emphasis on the Object-Oriented aspects of Java programming and neglected many of the procedural programming facilities provided within the language. Furthermore we have only touched on the vast API which provides a wealth of classes for all manner of applications. However you should now know enough to be able to make use of the on-line documentation provided on the WWW. Here are some useful links:

The Java Language specification

http://java.sun.com/docs/books/jls/html/index.html

Java API Packages like java.applet and java.AWT

http://java.sun.com/products/jdk/1.1/docs/api/packages.html

The javadoc utility

http://java.sun.com/products/jdk/1.1/docs/tooldocs/solaris/javadoc.html

23.1  Task - Cellular Automata

A chess-doard cellula automaton is a process which generates a pattern of black and white squares on a rectangular grid. An initial row is seeded with a particular pattern and then some rule is used to convert this pattern into a new pattern on the next row. The same rule is then used again to generate the next row and so on.

The simplest first row is all zero (false) except for a single isolated 1 (true). A particularly simple but powerful form of rule is where the jth element of a row is determined by the (j-1)th, jth and (j+1)th values in the previous row:

M(k,j)=rule(M(k-1,j-1),M(k-1,j),M(k-1,j+1))

i.e. the M(k,j) is 1 or 0 (true or false, black or white, etc.) depending on the 3 elements above in the matrix. The so-called rule number 30 summarised below gives particularly interesting results.

   k-1 --> k
  1 1 1    0
  1 1 0    0
  1 0 1    0
  1 0 0    1
  0 1 1    1
  0 1 0    1
  0 0 1    1
  0 0 0    0

Write a Java application program to render the pattern produced by rule 30.

Task 29 completed on:   demonstrator:

23.2  Task - Recusive Triangles

A right-angled triangle with sides (1,2,Ö5) can be subdivided into 5 similar triangles as shown in figure 9.

fig_tri.gif
Figure 9: The subdivision of a (1,2,Ö5) triangle

Each of the component triangles can be so divided in turn. Following this recursion a complicated aperiodic pattern can be produced. Write a Java application program to draw the pattern resulting from a specified level (about 4 or 5) of recursion.

Task 30 completed on:   demonstrator:

24  Quick Java reference

24.1  Primitive types

boolean             logical true or false
char                16-bit Unicode 1.1.5 character
byte                8-bit signed two's complement integer
short               16-bit signed two's complement integer
int                 32-bit signed two's complement integer
long                64-bit signed two's complement integer
float               32-bit IEEE 754-1985 floating-point number
double              64-bit IEEE 754-1985 floating-point number

Casting is performed (if possible) by (type)expression where type is one of above.

24.2  Character escape sequences

Special characters in strings are defined by the following escape sequences.

\n            newline
\t            tab
\b            backspace
\r            return
\f            form feed
\\            backslash itself
\'            single quote
\"            double quote
\ddd          a char by octal value, where each d is one of 0-7

24.3  Arithmetic operators

+             addition (also used to concatenate strings)
-             subtraction
*             multiplication
/             division
%             remainder
++            increment (can be before or after operand)
--            decrement (can be before or after operand)

24.4  Relational and conditional operators

>             greater than
>=            greater than or equal to
<             less than
<=            less than or equal to
==            equal to
!=            not equal to

24.5  Logical operators

&&             logical AND
||             logical OR
!              logical NOT

24.6  Bitwise operators

&              bitwise AND
|              bitwise inclusive OR
^              bitwise exclusive or (XOR)
~              take unary bitwise complement
<<             unary shift bits left filling with zeros to right
>>>            unary shift bits right filling with zeros to left
>>             unary shift bits right filling with highest (sign) bit on left

24.7  class Math methods

sin(a)          sine(a) a in radians
cos(a)          cosine(a) a in radians
tan(a)          tangent(a) a in radians
asin(v)         arcsine(v) v in range -1.0 to 1.0
acos(v)         arccosine(v) v in range -1.0 to 1.0
atan(v)         arctangent(v) returned in range -pi/2 to +pi/2
atan2(x,y)      arctangent(x/y) returned in range -pi to pi
exp(x)          e^x
pow(y,x)        y^x 
log(x)          ln x (natural logarithm of x)
sqrt(x)         square root of x
ceil(x)         Smallest whole number >= x
floor(x)        Largest whole number <= x
rint(x)         return truncated integer value of x as double
round(x)        return (int)floor(x+0.5) for either float or double
abs(x)          return absolute value of any numeric type
max(x,y)        return larger of x and y for any numeric type
min(x,y)        return smaller of x and y for any numeric type

24.8  Repetition and control

for(init-expr; log-expr; incr-expr)
for(int i=start; i<end ; i++)      definite loop
do {code} while(log-expr);         indefinite loop test after pass
while(log-expr) {code}             indefinite loop test before pass
if(log-expr) {code} else {code}    if-else block
value = (this ? if-true:if-false)  conditonal operator
throw new someException            throw an exception
try {code} catch(exception) {code}
finally {code}                     try-catch block
switch (thiscase)                  switch-block
{case v1: code;                    without break fall through to v2
 case v2: code; break;
 default: code;}                   switch-block
synchronized (object reference)    synchronized block (reference lock)
 {code}                           

24.9  class String

indexOf(char ch)                     index of first ch
indexOf(char ch, int start)          index of first ch>=start
indexOf(String str)                  index of first str
indexOf(String int start)            index of first str>=start
lastIndexOf(char ch)                 index of last ch
lastIndexOf(char ch, int start)      index of last ch<=start
lastIndexOf(String str)              index of last str
lastIndexOf(String str, int start)   index of last str<=start
compareTo(String str)                true if same 
regionMatches(int start,
  String other, int ostart, int len) true if same
regionMatches(boolean ignorecase,
  int start, String other,
  int ostart, int len)               true if same
startsWith(String prefix, int off)   true if prefix at off
startsWith(String prefix)            true if prefix at 0
endsWith(String suffix)              true if ends with suffix
startsWith(String prefix)            true if prefix at 0
valueOf(type)                        convert to String

new Boolean(String).booleanValue()   convert from String
Integer.ParseInt(String, int base)   convert from String
Long.ParseLong(String, int base)     convert from String
new Float(String).floatvalue()       convert from String
new Double(String).doubleValue()     convert from String




File translated from TEX by TTH, version 3.01.
On 17 Sep 2004, 11:42.