Custom Class Assignment Operator In Java

6.1. Optional typing

Optional typing is the idea that a program can work even if you don’t put an explicit type on a variable. Being a dynamic language, Groovy naturally implements that feature, for example when you declare a variable:

1 is declared using an explicit type,
2we can call the method on a

Groovy will let you write this instead:

1 is declared using
2we can still call the method, because the type of is resolved at runtime

So it doesn’t matter that you use an explicit type here. It is in particular interesting when you combine this feature with static type checking, because the type checker performs type inference.

Likewise, Groovy doesn’t make it mandatory to declare the types of a parameter in a method:

can be rewritten using as both return type and parameter types, in order to take advantage of duck typing, as illustrated in this example:

1both the return type and the parameter types use
2it makes it possible to use the method with
3but also with since the method is defined
Using the keyword here is recommended to describe the intent of a method which is supposed to work on any type, but technically, we could use instead and the result would be the same: is, in Groovy, strictly equivalent to using .

Eventually, the type can be removed altogether from both the return type and the descriptor. But if you want to remove it from the return type, you then need to add an explicit modifier for the method, so that the compiler can make a difference between a method declaration and a method call, like illustrated in this example:

1if we want to omit the return type, an explicit modifier has to be set.
2it is still possible to use the method with
3and also with
Omitting types is in general considered a bad practice in method parameters or method return types for public APIs. While using in a local variable is not really a problem because the visibility of the variable is limited to the method itself, while set on a method parameter, will be converted to in the method signature, making it difficult for users to know which is the expected type of the arguments. This means that you should limit this to cases where you are explicitly relying on duck typing.

6.2. Static type checking

By default, Groovy performs minimal type checking at compile time. Since it is primarily a dynamic language, most checks that a static compiler would normally do aren’t possible at compile time. A method added via runtime metaprogramming might alter a class or object’s runtime behavior. Let’s illustrate why in the following example:

1the class only defines two properties, and
2we can create an instance of Person
3and call a method named

It is quite common in dynamic languages for code such as the above example not to throw any error. How can this be? In Java, this would typically fail at compile time. However, in Groovy, it will not fail at compile time, and if coded correctly, will also not fail at runtime. In fact, to make this work at runtime, one possibility is to rely on runtime metaprogramming. So just adding this line after the declaration of the class is enough:

This means that in general, in Groovy, you can’t make any assumption about the type of an object beyond its declaration type, and even if you know it, you can’t determine at compile time what method will be called, or which property will be retrieved. It has a lot of interest, going from writing DSLs to testing, which is discussed in other sections of this manual.

However, if your program doesn’t rely on dynamic features and that you come from the static world (in particular, from a Java mindset), not catching such "errors" at compile time can be surprising. As we have seen in the previous example, the compiler cannot be sure this is an error. To make it aware that it is, you have to explicitly instruct the compiler that you are switching to a type checked mode. This can be done by annotating a class or a method with .

When type checking is activated, the compiler performs much more work:

  • type inference is activated, meaning that even if you use on a local variable for example, the type checker will be able to infer the type of the variable from the assignments

  • method calls are resolved at compile time, meaning that if a method is not declared on a class, the compiler will throw an error

  • in general, all the compile time errors that you are used to find in a static language will appear: method not found, property not found, incompatible types for method calls, number precision errors, …​

In this section, we will describe the behavior of the type checker in various situations and explain the limits of using on your code.

6.2.1. The annotation

Activating type checking at compile time

The annotation enabled type checking. It can be placed on a class:

In the first case, all methods, properties, fields, inner classes, …​ of the annotated class will be type checked, whereas in the second case, only the method and potential closures or anonymous inner classes that it contains will be type checked.

Skipping sections

The scope of type checking can be restricted. For example, if a class is type checked, you can instruct the type checker to skip a method by annotating it with :

1the class is marked as type checked
2so the method is automatically type checked
3but is marked with
4the type checker doesn’t complain about missing properties here

In the previous example, relies on dynamic code. There’s no real method or property, so the type checker would normally complain and compilation would fail. Since the method that uses the builder is marked with , type checking is skipped for this method, so the code will compile, even if the rest of the class is type checked.

The following sections describe the semantics of type checking in Groovy.

6.2.2. Type checking assignments

