7. Java Objects

7.1 Object Expressions

Sleep can create, access, and query Java objects through Object expressions. Converting Java programs to Sleep is a snap once you master the concepts in this chapter.

Object expressions are enclosed inside of square brackets. The first parameter of an object expression is a target. The second is the message. Sleep converts the return value of an object expression to a Sleep scalar.

[target message]

Use a $scalar, Java class, or other object expression as the target.

Object expressions can receive arguments. Specify arguments as a comma separated list separated from the target and message parameters by a colon.

[target message: argument, argument, ...]

Use object expressions to access the Java class library. A good first example is the famous "Hello World" program.

[[System out] println: "Hello World!"];

Hello World!

The target of this expression is [System out]. The message is println. And the argument is "Hello World". Notice that [System out] is itself an object expression.

Messages are Java method and field names. A common mistake is to specify a field using the Java dot notation i.e.

[System.out println: "Hello World!"];

Error: Unknown expression at line 1 out Error: Unknown expression at line 1 System

As you can see, this doesn't work.

You can use the Java dot notation to specify the full name of a class.

[[java.lang.System out] println: "Hello World!"];

Hello World!

Access named inner classes with a dollar sign between the parent and inner class names: ParentClass$InnerClass.

Scalars as Objects

All Sleep scalars have an object representation. You can use object expressions to operate on scalar data as well as Java data.

$value = ["this is a String" lastIndexOf: "i" ];

This example is equivalent to "this is a string".lastIndexOf("i") in Java. Numbers are treated as their wrapper java objects. For example: An object expression converts a double scalar into a java.lang.Double object.

$value = [3.45 isNaN];

Object Instantiation

Use the new keyword to instantiate an object. The syntax for this is:

[new ClassName] and [new ClassName: argument, ...].

An example:

$scalar = [new java.util.StringTokenizer: "this is a test", " "];

Sleep can import classes to save typing the package name each time. Sleep attempts to resolve literal class names against the import list at compile time.

import java.awt.Point; $point = [new Point: 3, 4]; println($point);

java.awt.Point[x=3,y=4]

You can use wildcards to import the entire contents of a package.

import java.awt.*;

Sleep imports java.lang, java.util, and sleep.runtime by default.

Fields

Use the &setField function to set a field in an object to some value.

import java.awt.Point; $p = [new Point]; setField($p, x => 33, y => 45); println($p);

java.awt.Point[x=33,y=45]

Class Literals

The literal form for a java.lang.Class object in Sleep is the hat symbol followed by a classname. The parser resolves these literals when compiling the script.

The literal ^String is equivalent to [Class forName: "java.lang.String"].

Use the isa predicate to check if an object is an instance of a specified class:

>> Welcome to the Sleep scripting language > ? "some string" isa ^String true > ? 33.0 isa ^String false > ? 33.0 isa ^Double true

3rd-party Jars

Sleep can dynamically import packages from jar files that do not currently exist on the Java classpath. Use an import from statement:

import package from: path-to/filename.jar;

This snippet uses the JDOM XML API located in jdom.jar:

import org.jdom.* from: jdom.jar; import org.jdom.input.* from: jdom.jar; import org.jdom.output.* from: jdom.jar; # load the document in $builder = [new SAXBuilder]; $document = [$builder build: @ARGV[0]]; # print the document out. $output = [new XMLOutputter: [Format getPrettyFormat]]; [$output output: $document, [System out]];

Sleep has its own classpath (the current working directory is included). The import from statement will attempt to find the jar file in any of the Sleep classpath folders. Read the Sleep classpath value with systemProperties()["sleep.classpath"]. Setting the Sleep classpath is covered in chapter 1.

Taint Mode and Objects

Taint mode is a security feature of Sleep that flags all data originating from an external source as tainted. Data derived from tainted data is considered tainted as well. Some functions will not accept tainted data in their arguments. Sensitive functions include &compile_closure, &eval, &expr, and &include.

Sleep will taint objects that receive tainted arguments in an object expression. Object expressions that query a tainted object will return tainted data. This is excessive but it gets the job done.

debug(debug() | 128); import java.util.LinkedList; $evals = [new LinkedList]; [$evals add: "2 + 2"]; [$evals add: @ARGV[0]]; $iterator = [$evals iterator]; while ([$iterator hasNext]) { $next = [$iterator next]; println("$next = " . expr($next)); }

$ java -Dsleep.taint=true -jar sleep.jar taint2.sl "3 * 9" Warning: tainted object: [2 + 2] from: '3 * 9' at taint2.sl:7 Warning: tainted value: 1 from: '3 * 9' at taint2.sl:7 Warning: tainted value: java.util.LinkedList$ListItr@199939 from: [2 + 2, 3 * 9] at taint2.sl:9 Warning: tainted value: 1 from: java.util.LinkedList$ListItr@199939 at taint2.sl:10 Warning: tainted value: '2 + 2' from: java.util.LinkedList$ListItr@199939 at taint2.sl:12 Warning: Insecure &expr: '2 + 2' is tainted at taint2.sl:13 Warning: tainted value: 1 from: java.util.LinkedList$ListItr@199939 at taint2.sl:10 Warning: tainted value: '3 * 9' from: java.util.LinkedList$ListItr@199939 at taint2.sl:12 Warning: Insecure &expr: '3 * 9' is tainted at taint2.sl:13 Warning: tainted value: 0 from: java.util.LinkedList$ListItr@199939 at taint2.sl:10

To detaint a Sleep scalar use &untaint. Use &debug level 128 to trace the propagation of tainted values. To enable/disable this mode see 1.1 Stand-alone Scripts: Command Line Options.

See Chapter 9.3 Extend Sleep: Taint Mode for more background on taint mode.

