SAS Institute. The Power to Know

FOCUS AREAS

Return to previous page

Base SAS

The Java Object and the DATA Step Component Interface

The DATA step Java object is production in SAS 9.2. It was preproduction in SAS 9.0 and 9.1. New documentation for SAS 9.2 is highlighted with a blue background color.


Introduction

The DATA step Component Interface creates and manipulates component objects from within a DATA step program. (In the SAS online documentation, see Using DATA Step Component Objects in SAS Language Reference: Concepts and DATA Step Object Attributes and Methods in SAS Language Reference: Dictionary.)

One such object is the Javaobj, which provides a mechanism, similar to the Java Native Interface (JNI), for instantiating Java classes, and accessing fields and methods on the resultant objects.


Declaring a Javaobj

A Javaobj is declared via the Component Interface DECLARE statement:
   declare javaobj j("someJavaClass");

This will declare and store an instance of a Javaobj object in the variable j. The Javaobj will represent an instance of the Java class someJavaClass found on the current Java CLASSPATH.

Typically, the declaration should be done only once (for one instance of j). In order to ensure this, the declaration can be done as follows:

   if _N_ = 1 then do;
     declare javaobj j("someJavaClass");
   end;

Javaobj can also be instantiated via the _NEW_ statement:

     declare javaobj j;
     j = _NEW_ javaobj("someJavaClass");

Constructor Arguments

The first (and only required) argument to a Javaobj constructor is the name of the Java class to be instantiated. For instance, to create a hashtable:
   declare javaobj h("java/util/Hashtable");

Thus the classname is a constructor argument to the Javaobj class. Any remaining arguments become constructor arguments to the Java class itself.

For example, to create a hashtable with an initial capacity of 100 and load factor of .8, we would create a wrapper class for java/util/Hashtable

    import java.util.*;

    public class mhash extends Hashtable
    {
      mhash(double size, double load)
       {
         super((int)size, (float)load);
       }
    }
and then instantiate it from the DATA step:
   declare javaobj h("mhash", 100, .8);
The wrapper class is necessary because the DATA step's only numeric type is equivalent to Java double.

Accessing Object Fields

Once the Javaobj has been instantiated, it is possible to access and modify its (public) fields through method calls on the Javaobj.

For example, assume we had a simple Java class

import java.util.*;
import java.lang.*;
public class ttest
{
  public int    i;
  public double d;
  public String s;
}
We can create an instance of this class, and set/get fields on the resulting object with the following DATA step program:
data _null_;
 dcl javaobj j("ttest");
 length val 8;
 length str $20;
 j.setIntField("i", 100);
 j.setDoubleField("d", 3.14159);
 j.setStringField("s", "abc");

 j.getIntField("i", val);
 put val=;
 j.getDoubleField("d", val);
 put val=;
 j.getStringField("s", str);
 put str=;
 run;
Thus we have specific JNI-like methods for accessing fields of different types. The first argument to any such field method is the name of the field. The second argument is the get/set value.

Accessing Object Methods

It is possible to access object methods in a similar fashion. Suppose the above Java class were changed as follows:
import java.util.*;
import java.lang.*;
public class ttest
{
  public int    i;
  public double d;
  public String s;

  public int im()
  {
    return i;
  }

  public String sm()
  {
    return s;
  }

  public double dm()
  {
    return d;
  }
}
We could access the Java values through Javaobj method calls:
data _null_;
 dcl javaobj j("ttest");
 length val 8;
 length str $20;
 j.setIntField("i", 100);
 j.setDoubleField("d", 3.14159);
 j.setStringField("s", "abc");

 j.callIntMethod("im", val);
 put val=;
 j.callDoubleMethod("dm", val);
 put val=;
 j.callStringMethod("sm", str);
 put str=;
 run;
Note that the return value of the method is always specified as the last parameter. For example, if we had a Java double method m
    public double m(double x, double y)
      {
         return x * y;
      }
This method would be called from the DATA step as
    length val1 val2 ret 8;
    j.callDoubleMethod("m", val1, val2, ret);