An object of type can be assigned to a variable of type if and only if:

  • equals

  • or is one of , , or

  • or is null and is not a primitive type

  • or is an array and is an array and the component type of is assignable to the component type of

  • or is an array and is a list and the component type of is assignable to the component type of

  • or is a superclass of

  • or is an interface implemented by

  • or or are a primitive type and their boxed types are assignable

  • or extends and is a SAM-type (single abstract method type)

  • or and derive from and conform to the following table

TAExamples

Double

Any but BigDecimal or BigInteger

Float

Any type but BigDecimal, BigInteger or Double

Long

Any type but BigDecimal, BigInteger, Double or Float

Integer

Any type but BigDecimal, BigInteger, Double, Float or Long

Short

Any type but BigDecimal, BigInteger, Double, Float, Long or Integer

Byte

Byte

6.2.3. List and map constructors

In addition to the assignment rules above, if an assignment is deemed invalid, in type checked mode, a list literal or a map literal can be assigned to a variable of type if:

  • the assignment is a variable declaration and is a list literal and has a constructor whose parameters match the types of the elements in the list literal

  • the assignment is a variable declaration and is a map literal and has a no-arg constructor and a property for each of the map keys

For example, instead of writing:

You can use a "list constructor":

If you use a map constructor, additional checks are done on the keys of the map to check if a property of the same name is defined. For example, the following will fail at compile time:

1The type checker will throw an error at compile time

6.2.4. Method resolution

In type checked mode, methods are resolved at compile time. Resolution works by name and arguments. The return type is irrelevant to method selection. Types of arguments are matched against the types of the parameters following those rules:

An argument of type can be used for a parameter of type if and only if:

  • equals

  • or is a and is a

  • or is null and is not a primitive type

  • or is an array and is an array and the component type of is assignable to the component type of

  • or is a superclass of

  • or is an interface implemented by

  • or or are a primitive type and their boxed types are assignable

  • or extends and is a SAM-type (single abstract method type)

  • or and derive from and conform to the same rules as assignment of numbers

If a method with the appropriate name and arguments is not found at compile time, an error is thrown. The difference with "normal" Groovy is illustrated in the following example:

1 is an error, but since we’re in a dynamic mode, the error is not caught at compile time

The example above shows a class that Groovy will be able to compile. However, if you try to create an instance of and call the method, then it will fail at runtime, because doesn’t exist. Of course, we already showed how Groovy could make this a perfectly valid call, for example by catching or implementing a custom meta-class, but if you know you’re not in such a case, comes handy:

1 is this time a compile-time error

Just adding will trigger compile time method resolution. The type checker will try to find a method accepting a on the class, but cannot find one. It will fail compilation with the following message:

It is important to understand the logic behind the type checker: it is a compile-time check, so by definition, the type checker is not aware of any kind of runtime
Using DATA Step Component Objects

Using the Java Object

The Java object provides a mechanism that is similar to the Java Native Interface (JNI) for instantiating Java classes and accessing fields and methods on the resultant objects. You can create hybrid applications that contain both Java and DATA step code.

In previous versions of SAS, Java classes were found using the JREOPTIONS system option.

In SAS 9.2, you must set the CLASSPATH environment variable so the Java object can find your Java classes. The Java object represents an instance of a Java class that is found in the current Java classpath. Any class that you use must appear in the classpath. If the class is in a .jar file, then the .jar filename must appear in the classpath.

How you set the CLASSPATH environment variable depends on your operating environment. For most operating systems, you can set the CLASSPATH environment variable either locally (for use only in your SAS session) or globally. Table 23.1 shows methods and examples for different operating environments. For more information, see the SAS documentation for your operating environment.

Operating Environment Method Example
Windows
Globally Windows System Environment Variable in Control PanelControl PanelSystemAdvancedEnvironment Variables (Windows XP Classic view)

SAS configuration fileset classpath c:\HelloWorld.jar
Locally SAS command line-set classpath c:\HelloWorld.jar
UNIX
Globally SAS configuration fileset classpath ~/HelloWorld.jar
Locally EXPORT command1export classpath=~/HelloWorld.jar;
z/OS
Globally TKMSENV data setset TKJNI_OPT_CLASSPATH=/u/userid/java: /u/userid/java/test.jar: asis
Locally Not available
VMS
Globally Command line2$ define java$classpath disk:[subdir] abc.jar, disk:[subdir2]def.jar

detach_template.com script that gets generated in sas$root:[misc.base] at installationdefine java$classpath disk:[subdir] abc.jar, disk:[subdir2]def.jar
Locally Not available
1 The syntax depends on the shell.