Parameter Marshalling

Object expressions that send messages to Java objects must resolve the proper method to use. Sleep employs a heuristic to map scalar types to Java types.

This heuristic loops through all methods with the same name as the message parameter. The algorithm checks each method signatuare against the expression arguments looking for a best match.

In general a match is determined by checking if the scalar type is an instance of the expected Java object. Sleep's primitive types (int, double, long) map to their equivalent Java primitive types.

Sleep converts arrays to a java.util.Collection and hashes to a java.util.Map.

Sleep also makes an effort to convert Sleep arrays to Java arrays if necessary. The interpreter uses the first element of the Sleep array to determine the type of the Java array.

Closures are automatically marshalled into proxy object instances. I describe this process in the next subsection.

$null maps to null automatically.

The following table details the definite match and secondary match for Sleep types mapping to Java types:

Scalar TypePrimary MatchSecondary Match
string
java.lang.String
char (length == 1)
char[]
byte[]
java.lang.Object
int
int java.lang.Integer
other primitives
java.lang.Object
java.lang.String
double
double java.lang.Double
other primitives
java.lang.Object
java.lang.String
long
long java.lang.Long
other primitives
java.lang.Object
java.lang.String
object
anything I am an instanceof
&closure
Any Java interface java.lang.Object
@array
java array
java.util.List
java.util.Collection
sleep.runtime.ScalarArray
java.lang.Object
%hash
java.util.Map
sleep.runtime.ScalarHash
java.lang.Object
$null
matches everything :)

You can bypass this automatic marshalling with casting. Individual scalars are cast using the &casti function.

$cast = casti(1, 'b'); println([$cast getClass]); $cast = casti(33.5, 'f'); println([$cast getClass]);

class java.lang.Byte class java.lang.Float

Create Java arrays of any dimension and type with the &cast function.

@array = @("a", "b", "c", "d", "e", "f"); $casted = cast(@array, "*", 2, 3); # create a 2x3 array println("Class is: " . [$casted getClass]);

Class is: class [[Ljava.lang.String;

Values returned by object expressions are marshalled back into scalar types. This process is detailed in the following table:

Java TypesScalar Types
boolean / java.lang.Boolean (false)$null
boolean / java.lang.Boolean (true)int
byte / java.lang.Byteint
byte[]string
char / java.lang.Characterstring
char[]string
double / java.lang.Doubledouble
float / java.lang.Floatdouble
int / java.lang.Integerint
java.lang.Array@array
java.lang.Objectobject
java.lang.Stringstring
long / java.lang.Longlong
null$null
short / java.lang.Shortint

Use &scalar to feed an object scalar through this conversion process.

7.2 Proxy Instances

Sleep can convert a closure into an anonymous Java object that responds to certain interfaces. This object is a proxy instance. The interpreter automatically marshalls closures to proxy instances when passing them to Java.

This feature works only on interfaces right now. In the future there may be an addon that allows this with arbitrary Java objects.

A Java method call on a proxy instance results in a call on a closure. The argument $0 is set to the method name. The arguments from Java are converted to scalars $1, $2, etc.

debug(7); global('$frame $button $clicked'); import javax.swing.*; import java.awt.*; $frame = [new JFrame: "Test"]; [$frame setSize: 240, 120]; [$frame setDefaultCloseOperation: [JFrame EXIT_ON_CLOSE]]; $button = [new JButton: "Click me"]; [[$frame getContentPane] add: $button]; [$frame show]; $clicked = 0; sub button_pressed { # $0 is "actionPerformed" # $1 is a java.awt.event.ActionEvent object $clicked++; [[$1 getSource] setText: "Clicked $clicked time(s)"]; } # add &button_pressed as our action listener [$button addActionListener: &button_pressed];

A screenshot of the application:

This example marshalls &button_pressed into a java.awt.event.ActionListener instance. Any call to actionPerformed calls &button_pressed with $0 set to actionPerformed and $1 set to the java.awt.event.ActionEvent object.

Uncaught exceptions within a proxy instance will bubble up to the calling Java object. Sleep wraps a non-throwable object with a java.lang.RuntimeException.

debug(7); global('$frame $button'); import javax.swing.*; import java.awt.*; $frame = [new JFrame: "Test Exceptions"]; [$frame setSize: 240, 120]; [$frame setDefaultCloseOperation: [JFrame EXIT_ON_CLOSE]]; $button = [new JButton: "Click me"]; [[$frame getContentPane] add: $button]; [$frame show]; sub button_pressed { throw "argh, why did you click me?"; } [$button addActionListener: &button_pressed];

Exception in thread "AWT-EventQueue-0" java.lang.RuntimeException: argh, why did you click me?

Create proxy instances with the &newInstance function.

7.3 Exceptions

Exceptions that come from Java are soft errors. You can use the &checkError mechanism to retrieve them.

import sleep.error.YourCodeSucksException; $loader = [new ScriptLoader]; $script = [$loader loadScript: "debunked.sl"]; if (checkError($exception)) { if ($exception isa ^YourCodeSucksException) { print([$exception formatErrors]); } } else { [$script runScript]; }

Error: Mismatched Parentheses - missing close paren at line 2 println($x; ^

Or if you set &debug 34 you can use try catch blocks to respond to exceptions. See 4.3 Exceptions: Choice in Error Handling for more details.

import sleep.error.YourCodeSucksException; debug(debug() | 34); # throw all soft errors try { $loader = [new ScriptLoader]; $script = [$loader loadScript: "debunked.sl"]; [$script runScript]; } catch $exception { if ($exception isa ^YourCodeSucksException) { print([$exception formatErrors]); } }

Error: Mismatched Parentheses - missing close paren at line 2 println($x; ^