Accessing Class Fields and Methods

To access the static fields and methods of a Java class, we can use the corresponding static Javaobj method calls. For example, for the following Java class
import java.util.*;
import java.lang.*;
public class ttestc
{
  public static double d;
  public static double dm()
  {
    return d;
  }
}
we have the DATA step code
data x;
 dcl javaobj j("ttestc");
 length d 8;

 j.SetStaticDoubleField("d", 3.14159);
 j.callStaticDoubleMethod("dm", d);
 put d=;
 run;

Type Issues

The Java type set is a superset of the DATA step type set. The latter's principal types are numeric and character, whereas Java contains types such as byte, short and char, in addition to the standard numeric and character values.

When using the Javaobj method calls, all Java numeric types are mapped to the DATA step numeric type. Thus byte, int, short, long, float, and double are all mapped to DATA step numeric. Java String values are mapped to DATA step character values (as UTF strings).

The Java char type is unsupported at this time.

It is also not possible to return objects (other than String) from Java classes to the DATA step (but it is possible to pass objects to Java methods, see Passing Object Arguments).

Some Java methods that return objects can be handled by creating wrapper classes to convert the object values. For instance, the Java hashtable returns object values, but we can still use it from the DATA step by creating a simple Java wrapper class to handle the type conversions:

import java.util.*;

public class mhash
{
  private Hashtable table;

  public mhash()
  {
    table = new Hashtable();
  }

  public void put(double key, double value)
    {
      table.put(new Double(key), new Double(value));
    }

  public void put(String key, String value)
  {
    table.put(key, value);
  }

  public double get(double key)
    {
      Object ret = table.get(new Double(key));
      if (ret instanceof Double)
        return ((Double)ret).doubleValue();
      else
        return Double.NaN;
    }

  public String get(String key)
  {
    return (String)table.get(key);
  }
}
Then the mhash class can be accessed from the DATA step:
data _null_;
   length s $20;

   /*
    * Simple Java hash table test.  mhash.class is a wrapper
    * for java/util/Hashtable - so that we can pass Java strings
    * directly instead of generic Objects.
    */
   declare javaobj h("mhash");

   /* Load up the table */
   h.callVoidMethod("put", "key1", "data1");
   h.callVoidMethod("put", "key2", "data2");
   h.callVoidMethod("put", "key3", "data3");
   h.callVoidMethod("put", "key4", "data4");
   h.callVoidMethod("put", "key5", "data5");

   /* Retrieve a value */
   h.callStringMethod("get", "key3", s);
   put s=;
   run;

Arrays

DATA step arrays can be passed to Java objects. For example, given the Java class
import java.util.*;
import java.lang.*;
class jtest
{
  public void dbl(double args[])
  {
     for(int i = 0; i < args.length; i++)
      System.out.println(args[i]);
  }

  public void str(String args[])
  {
     for(int i = 0; i < args.length; i++)
      System.out.println(args[i]);
  }
}
we can pass the corresponding DATA step arrays:
data _null_;
  dcl javaobj j("jtest");
  array s[3] $20 ("abc", "def", "ghi");
  array d[10] (1:10);
  j.callVoidMethod("dbl", d);
  j.callVoidMethod("str", s);
  run;
Array get/set array field operations are planned for the Javaobj, but have not been implemented at this time.

Currently, only one-dimensional array parameters are fully supported. However, you can pass multidimensional array arguments by taking advantage of the fact that the arrays are passed in row-major order. You have to handle the dimensional indexing manually on the Java side, that is, declare a one-dimensional array parameter and index to the subarrays accordingly.


Passing Object Arguments

While it is not possible to return objects from Java classes to the DATA step, it is possible to pass objects (other than Strings) to Java class methods.

For example, assume we had the following wrapper classes for java/util/Vector and its iterator:

import java.util.*;

class mVector extends Vector
{
  public mVector()
  {
    super();
  }

  public mVector(double d)
  {
    super((int)d);
  }

