Интерпретация JavaScript в Java с помощью Rhino: приостановка/возобновление скриптов

Я использую пакет javax.script. * JDK. В частности, я использую механизм JavaScript, который, из того, что я читал, похоже, основан на разработанном Mozilla JavaScript-in-Java-интерпретаторе Rhino.

То, что я намереваюсь выполнить, состоит в том, чтобы в моем режиме JavaScript мог "приостановить" себя в определенный момент кода (скажем, наполовину через вызов функции) и только возобновить себя позже, когда Java разрешит это сделать.

Чтобы проиллюстрировать, что я имею в виду, представьте этот код JavaScript:

function myJSFunction() {
    print("Hello ");
    mysteriousPauseFunction(); // this is the part I'm wondering about.  basically, the script should break here and resume later at Java discretion...
    // upon reaching this comment, we know now that Java has told JavaScript that it okay to resume, so the next line will now be executed...
    print("world");
}

Если "пауза" / "ломающаяся" часть включает привязку функции Java и передачу ее ссылки на текущий ScriptEngine или что-то еще, что круто со мной. Я думаю, что это, вероятно, будет включать: приостановку JavaScript из Java.

Я сделал несколько поисковых запросов и обнаружил, что ключевое слово здесь выглядит как "продолжения". Из того, что я могу сказать, Rhino поддерживает только продолжения в интерпретируемом режиме (в сравнении с скомпилированным режимом), который, как я вижу, достигается путем установки "контекста" на -2. Поскольку встроенный JDK ScriptEngine, похоже, не упоминает ничего о контекстах (или, может быть, мне его не хватает), значит ли это, что я должен загружать и использовать библиотеку Mozilla Rhino напрямую?

И есть ли продолжения носорогов, что мне нужно для этого? Я нашел полезный учебник о продолжениях Rhino, но, прочитав его, я не уверен на 100%, если это будет возможно чтобы выполнить то, что я описал выше. Если это то, что я ищу, тогда мой следующий вопрос касается упоминания о "сериализации": означает ли это, что, когда я возобновляю свой script, все переменные будут отменены, если я не сериализую их?

Обновление: Похоже, это возможно с Rhino. Вот то, что у меня есть до сих пор в моем JavaScript; после кода я объясню, что он делает...

var end = new Continuation();

function myJSFunction()
{
    print("Hello ");
    var kont = new Continuation();
    storePause(script, kont); // script is previously bound by Java into the JavaScript.  it is a reference to the script itself.
    end();
    print("world");

}

Моя функция "storePause()" - это функция Java, которую я написал, и она привязана к JavaScript, но прямо сейчас она ничего не делает. Следующей моей целью будет сформировать его код таким образом, чтобы он сохранял продолжение и script информацию как объекты Java, так что Java может возобновить script позже.

В настоящий момент то, что он делает, приостанавливает/ "ломает" script после "Hello", но перед печатью "мир", это доказывает мне, что можно приостановить script таким образом.

Итак, все, что я должен был оставить, чтобы понять на этом этапе, - это возобновление продолжения. Обратите внимание, что вышеописанное работает с использованием механизма сценариев JDK по умолчанию (мне не нужно беспокоиться о интерпретируемом режиме и скомпилированном режиме на данный момент - он по умолчанию используется для интерпретируемого режима), но он выглядит как процесс возобновления a script потребуется библиотека Mozilla Rhino.

Ответы

Ответ 1

Хорошо, мне потребовалось много часов, чтобы прорыть документацию, учебники и примеры, а также разместить здесь и в Rhino Google Group, но мне удалось скомпилировать рабочее решение. Поскольку, кажется, нет полного примера, я опубликую свои результаты здесь для всех, кто наткнется на это в будущем.

Собственно, мои выводы, вероятно, слишком долго, чтобы публиковать здесь, поэтому я решил написать учебник в своем блоге:

http://www.joshforde.com/blog/?p=7

Надеюсь, что это поможет кому-то. Насколько я знаю, это единственный полный учебник Rhino, в котором показано, как выполнить все следующие операции: инициализировать Rhino, загрузить script из файла JavaScript (*.js), автоматически связать все функции в определенном Java class (например ScriptFunctions) в качестве глобальных функций в JavaScript и, наконец, вызывать функцию JavaScript и обрабатывать продолжения для этого вызова.

В основном проблема заключалась в том, что мне сначала нужно было загрузить исходный код Mozilla Rhino (поскольку версия, заполненная JDK, устарела и не поддерживает продолжения), перепишите весь мой код для использования официального синтаксиса пакета Rhino (он сильно отличается от синтаксиса JDK ScriptingEngine), напишите функцию Java, которая генерирует исключение ContinuationPending и привязывает его к JavaScript, чтобы JavaScript мог вызывать его (потому что бросание ContinuationPending непосредственно из JavaScript приводит к тому, что JavaScriptException является брошен, а не вызывается ContinuationPending и даже пытается вызвать getCause(), когда результаты JavaScriptException равны нулю), а затем в моем Java-коде, который вызывает мою функцию JavaScript ( "myJSFunction" в моем исходном примере), используйте блоки try/catch для проверки ContinuationPending (который является исключением), а затем использовать ContinuationPending позже, чтобы возобновить script.

Уф. Это было сложно, но все это стоит того.

Ответ 2

Вы не объяснили, почему вы это делаете, но я эмулировал программу, которая взаимодействует с конечным пользователем, например:

print('Hello!'); 
a=Number(input('enter a number')); 
b=Number(input('and another number')); 
print('the sum of '+a+' plus '+b+' is '+(a+b))

У меня есть работа, просто создав функцию печати и ввода в javascript, которая проверяет состояние программы.

здесь вы можете увидеть демонстрацию .

все это написано на javascript, чтобы вы могли посмотреть исходный код с любым браузером.

Надеюсь, что это поможет

Ответ 3

Вы можете использовать wait/notify:

public final class Pause {
  private final Object lock = new Object();

  public void await() throws InterruptedException {
    synchronized (lock) {
      lock.wait();
    }
  }

  public void resumeAll() {
    synchronized (lock) {
      lock.notifyAll();
    }
  }
}

Использование:

final Pause pause = new Pause();

class Resumer implements Runnable {
  @Override public void run() {
    try {
      Thread.sleep(5000);
      pause.resumeAll();
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
    }
  }
}
new Thread(new Resumer()).start();

SimpleBindings bindings = new SimpleBindings();
bindings.put("pause", pause);
String script = "print('Hello, ');\n"
              + "pause.await();\n"
              + "println('ECMAScript!');\n";
new ScriptEngineManager().getEngineByName("ECMAScript")
                         .eval(script, bindings);

Это относительно упрощенное решение, так как вы не упоминаете никаких других ограничений. wait() заставляет поток блокироваться, что неприемлемо для всех сред. Также нет простого способа определить, какие потоки ждут в экземпляре Pause, если вы хотите одновременно запускать скрипты.

Примечание: InterruptedException on await() следует обрабатывать либо вызывающим, либо делать что-то более разумное в await().