2 The command line should be defined before you invoke SAS so the process that the JVM actually runs in gets the definition as well.

The following restrictions and requirements apply when using the Java object:

  • The Java object is designed to call Java methods from SAS. The Java object is not intended to extend the SAS library of functions. Calling PROC FCMP functions is much more efficient for fast in-process extensions to the DATA step, especially when large data sets are involved. Using the Java object to perform this kind of processing with large data sets takes significantly more time.

  • The only Java Runtime Environments (JREs) that are supported by SAS are those that are explicitly required during the installation of the SAS software.

  • The only Java options that are supported by SAS are those that are set when SAS is installed.

  • Ensure that your Java application runs correctly before using it with the Java object.

You declare a Java object by using the DECLARE statement. After you declare the new Java object, use the _NEW_ operator to instantiate the object, using the Java object name as an argument tag.

declare javaobj j; j = _new_ javaobj("somejavaclass");

In this example, the DECLARE statement tells the compiler that the object reference J is of type java--that is, the instance of the Java object is stored in the variable J. At this point, you have declared only the object reference J. It has the potential to hold a component object of type java. You should declare the Java object only once. The _NEW_ operator creates an instance of the Java object and assigns it to the object reference J. The Java class name, SOMEJAVACLASS, is passed as a constructor argument, which is the first-and-only argument that is required for the Java object constructor. All other arguments are constructor arguments to the Java class itself.

As an alternative to the two-step process of using the DECLARE statement and the _NEW_ operator to declare and instantiate a Java object, you can declare and instantiate a Java object in one step by using the DECLARE statement as a constructor method. The syntax is as follows:

DECLARE JAVAOBJ object-name("java-class", <argument-1, ... argument-n>);

For more information, see the DECLARE Statement and the _NEW_ Operator for the Java object in SAS Language Reference: Dictionary.

Once you instantiate a Java object, you can access and modify its public and class fields in a DATA step through method calls on the Java object. Public fields are non-static and declared as public in the Java class. Class fields are static and accessed from Java classes.

Method calls to access object fields have one of these forms, depending on whether you are accessing non-static or static fields:

GETtypeFIELD("field-name", value);
GETSTATICtypeFIELD("field-name", value);

Method calls to modify object fields have one of these forms, depending on whether you access static or non-static fields:

SETtypeFIELD("field-name", value);
SETSTATICtypeFIELD("field-name", value);

Note:   The type argument represents a Java data type. For more information about how Java data types relate to SAS data types, see Type Issues. The field-name argument specifies the type for the Java field, and value specifies the value that is returned or set by the method.  

For more information and examples, see Java Object Language Elements in SAS Language Reference: Dictionary.

Once you instantiate a Java object, you can access its public and class methods in a DATA step through method calls on the Java object. Public methods are non-static and declared as public in the Java class. Class methods are static and accessed from Java classes.

Method calls to access Java methods have one of these forms, depending on whether you are accessing non-static or static methods:

object.CALLtypeMETHOD ("method-name", <method-argument-1 ..., method-argument-n>, <return value>);
object.CALLSTATICtypeMETHOD ("method-name", <method-argument-1 ..., method-argument-n>, <return value>);

Note:   The type argument represents a Java data type. For more information about how Java data types relate to SAS data types, see Type Issues.  

For more information and examples, see Java Object Language Elements in SAS Language Reference: Dictionary.

The Java type set is a superset of the SAS data types. Java has data types such as BYTE, SHORT, and CHAR in addition to the standard numeric and character values. SAS has only two data types: numeric and character.

Table 23.2 describes how Java data types are mapped to SAS data types when using the Java object method calls.

Java Data Type SAS Data Type
BOOLEAN numeric
BYTE numeric
CHAR numeric
DOUBLE numeric
FLOAT numeric
INT numeric
LONG numeric
SHORT numeric
STRING character1
1 Java string data types are mapped to SAS character data types as UTF-8 strings.

Other than STRING, it is not possible to return objects from Java classes to the DATA step. However, it is possible to pass objects to Java methods. For more information, see Passing Java Object Arguments.

Some Java methods that return objects can be handled by creating wrapper classes to convert the object values. In the following example, the Java hash table returns object values, but you can still use the hash table from the DATA step by creating simple Java wrapper classes to handle the type conversions. Then you can access the dhash and shash classes from the DATA step.