  public void addElement(String s)
  {
    addElement((Object)s);
  }
}

import java.util.*;
public class mIterator
{
  protected mVector m_v;
  protected Iterator iter;

  public mIterator(mVector v)
  {
    m_v = v;
    iter = v.iterator();
  }

  public boolean hasNext()
  {
    return iter.hasNext();
  }

  public String next()
  {
    String ret = null;
    ret = (String)iter.next();
    return ret;
  }
}
These wrapper classes are useful for performing type conversions, as in the mVector constructor which takes a double argument. Overloading the constructor is necessary because java/util/Vector's constructor takes an integer value, but the DATA step has no integer type.

We can write a DATA step program to make use of these classes as follows:

data _null_;
 length b 8;
 length val $200;
 dcl javaobj v("mVector");

 v.callVoidMethod("addElement", "abc");
 v.callVoidMethod("addElement", "def");
 v.callVoidMethod("addElement", "ghi");
 dcl javaobj iter("mIterator", v);

 iter.callBooleanMethod("hasNext", b);
 do while(b);
   iter.callStringMethod("next", val);
   put val=;
   iter.callBooleanMethod("hasNext", b);
 end;

 v.delete();
 iter.delete();
 run;
Thus we can create and fill a vector, pass it to the iterator's constructor, and the list all the values in the vector - all within DATA step code.

Note that the iterator must be created after the vector is filled - the iterator keeps a copy of the vector's modification count at creation, and this must stay in sync with the vector's current modification count (i.e. the above code would throw an exception if the iterator were created before the vector was filled).

One current limitation to passing objects is that the JNI method lookup routine does not do a full class lookup based on a given signature. This means you could not change the mIterator constructor to take a Vector

    public mIterator(Vector v)
  {
    m_v = v;
    iter = v.iterator();
  }
Even though mVector is a subclass of Vector, the method lookup routine will not find the constructor. This problem may be remedied by implementing a more robust method lookup in the Javaobj. For now, however, the only solution is to manage the types in Java by adding new methods or creating wrapper classes.

Exceptions

Java exceptions are handled through the ExceptionCheck, ExceptionClear and ExceptionDescribe methods.

ExceptionCheck is used to determine if an exception occurred during a method call.

For example, suppose we have a Java class which contains a method that throws an exception:

    public class a
    {
       public void m() throws NullPointerException
       {
          throw new NullPointerException();
       }
    }
We can call the method m and check for the exception in DATA step code as follows:
data _null_;
   length e 8;
   dcl javaobj j('a');

   rc = j.callVoidMethod('m');

   /* Check for exception.  Value is returned in variable 'e' */
   rc = j.ExceptionCheck(e);
   if (e) then
     put 'exception';
   else
     put 'no exception';

   run;

A more complex example involves using the Java IO classes to read an external file from DATA step code.

First, we'll need a wrapper class for DataInputStream which allows us to pass a FileInputStream to the constructor. The wrapper is necessary because the constructor actually takes an InputStream, the parent of FileInputStream, and the current method lookup is not robust enough to do the superclass lookup (see the comments in Passing Object Arguments).

   public class myDataInputStream extends java.io.DataInputStream
   {
      myDataInputStream(java.io.FileInputStream fi)
      {
         super(fi);
      }
   }

Once we have this wrapper class, we can use it to create a DataInputStream for an external file and read it until end-of-file:

\
data _null_;
   length d e 8;
   dcl javaobj f("java/io/File", "some_file_containing_integers");
   dcl javaobj fi("java/io/FileInputStream", f);
   dcl javaobj di("myDataInputStream", fi);

   do while(1);
     di.callIntMethod("readInt", d);
     di.ExceptionCheck(e);
     if (e) then
       leave;
     else
       put d=;
   end;
   run;

Here the ExceptionCheck method is used to determine when the readInt method throws an EOFException, which allows us to end the input loop.

To clear an exception, call the ExceptionClear method. Using the example class a from above, we can check for an exception and then clear it:

data _null_;
   length e 8;
   dcl javaobj j('a');

   rc = j.callVoidMethod('m');

   /* Check for exception */
   rc = j.ExceptionCheck(e);
   if (e) then
     put 'exception';
   else
     put 'no exception';

   /* Clear the exception */

   /* Check it again */
   rc = j.ExceptionClear();
   rc = j.ExceptionCheck(e);
   if (e) then
     put 'exception';
   else
     put 'no exception';
 run;

This will give the output

   exception
   no exception

If you call a method which throws an exception, it is strongly recommended that you check for an exception after the call. If an exception was thrown, you should perform some appropriate action and then clear the exception.

The ExceptionDescribe method is used to turn exception debug-logging on and off. If exception debugging is on, exception information will be printed to the JVM standard output (JVM standard output is redirected to the SAS log by default).

Passing a nonzero argument to ExceptionDescribe turns exception debugging on; a zero argument turns it off. Exception debugging is off by default.

For example, you can get information about what exception is thrown in the previous example:

data _null_;
   length e 8;
   dcl javaobj j('a');

   j.ExceptionDescribe(1);
   rc = j.callVoidMethod('m');
run;

This will print the following information to standard output:

java.lang.NullPointerException
        at a.m(a.java:5)

Java Standard Output

Output from statements in Java which are directed to standard output, such as

   System.out.println("hello");
   

To redirect Java standard output to a file, you can do the following:

import java.io.*;
class redirect
  {
    redirect()
      {
      }

    redirect(String out)
      {
        FileOutputStream f;
        PrintStream p;

        try
          {
            f = new FileOutputStream(out);
            p = new PrintStream(f);
            System.setOut(p);
          }
        catch(Exception e)
          {
            System.out.println(e.getMessage());
          }
      }

    void hello()
      {
        System.out.println("hello");
     }
  }

which can be called from a Javaobj:

data x;
   dcl javaobj j('redirect', 'hello_out');
   j.callVoidMethod('hello');
run;

In this case, standard output from the JVM will be written to the file hello_out.

Java output directed to the SAS log will be flushed when the DATA step terminates. This will cause the Java output to appear after any output that was issued while the step was running.

For instance, the following program

data _null_;
   dcl javaobj j('p');
   do i = 1 to 3;
      j.callVoidMethod('p');
      put 'In DATA Step';
   end;
run;

when used with the following Java program

import java.io.*;
public class p
{
  void p()
    {
      System.out.println("In Java class");
    }
}

will produce the following output

In DATA Step
In DATA Step
In DATA Step
In Java class
In Java class
In Java class

To synchronize the output so that it appears in the order of execution, use the flushJavaOutput method:

data _null_;
dcl javaobj j('p');
do i = 1 to 3;
  j.callVoidMethod('p');
  j.flushJavaOutput();
  put 'In DATA Step';
end;
run;

This will produce the following output:

In Java class
In DATA Step
In Java class
In DATA Step
In Java class
In DATA Step

User Interface Example

In addition to providing a Java component access mechanism, the Javaobj can be used to create examples that are more interactive.

For instance, we can create a simple Java user interface using the following Java classes:

import java.awt.*;




public class fr extends Frame
{
  public fr()
  {
    super.setBackground(Color.lightGray);
    super.resize(500, 400);
    super.setTitle("New Frame");
    super.setVisible(true);
    super.show();
  }

  public boolean handleEvent(Event e)
  {
    if (e.id == Event.WINDOW_DESTROY)
      {
        this.hide();
        return true;
      }

    return super.handleEvent(e);
  }
}

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

public class colors extends fr
{
  private Button red;
  private Button blue;
  private Button green;
  private Button quit;
  private Vector list;
  boolean d;
  whandler wh;
  bhandler bh;

