12.1. Управление Java с помощью Rhino

Rhino - это интерпретатор JavaScript, написанный на языке Java, цель которого - упростить возможность создания программ на языке JavaScript, которые могли бы использовать мощь платформы Java. Интерпретатор Rhino автоматически выполняет преобразование простых типов JavaScript в простые типы Java и наоборот, благодаря чему сценарии на языке JavaScript могут читать и изменять свойства и вызывать методы объектов на языке Java.

Rhino определяет несколько важных глобальных функций, не являющихся частью базового языка JavaScript:

// Специфические глобальные функции, определяемые интерпретатором

// Введите help() в строке приглашения rhino, чтобы получить дополнительную информацию

print(х);          // Глобальная функция вывода в консоль

version(170);      // Требует от Rhino задействовать особенности версии 1.7 );

load(filename....  // Загружает и выполняет один или более файлов

                   // с программами на языке JavaScript

readFile(file);    // Читает текстовый файл и возвращает его содержимое в виде строки

readUrl(url);      // Читает текстовое содержимое по адресу URL и возвращает

                   // в виде строки

spawn(f);          // Вызывает f() или загружает и выполняет файл f

                   // в новом потоке выполнения

runCommand(cmd.    // Запускает системную команду с нулем или более

[args...])         // аргументов командной строки

quit()             // Завершает работу Rhino

Как получить Rhino

Интерпретатор Rhino - свободное программное обеспечение, разработанное проектом Mozilla. Вы можете загрузить копию по адресу http://www.mozilla.org/rhino/. Версия Rhino 1.7г2 реализует ECMAScript 3 и ряд расширений языка, описанных в главе 11. Rhino - достаточно зрелый программный продукт, и новые его версии появляются не так часто. На момент написания этих строк в репозитории с исходными текстами была доступна предварительная версия 1.7гЗ, включающая частичную реализацию стандарта ECMAScript 5.

Rhino распространяется в виде JAR-архива. Запускается он командой, как показано ниже:

java -jar rhino1_7R2/js.jar program.js

Если опустить параметр program.js, интерпретатор Rhino запустится в интерактивном режиме, который позволит вам опробовать простенькие программы и однострочные примеры.

**************************************************

Обратите внимание на функцию print(): мы будем использовать ее в этом разделе вместо console.log(). Интерпретатор Rhino представляет пакеты и классы Java как объекты JavaScript:

// Глобальная переменная Packages определяет корень иерархии пакетов Java

Packages.any.package.name // Любой пакет из CLASSPATH

java.lang // Глобальная переменная java - краткая ссылка на Packages.java

javax.swing // A javax - краткая ссылка на Packages.javax

// Классы: доступны как свойства пакетов

var System = java.lang.System;

var JFrame = javax.swing.JFrame;

Поскольку пакеты и классы представлены как объекты JavaScript, их можно присваивать переменным, чтобы давать им более короткие имена. Но при желании их можно импортировать более формальным способом:

var ArrayList = java.util.ArrayList; // Создать краткое имя для класса

importClass(java.util.HashMap); // Аналог: var HashMap=java.util.HashMap

// Импорт пакета (отложенный) с помощью importPackage().

// Не следует импортировать java.lang: слишком много конфликтов имен

// с глобальными переменными JavaScript.

importPackage(java.util);

importPackage(java.net);

// Другой прием: передать произвольное количество классов и пакетов функции

// JavaImporter() и использовать возвращаемый объект с инструкцией with

var guipkgs = JavaImporter(java.awt, java.awt.event, Packages.javax.swing);

with (guipkgs) {

  /* Здесь определены такие классы, как Font, ActionListener и JFrame */

}

С помощью ключевого слова new можно создавать экземпляры классов языка Java, как если бы это были классы JavaScript:

// Объекты: создание из классов Java с помощью ключевого слова new

var f = new java. io. File("tmp/test''); // Этот объект используется ниже

var out = new java.io.FileWriter(f);

Интерпретатор Rhino позволяет использовать JavaScript-оператор instanceof для работы с объектами и классами на языке Java:

f instanceof java.io.File // => true

out instanceof java.io.Reader // => false: объект Writer, а не Reader

out instanceof java.io.Closeable // => true: Writer реализует Closeable

Как видно выше в примерах создания объектов экземпляров, интерпретатор Rhino позволяет передавать значения конструкторам Java и присваивать возвращаемые ими значения переменным JavaScript. (Обратите внимание на неявное преобразование типов, выполняемое интерпретатором Rhino в этом примере: JavaScript-строка "/tmp/test" автоматически преобразуется в значение типа java.lang.String.) Методы Java очень похожи на конструкторы Java, и Rhino позволяет программам на языке JavaScript вызывать методы на языке Java:

// Статические методы на языке Java действуют подобно функциям JavaScript

java.lang.System.getProperty("java.version") // Вернет версию Java

var isDigit = java.lang.Character.isDigit; // Присвоит статич. метод переменной

isDigit("t")   // => true: Арабская цифра 2

// Вызвать методы экземпляра объектов f и out на языке Java, созданных выше

out.write("Hello World ");

out.close():

var len = f.length();

Кроме того, Rhino позволяет получать и изменять значения статических полей Java-классов и полей экземпляров Java-объектов из программы на языке JavaScript. В классах на языке Java часто не определяют общедоступные поля, отдавая предпочтение методам доступа. Если в Java-классе определены методы доступа, Rhino обеспечивает доступ к ним, как к свойствам объекта на языке JavaScript:

// Прочитать значение статического поля Java-класса

var stdout = java.lang.System.out:

// Rhino отображает методы доступа в отдельные свойства JavaScript

f.name // => "/tmp/test": вызовет f.getName()

f.directory // => false: вызовет f.isDirectory()

В языке Java имеется возможность создавать перегруженные версии методов, имеющие одинаковые имена, но разные сигнатуры. Обычно интерпретатор Rhino способен определить, какую версию метода следует вызвать, опираясь на типы аргументов, которые передаются программой на языке JavaScript. Однако иногда бывает необходимо явно идентифицировать метод по имени и сигнатуре:

// Предположим, что Java-объект о имеет метод f, который принимает целое

// или вещественное число. В JavaScript необходимо будет явно указать сигнатуру:

о[’f(int)'](3); // Вызвать метод, принимающий целое число

о['f(float)'](Math.PI); // Вызвать метод, принимающий вещественное число

Для итераций по методам, полям и свойствам Java-классов можно использовать цикл for/in:

importClass(java.lang.System);

for(var m in System) print(m); // Выведет статические члены java.lang.System

for(m in f) print(m); // Выведет члены экземпляра java.io.File

// Обратите внимание, что таким способом нельзя перечислить классы в пакете

for (с in java.lang) print(c): // Этот прием не сработает

Rhino позволяет программам на языке JavaScript получать и изменять значения элементов Java-массивов, как если бы они были JavaScript-массивами. Конечно, Java-массивы отличаются от JavaScript-массивов: они имеют фиксированную длину, их элементы имеют определенный тип, и они не имеют JavaScript-методов, таких как slice(). В JavaScript не существует синтаксических конструкций, которые могли бы использоваться интерпретатором Rhino для создания Java-массивов в программах на языке JavaScript, поэтому для этой цели необходимо использовать класс java.lang.reflect Array:

// Создать массив из 10 строк и массив из 128 байтов

var words = java.lang.reflect.Array.newlnstance(java.lang.String, 10);

var bytes = java.lang.reflect.Array.newlnstance(java.lang.Byte.TYPE, 128);

// После создания с массивами можно работать как с JavaScript-массивами:

for(var і = 0; і < bytes.length; i++) bytes[i] = i;

Программирование на языке Java часто связано с реализацией интерфейсов. Чаще всего с этой необходимостью приходится сталкиваться при разработке графических интерфейсов, когда каждый обработчик события должен реализовать интерфейс приемника событий. Следующие примеры демонстрируют, как это сделать:

// Интерфейсы: Реализация интерфейсов выглядит следующим образом:

var handler = new java.awt.event.FocusListener({

  focusGained: function(e) { printfgot focus"); },

  focusLost: function(e) { print("lost focus"): }

}):

// Аналогично выполняется расширение абстрактных классов

var handler = new java.awt.event.WindowAdapter({

  windowclosing: function(e) { java.lang.System.exit(0); }

}):

// Когда интерфейс определяет единственный метод, можно использовать простую функцию

button.addActionListener(function(e) { print("button clicked"); });

// Если все методы интерфейса или абстрактного класса имеют одну и ту же сигнатуру,

// в качестве реализации можно использовать единственную функцию,

// a Rhino будет передавать ей имя метода в последнем аргументе

frame.addWindowListener(function(e, name) {

  if (name === "windowclosing") java.lang.System.exit(0);

});

// Если необходимо определить объект, реализующий несколько интерфейсов,

// можно использовать класс JavaAdapter:

var о = new JavaAdapter(java.awt.event.ActionListener, java.lang.Runnable, {

  run: function() {}. // Реализует интерфейс Runnable

  actionPerformed: function(e) {} // Реализует интерфейс ActionListener

});

Когда Java-метод возбуждает исключение, интерпретатор Rhino продолжает его распространение как JavaScript-исключения. Получить оригинальный Java-объект java.lang.Exception можно через свойство javaException JavaScript-объекта Error:

try {

  java.lang.System.getProperty(null); // null - недопустимый аргумент

}

catch(e) { // e - JavaScript-исключение

  print(e.javaException); // это обертка для java.lang.NullPointerException

}

Здесь необходимо сделать последнее замечание по поводу преобразования типов в Rhino. Интерпретатор Rhino автоматически преобразует простые числа, логические значения и null. Java-тип char интерпретируется в языке JavaScript как число, так как в языке JavaScript отсутствует символьный тип. JavaScript-строки автоматически преобразуются в Java-строки, но (и это может быть камнем преткновения) Java-строки остаются объектами java.lang.String и не преобразуются обратно в JavaScript-строки. Взгляните на следующую строку из примера, приводившегося ранее:

var version = java.lang.System.getProperty("java.version");

После выполнения этой инструкции переменная version будет хранить объект java.lang.String. Он обычно ведет себя как JavaScript-строка, но существуют важные отличия. Во-первых, Java-строка вместо свойства length имеет метод length(). Во-вторых, оператор typeof возвращает тип «object» для Java-строк. Java-строку нельзя преобразовать в JavaScript-строку вызовом метода toString(), потому что все Java-объекты имеют собственные методы toString(), возвращающие экземпляры javadang.String. Чтобы преобразовать Java-значение в строку, его нужно передать JavaScript-функции String():

var version = String(java.lang.System.getPropertyC'java.version"));