/* Java code */ import java.util.*; public class dhash { private Hashtable table; public dhash() { table = new Hashtable (); } public void put(double key, double value) { table.put(new Double(key), new Double(value)); } public double get(double key) { Double ret = table.get(new Double(key)); return ret.doubleValue(); } } import java.util.*; public class shash { private Hashtable table; public shash() { table = new Hashtable (); } public void put(double key, String value) { table.put(new Double(key), value); } public String get(double key) { return table.get(new Double(key)); } }/* DATA step code */ data _null_; dcl javaobj sh('shash'); dcl javaobj dh('dhash'); length d 8; length s $20; do i = 1 to 10; dh.callvoidmethod('vput', i, i * 2); end; do i = 1 to 10; sh.callvoidmethod('put', i, 'abc' || left(trim(i))); end; do i = 1 to 10; dh.calldoublemethod('get', i, d); sh.callstringmethod('get', i, s); put d= s=; end; run;

The following lines are written to the SAS log:

d=2 s=abc1 d=4 s=abc2 d=6 s=abc3 d=8 s=abc4 d=10 s=abc5 d=12 s=abc6 d=14 s=abc7 d=16 s=abc8 d=18 s=abc9 d=20 s=abc10

You can pass DATA step arrays to Java objects.

In the following example, the arrays d and s are passed to the Java object j.

/* Java code */ 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]); } }/* DATA Step code */ 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;

The following lines are written to the SAS log:

1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 abc def ghi

Only one-dimensional array parameters are supported. However, it is possible to pass multidimensional array arguments by taking advantage of the fact that the arrays are passed in row-major order. You must handle the dimensional indexing manually in the Java code--that is, you must declare a one-dimensional array parameter and index to the subarrays accordingly.

While it is not possible to return objects from Java classes to the DATA step, it is possible to pass objects, as well as strings, to Java class methods.

For example, suppose you have the following wrapper classes for java/util/Vector and its iterator:

/* Java code */ 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 (for example, the mVector constructor 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.

The following DATA step program uses these classes. The program creates and fills a vector, passes the vector to the iterator's constructor, and then lists all the values in the vector. Note that you must create the iterator after the vector is filled. The iterator keeps a copy of the vector's modification count at the time of creation, and this count must stay in synchronization with the vector's current modification count. The code would throw an exception if the iterator were created before the vector was filled.

/* DATA step code */ 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; m.delete(); v.delete(); iter.delete(); run;

The following lines are written to the SAS log:

val=abc val=def val=ghi

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

/* Java code */ 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. Currently, the only solution is to manage the types in Java by adding new methods or creating wrapper classes.

Java exceptions are handled through the EXCEPTIONCHECK, EXCEPTIONCLEAR, and EXCEPTIONDESCRIBE methods.

The EXCEPTIONCHECK method is used to determine whether an exception occurred during a method call. If you call a method that can throw an exception, it is strongly recommended that you check for an exception after the call. If an exception is thrown, you should take appropriate action and then clear the exception by using the EXCEPTIONCLEAR method.

The EXCEPTIONDESCRIBE method is used to turn exception debug logging on or off. If exception debug logging is on, exception information is printed to the JVM standard output. By default, JVM standard output is redirected to the SAS log. Exception debugging is off by default.

For more information, see the EXCEPTIONCHECK method, the EXCEPTIONCLEAR method, and the EXCEPTIONDESCRIBE method in SAS Language Reference: Dictionary.

Output from statements in Java that are directed to standard output such as the following are sent to the SAS log by default.

System.out.println("hello");

The Java output that is directed to the SAS log is flushed when the DATA step ends. This flushing causes the Java output to appear after any output that was generated while the DATA step was running. Use the FLUSHJAVAOUTPUT method to synchronize the output so that it appears in the order of execution.


Example 1: Calling a Simple Java Method

This Java class creates a simple method that sums three numbers.

/* Java code */ class MyClass { double compute(double x, double y, double z) { return (x + y + z); } }/* DATA step code data _null_; dcl javaobj j("MyClass"); rc = j.callDoubleMethod("compute", 1, 2, 3, r); put rc= r=; run;

The following lines are written to the SAS log:

rc=0 rc=6

Example 2: Creating a User Interface

In addition to providing a Java component access mechanism, you can use the Java object to create a simple Java user interface.

This Java class creates a simple user interface with several buttons. The user interface also maintains a queue of values that represent the sequence of button choices that are entered by a user.

/* Java code */ import java.awt.*; import java.util.*; import java.awt.event.*; class colorsUI extends Frame { private Button red; private Button blue; private Button green; private Button quit; private Vector list; private boolean d; private colorsButtonListener cbl; public colorsUI() { d = false; list = new Vector(); cbl = new colorsButtonListener(); setBackground(Color.lightGray); setSize(320,100); setTitle("New Frame"); setVisible(true); setLayout(new FlowLayout(FlowLayout.CENTER, 10, 15)); addWindowListener(new colorsUIListener()); red = new Button("Red"); red.setBackground(Color.red); red.addActionListener(cbl); blue = new Button("Blue"); blue.setBackground(Color.blue); blue.addActionListener(cbl); green = new Button("Green"); green.setBackground(Color.green); green.addActionListener(cbl); quit = new Button("Quit"); quit.setBackground(Color.yellow); quit.addActionListener(cbl); this.add(red); this.add(blue); this.add(green); 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; } class colorsButtonListener implements ActionListener { public void actionPerformed(ActionEvent e) { Button b; String l; b = (Button)e.getSource(); l = b.getLabel(); if ( l.equals("Quit") ) { d = true; hide(); l = ""; } enqueue(l); } } class colorsUIListener extends WindowAdapter { public void windowClosing(WindowEvent e) { Window w; w = e.getWindow(); d = true; enqueue(""); w.hide(); } } public static void main(String s[]) { colorsUI cui; cui = new colorsUI(); } }/* DATA step code */ data colors; length s $10; length done 8; drop done; if (_n_ = 1) then do; /* Declare and instantiate colors object (from colorsUI.class) */ dcl javaobj j("colorsUI"); end; /* * colorsUI.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; proc print data=colors; run; quit;

In the DATA step code, the colorsUI class is instantiated and the user interface is displayed. You enter a loop that is terminated when you click Quit. This action is communicated to the DATA step through the Done variable. While looping, the DATA step retrieves the values from the Java class's queue and writes the values successively to the output data set.

User Interface Created by the Java Object


Example 3: Creating a Custom Class Loader

You might not want to put all your Java classes in the classpath. You can write your own class loader to find the classes and load them. The following example illustrates how you can create a custom class loader.

In this example, you create a class, x, which resides in a folder or directory, y. You call the methods in this class by using the Java object with the classpath that includes the y folder.

/* Java code */ 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"); } }/* DATA step code */ data _null_; dcl javaobj j('com/sas/x'); j.callvoidmethod('m'); j.callvoidmethod('m2'); run;

The following lines are written to the SAS log.

method m in y folder method m2 in y folder

Suppose you have another class, x, that is stored in a different folder, z.

/* Java code */ package com.sas; public class z { public void m() { System.out.println("method m in y folder"); } public void m2() { System.out.println("method m2 in y folder"); } }

You can call methods in this class instead of the class in folder y by changing the classpath, but this requires restarting SAS. The following method allows for more dynamic control of how classes are loaded.

To create a custom class loader, first you create an interface that contains all the methods you will call through the Java object--in this program, m and m2.

/* Java code */ public interface apiInterface { public void m(); public void m2(); }

Then you create a class for the actual implementation.

/* Java code */ 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 are called by delegating to the Java object instance class. Note that the code to create the apiClassLoader custom class loader is provided later in this section.

/* Java code */ 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; } } }

The following DATA step program calls these methods by delegating through the api Java object instance class. The Java object instantiates the api class, which creates a custom class loader to load classes from the z folder. The api class calls the custom loader and returns an instance of the apiImpl interface implementation class to the Java object. When methods are called through the Java object, the api class delegates them to the implementation class.

/* DATA step code */ data _null_; dcl javaobj j('api'); j.callvoidmethod('m'); j.callvoidmethod('m2'); run;

The following lines are written to the SAS log:

method m is z folder method m2 in z folder

In the previous Java code, you could also use .jar files to augment the classpath in the ClassLoader constructor.

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

In this case, the Java code for the custom class loader is as follows. This code for this class loader can be added to 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 * the method cannot find the class, the method 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()); } } } }

Copyright © 2010 by SAS Institute Inc., Cary, NC, USA. All rights reserved.

0 thoughts on “Custom Class Assignment Operator In Java”

    -->

Leave a Comment

Your email address will not be published. Required fields are marked *