  public colors()
  {
    super();
    wh = new whandler(this);
    bh = new bhandler(this);
    addWindowListener(wh);
    d = false;
    list = new Vector();
    GridBagLayout l = new GridBagLayout();
    GridBagConstraints c = new GridBagConstraints();
    this.setLayout(l);
    red = new Button("Red");
    red.setBackground(Color.red);
    red.addActionListener(bh);
    blue = new Button("Blue");
    blue.setBackground(Color.blue);
    blue.addActionListener(bh);
    green = new Button("Green");
    green.setBackground(Color.green);
    green.addActionListener(bh);
    quit = new Button("Quit");
    quit.setBackground(Color.yellow);
    quit.addActionListener(bh);

    c.gridx = 0;
    c.gridy = 0;
    c.gridwidth = 3;
    c.gridheight = 3;
    l.setConstraints(red, c);
    this.add(red);

    c.gridx = 3;
    c.gridwidth = 3;
    c.gridheight = 3;
    l.setConstraints(blue, c);
    this.add(blue);

    c.gridx = 6;
    c.gridwidth = 3;
    c.gridheight = 3;
    l.setConstraints(green, c);
    this.add(green);

    c.gridx = 20;
    c.gridy = 20;
    c.gridwidth = 3;
    c.gridheight = 3;
    l.setConstraints(quit, c);
    this.add(quit);
    show();
  }


  public synchronized void enqueue(Object o)
  {
    synchronized(list)
      {
        list.addElement(o);
        notify();
      }
  }

  public synchronized Object dequeue()
  {
    try
      {
        while(list.isEmpty())
          wait();

        if (d)
          return null;

        synchronized(list)
          {
            Object ret = list.elementAt(0);
            list.removeElementAt(0);
            return ret;
          }
      }
    catch(Exception e)
      {
        return null;
      }
  }

  public String getNext()
  {
    return (String)dequeue();
  }

  public boolean done()
  {
    return d;
  }

  public static void main(String[] args)
  {
    colors c = new colors();
  }

  class bhandler implements ActionListener
  {
    colors colors;

    bhandler(colors colors)
    {
      this.colors = colors;
    }

    public void actionPerformed(ActionEvent e)
    {
      String c = e.getActionCommand();
      if (c.equals("Quit"))
        {
          synchronized(this)
            {
              enqueue("");
            }

          d = true;
          colors.hide();
          colors.dispose();
        }
      else
        {
          synchronized(this)
            {
              enqueue(c);
            }
        }
    }
  }

  class whandler extends WindowAdapter
  {
    colors c;

    public whandler(colors c)
    {
      this.c = c;
    }

    public void windowClosing(WindowEvent e)
    {
      super.windowClosing(e);
      synchronized(this)
        {
          enqueue("");
        }

      c.d = true;
      c.hide();
      c.dispose();
    }
  }
}

This Java class will create a simple UI with several buttons. It also maintains a queue of values representing the sequence of button choices entered by a user.

The 'driver' DATA step is:

data colors;
  length s $10;
  length done 8;
  drop done;

  if (_n_ = 1) then do;
    /* Declare and instantiate colors object (from colors.class) */
    dcl javaobj j("colors");
  end;

  /*
   * colors.class will display a simple UI and maintain a
   * queue to hold color choices.
   */

  /* Loop until user hits quit button */
  do while (1);
      j.callBooleanMethod("done", done);
      if (done) then
        leave;
      else do;
        /* Get next color back from queue */
        j.callStringMethod("getNext", s);
        if s ne "" then
          output;
      end;
  end;
  run;

Here the colors class is instantiated (and the UI is displayed). Then we enter a loop which is terminated when the Quit button is pressed (this action is communicated to the DATA step via the done variable). While looping, the DATA step retrieves values from the Java class's queue, and writes them successively to the output data set.

For example,

                                          Obs      s

                                           1     Red
                                           2     Blue
                                           3     Green
                                           4     Blue
                                           5     Red
                                           6     Red
                                           7     Blue
                                           8     Green
                                           9     Red

Class Loader Example

Users can override the normal classpath search given in the jreoptions by creating a custom class loader. The following method is based on Zhiyong Li's SAS Application Class Loader.

Suppose we have a class x:

package com.sas;

public class x
{
  public void m()
  {
    System.out.println("method m in y folder");
  }

  public void m2()
  {
    System.out.println("method m2 in y folder");
  }
}

which resides in a folder (or directory) called y. We can call methods in this class using the Javaobj with a jreoption classpath which includes the y folder.

data _null_;
  dcl javaobj j('com/sas/x');
  j.callvoidmethod('m');
  j.callvoidmethod('m2');
run;

This will print the following output.

method m in y folder
  method m2 in y folder

Suppose further that we have another class x stored in a different folder z.

package com.sas;

public class x
{
  public void m()
  {
    System.out.println("method m in z folder");
  }

  public void m2()
  {
    System.out.println("method m2 in z folder");
  }
}

We can call methods in this class instead of the one in y by changing the classpath, but that may not always be a reasonable option in general. For instance, the given class could be part of the SAS installation, in which case changing the classpath could affect other applications. To further complicate things, the given class could reside in a jar file and depend on multiple other classes in other jar files.

To control class loading in cases like this, we can create a custom class loader.

To do this, we first create an interface which contains all the methods we will be called through the Javaobj, in this case methods m and m2:

public interface apiInterface
{
  public void m();
  public void m2();
}

Then a class for the actual implementation:

import com.sas.x;

public class apiImpl implements apiInterface
{
  private x x;

  public apiImpl()
  {
    x = new x();
  }

  public void m()
  {
    x.m();
  }

  public void m2()
  {
    x.m2();
  }
}

These methods will be called by delegating through the Javaobj instance class:

public class api
{
  /* Load classes from the z folder */
  static ClassLoader customLoader =  new apiClassLoader("C:\\z");
  static String API_IMPL = "apiImpl";
  apiInterface cp = null;

  public api()
    {
      cp = load();
    }

  public void m()
    {
      cp.m();
    }

  public void m2()
    {
      cp.m2();
    }

  private static apiInterface load()
    {
      try
        {
          Class aClass = customLoader.loadClass(API_IMPL);
          return (apiInterface) aClass.newInstance();
        }
      catch (Exception e)
       {
          e.printStackTrace();
          return null;
        }
    }
}

which is run from SAS as

data _null_;
  dcl javaobj j('api');
  j.callvoidmethod('m');
  j.callvoidmethod('m2');
run;

The Javaobj instantiates the api class, which will create a custom class loader to load classes from the z folder. The api class calls the custom loader and returns an instance of the interface implementation class (apiImpl) to the Javaobj. Then when methods are called through the Javaobj, the api class delegates them to the implementation class.

This example will print the following:

method m in z folder
  method m2 in z folder

Note: You can also use jar files to augment the classpath in the classloader constructor:

static ClassLoader customLoader = new apiClassLoader("C:\\z;C:\\temp\some.jar");

Note: The Java code for the custom class loader follows. This can be extended or modified as needed.


import java.io.*;
import java.util.*;
import java.util.jar.*;
import java.util.zip.*;

public class apiClassLoader extends ClassLoader
{
  //class repository where findClass performs its search
  private List classRepository;

  public apiClassLoader(String loadPath)
    {
      super(apiClassLoader.class.getClassLoader());
      initLoader(loadPath);
    }

  public apiClassLoader(ClassLoader parent,String loadPath)
    {
      super(parent);
      initLoader(loadPath);
    }

  /**
   * This method will look for the class in the class repository. If
   * it cannot find it, it will delegate to its parent class loader.
   *
   * @param className A String specifying the class to be loaded
   * @return  A Class object loaded by the apiClassLoader
   * @throws ClassNotFoundException if the method is unable to load the class
   */
  public Class loadClass(String name) throws ClassNotFoundException
    {
      // Check if the class is already loaded
      Class loadedClass = findLoadedClass(name);

      // Search for class in local repository before delegating
     if (loadedClass == null)
       {
         loadedClass = myFindClass(name);
       }

      // If class not found, delegate to parent
      if (loadedClass == null)
        {
          loadedClass = this.getClass().getClassLoader().loadClass(name);
        }
      return loadedClass;
    }

  private Class myFindClass(String className) throws ClassNotFoundException
    {
      byte[] classBytes = loadFromCustomRepository(className);
      if(classBytes != null)
        {
          return defineClass(className,classBytes,0,classBytes.length);
        }
      return null;
    }

  /**
   *  This method loads binary class file data from the classRepository.
   */
  private byte[] loadFromCustomRepository(String classFileName)
    throws ClassNotFoundException
    {
      Iterator dirs = classRepository.iterator();
      byte[] classBytes = null;
      while (dirs.hasNext())
        {
          String dir = (String) dirs.next();

          if (dir.endsWith(".jar"))
            {
              // Look for class in jar

              String jclassFileName = classFileName;

              jclassFileName = jclassFileName.replace('.', '/');
              jclassFileName += ".class";

              try
                {
                  JarFile j = new JarFile(dir);
                  for (Enumeration e = j.entries(); e.hasMoreElements() ;)
                    {
                      Object n = e.nextElement();

                      if (jclassFileName.equals(n.toString()))
                        {
                          ZipEntry zipEntry = j.getEntry(jclassFileName);
                          if (zipEntry == null)
                            {
                              return null;
                            }
                          else
                            {
                              // read file
                              InputStream is = j.getInputStream(zipEntry);
                              classBytes = new byte[is.available()];
                              is.read(classBytes);
                              break;
                            }
                        }
                    }
                }
              catch (Exception e)
                {
                  System.out.println("jar file exception");
                  return null;
                }
            }
          else
            {
              // Look for class in directory
              String fclassFileName = classFileName;

              fclassFileName = fclassFileName.replace('.', File.separatorChar);
              fclassFileName += ".class";

              try
                {
                  File file = new File(dir,fclassFileName);
                  if(file.exists()) {
                    //read file
                    InputStream is = new FileInputStream(file);
                    classBytes = new byte[is.available()];
                    is.read(classBytes);
                    break;
                  }
                }
              catch(IOException ex)
                {
                  System.out.println("IOException raised while reading class file data");
                  ex.printStackTrace();
                  return null;
                }
            }
        }
      return classBytes;
    }

  private void initLoader(String loadPath)
    {
      /*
       * loadPath is passed in as a string of directories/jar files
       * separated by the File.pathSeparator
       */
      classRepository = new ArrayList();
      if((loadPath != null) && !(loadPath.equals("")))
        {
          StringTokenizer tokenizer =
            new StringTokenizer(loadPath,File.pathSeparator);
          while(tokenizer.hasMoreTokens())
            {
              classRepository.add(tokenizer.nextToken());
            }
        }
    }
}

CLASSPATH and JRE Options

If you are using SAS 9.2, you must set the CLASSPATH environment variable so the Javaobj can find your classes. Any class you are searching for must appear on the classpath. If the class is in a jar file, then the jar file name must appear on the classpath.

In versions previous to 9.2, classes were found using the jreoptions option. In version SP4, use

   sdssas -jreoptions (-Dsas.app.class.path=your_java_classpath_here)

If you're running a version of SAS prior to SP4, use

   sdssas -jreoptions (-Djava.class.path=your_java_classpath_here)

An exception to the above rule for jreoptions occurs on the Windows platforms where the change occurred at SP3.


Method List

The Javaobj's methods are

Creation and Deletion

Field References

where each of these methods takes a Java field name as the first parameter and a get/set value for the second parameter.

Method References

where each of these methods takes a Java method name as the first parameter and that method's arguments as the remaining parameters. If the method returns a value, that value is specified as the last parameter on the call.

Exceptions

  • exceptionCheck
  • exceptionDescribe
  • exceptionClear

where the exceptionCheck takes a double argument which returns the exception status, the exceptionDescribe method takes a double argument which sets the debug status, and the exceptionClear method takes no parameters.

Output

  • flushJavaOutput

where flushJavaOutput requires no parameters.