[Git][java-team/rhino][master] 4 commits: Backported the JSR 223 script engine to use Rhino as a replacement for Nashorn with OpenJDK 17

Emmanuel Bourg gitlab at salsa.debian.org
Mon Feb 8 10:40:10 GMT 2021



Emmanuel Bourg pushed to branch master at Debian Java Maintainers / rhino


Commits:
7ef1f3ff by Emmanuel Bourg at 2021-02-08T11:39:12+01:00
Backported the JSR 223 script engine to use Rhino as a replacement for Nashorn with OpenJDK 17

- - - - -
5956b137 by Emmanuel Bourg at 2021-02-08T11:39:23+01:00
Standards-Version updated to 4.5.1

- - - - -
a825f850 by Emmanuel Bourg at 2021-02-08T11:39:27+01:00
Switch to debhelper level 13

- - - - -
1b685589 by Emmanuel Bourg at 2021-02-08T11:39:35+01:00
Upload to unstable

- - - - -


6 changed files:

- debian/changelog
- − debian/compat
- debian/control
- + debian/patches/script-engine.patch
- debian/patches/series
- debian/rules


Changes:

=====================================
debian/changelog
=====================================
@@ -1,3 +1,13 @@
+rhino (1.7.7.2-3) unstable; urgency=medium
+
+  * Team upload.
+  * Backported the JSR 223 script engine to use Rhino as a replacement
+    for Nashorn with OpenJDK 17
+  * Standards-Version updated to 4.5.1
+  * Switch to debhelper level 13
+
+ -- Emmanuel Bourg <ebourg at apache.org>  Mon, 08 Feb 2021 11:39:29 +0100
+
 rhino (1.7.7.2-2) UNRELEASED; urgency=medium
 
   * Team upload.


=====================================
debian/compat deleted
=====================================
@@ -1 +0,0 @@
-10


=====================================
debian/control
=====================================
@@ -7,11 +7,11 @@ Uploaders:
  Jakub Adam <jakub.adam at ktknet.cz>
 Build-Depends:
  ant,
- debhelper (>= 10),
+ debhelper-compat (= 13),
  default-jdk,
  javahelper,
  maven-repo-helper
-Standards-Version: 4.5.0
+Standards-Version: 4.5.1
 Vcs-Git: https://salsa.debian.org/java-team/rhino.git
 Vcs-Browser: https://salsa.debian.org/java-team/rhino
 Homepage: http://www.mozilla.org/rhino/


=====================================
debian/patches/script-engine.patch
=====================================
@@ -0,0 +1,1303 @@
+From f195514ffee1b759ba088883732e77b026b3a694 Mon Sep 17 00:00:00 2001
+From: Gregory Brail <gregbrail at google.com>
+Date: Fri, 5 Jun 2020 14:38:28 -0700
+Subject: [PATCH] Implement standard Java ScriptEngine
+
+This is not based on the now-removed JDK code but instead does a
+few things in a more modern way. See the comments for supported
+parameters (you can set language and optimization level via
+properties) and built-in functions (only print is supported right now.)
+
+This is built into a separate JAR called "rhino-engine" because
+otherwise, including it in any Java 8 JDK would break scripts that
+are expecting to see Nashorn instead.
+---
+ .circleci/config.yml                          |   2 +-
+ build.gradle                                  |  86 ++++-
+ .../services/javax.script.ScriptEngineFactory |   1 +
+ .../javascript/engine/BindingsObject.java     |  60 +++
+ .../mozilla/javascript/engine/Builtins.java   |  59 +++
+ .../engine/RhinoCompiledScript.java           |  33 ++
+ .../engine/RhinoInvocationHandler.java        |  25 ++
+ .../javascript/engine/RhinoScriptEngine.java  | 363 ++++++++++++++++++
+ .../engine/RhinoScriptEngineFactory.java      | 140 +++++++
+ .../tests/scriptengine/BuiltinsTest.java      |  54 +++
+ .../tests/scriptengine/FactoryTest.java       |  56 +++
+ .../tests/scriptengine/InvocableTest.java     | 158 ++++++++
+ .../tests/scriptengine/ScriptEngineTest.java  | 276 +++++++++++++
+ 13 files changed, 1305 insertions(+), 8 deletions(-)
+ create mode 100644 src/META-INF/services/javax.script.ScriptEngineFactory
+ create mode 100644 src/org/mozilla/javascript/engine/BindingsObject.java
+ create mode 100644 src/org/mozilla/javascript/engine/Builtins.java
+ create mode 100644 src/org/mozilla/javascript/engine/RhinoCompiledScript.java
+ create mode 100644 src/org/mozilla/javascript/engine/RhinoInvocationHandler.java
+ create mode 100644 src/org/mozilla/javascript/engine/RhinoScriptEngine.java
+ create mode 100644 src/org/mozilla/javascript/engine/RhinoScriptEngineFactory.java
+ create mode 100644 testsrc/org/mozilla/javascript/tests/scriptengine/BuiltinsTest.java
+ create mode 100644 testsrc/org/mozilla/javascript/tests/scriptengine/FactoryTest.java
+ create mode 100644 testsrc/org/mozilla/javascript/tests/scriptengine/InvocableTest.java
+ create mode 100644 testsrc/org/mozilla/javascript/tests/scriptengine/ScriptEngineTest.java
+
+--- /dev/null
++++ b/src/META-INF/services/javax.script.ScriptEngineFactory
+@@ -0,0 +1 @@
++org.mozilla.javascript.engine.RhinoScriptEngineFactory
+--- /dev/null
++++ b/src/org/mozilla/javascript/engine/BindingsObject.java
+@@ -0,0 +1,60 @@
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++package org.mozilla.javascript.engine;
++
++import org.mozilla.javascript.Context;
++import org.mozilla.javascript.Scriptable;
++import org.mozilla.javascript.ScriptableObject;
++import javax.script.Bindings;
++
++/**
++ * This class makes the Bindings object into a Scriptable. That way, we can query and modify
++ * the contents of the Bindings on demand.
++ */
++public class BindingsObject
++  extends ScriptableObject {
++  private final Bindings bindings;
++
++  BindingsObject(Bindings bindings) {
++    if (bindings == null) {
++      throw new IllegalArgumentException("Bindings must not be null");
++    }
++    this.bindings = bindings;
++  }
++
++  @Override
++  public String getClassName() {
++    return "BindingsObject";
++  }
++
++  @Override
++  public Object get(String name, Scriptable start) {
++    Object ret = bindings.get(name);
++    if (ret == null) {
++      return Scriptable.NOT_FOUND;
++    }
++    return Context.jsToJava(ret, Object.class);
++  }
++
++  @Override
++  public void put(String name, Scriptable start, Object value) {
++    bindings.put(name, Context.javaToJS(value, start));
++  }
++
++  @Override
++  public void delete(String name) {
++    bindings.remove(name);
++  }
++
++  @Override
++  public boolean has(String name, Scriptable start) {
++    return bindings.containsKey(name);
++  }
++
++  @Override
++  public Object[] getIds() {
++    return bindings.keySet().toArray();
++  }
++}
+--- /dev/null
++++ b/src/org/mozilla/javascript/engine/Builtins.java
+@@ -0,0 +1,59 @@
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++package org.mozilla.javascript.engine;
++
++import java.io.IOException;
++import java.io.OutputStreamWriter;
++import java.io.Writer;
++import javax.script.ScriptContext;
++import org.mozilla.javascript.Context;
++import org.mozilla.javascript.Function;
++import org.mozilla.javascript.ScriptRuntime;
++import org.mozilla.javascript.Scriptable;
++import org.mozilla.javascript.ScriptableObject;
++
++/**
++ * <p>
++ * This class defines the following built-in functions for the RhinoScriptEngine.
++ * </p>
++ * <ul>
++ * <li>print(arg, arg, ...): Write each argument, concatenated to the ScriptEngine's
++ * "standard output" as a string.</li>
++ * </ul>
++ */
++public class Builtins {
++
++  static final Object BUILTIN_KEY = new Object();
++
++  private Writer stdout;
++
++  void register(Context cx, ScriptableObject scope, ScriptContext sc) {
++    if (sc.getWriter() == null) {
++      stdout = new OutputStreamWriter(System.out);
++    } else {
++      stdout = sc.getWriter();
++    }
++
++    scope.defineFunctionProperties(new String[]{"print"},
++        Builtins.class,
++        ScriptableObject.PERMANENT | ScriptableObject.DONTENUM);
++  }
++
++  public static void print(Context cx, Scriptable thisObj, Object[] args, Function f)
++      throws IOException {
++    Builtins self = getSelf(thisObj);
++    for (Object arg : args) {
++      self.stdout.write(ScriptRuntime.toString(arg));
++    }
++    self.stdout.write('\n');
++  }
++
++  private static Builtins getSelf(Scriptable scope) {
++    // Since this class is invoked as a set of anonymous functions, "this"
++    // in JavaScript does not point to "this" in Java. We set a key on the
++    // top-level scope to address this.
++    return (Builtins) ScriptableObject.getTopScopeValue(scope, BUILTIN_KEY);
++  }
++}
+--- /dev/null
++++ b/src/org/mozilla/javascript/engine/RhinoCompiledScript.java
+@@ -0,0 +1,33 @@
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++package org.mozilla.javascript.engine;
++
++import javax.script.CompiledScript;
++import javax.script.ScriptContext;
++import javax.script.ScriptEngine;
++import javax.script.ScriptException;
++import org.mozilla.javascript.Script;
++
++public class RhinoCompiledScript
++  extends CompiledScript {
++
++  private final RhinoScriptEngine engine;
++  private final Script script;
++
++  RhinoCompiledScript(RhinoScriptEngine engine, Script script) {
++    this.engine = engine;
++    this.script = script;
++  }
++
++  @Override
++  public Object eval(ScriptContext context) throws ScriptException {
++    return engine.eval(script, context);
++  }
++
++  @Override
++  public ScriptEngine getEngine() {
++    return engine;
++  }
++}
+--- /dev/null
++++ b/src/org/mozilla/javascript/engine/RhinoInvocationHandler.java
+@@ -0,0 +1,25 @@
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++package org.mozilla.javascript.engine;
++
++import java.lang.reflect.InvocationHandler;
++import java.lang.reflect.Method;
++
++public class RhinoInvocationHandler
++    implements InvocationHandler {
++
++  private final Object thiz;
++  private final RhinoScriptEngine engine;
++
++  RhinoInvocationHandler(RhinoScriptEngine engine, Object thiz) {
++    this.engine = engine;
++    this.thiz = thiz;
++  }
++
++  @Override
++  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
++    return engine.invokeMethodRaw(thiz, method.getName(), method.getReturnType(), args);
++  }
++}
+--- /dev/null
++++ b/src/org/mozilla/javascript/engine/RhinoScriptEngine.java
+@@ -0,0 +1,355 @@
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++package org.mozilla.javascript.engine;
++
++import java.io.IOException;
++import java.io.Reader;
++import java.lang.reflect.Method;
++import java.lang.reflect.Proxy;
++import javax.script.AbstractScriptEngine;
++import javax.script.Bindings;
++import javax.script.Compilable;
++import javax.script.CompiledScript;
++import javax.script.Invocable;
++import javax.script.ScriptContext;
++import javax.script.ScriptEngine;
++import javax.script.ScriptEngineFactory;
++import javax.script.ScriptException;
++import javax.script.SimpleBindings;
++import org.mozilla.javascript.Callable;
++import org.mozilla.javascript.Context;
++import org.mozilla.javascript.ContextFactory;
++import org.mozilla.javascript.RhinoException;
++import org.mozilla.javascript.Script;
++import org.mozilla.javascript.Scriptable;
++import org.mozilla.javascript.ScriptableObject;
++
++/**
++ * <p>
++ * This is the implementation of the standard ScriptEngine interface for Rhino.
++ * </p>
++ * <p>
++ * An instance of the Rhino ScriptEngine is fully self-contained. Bindings at the GLOBAL_SCOPE may
++ * be set, but there is nothing special about them -- if both global and ENGINE_SCOPE bindings are
++ * set then the "engine" bindings override the global ones.
++ * </p>
++ * <p>
++ * The Rhino engine is not thread safe. Rhino does no synchronization of ScriptEngine instances and
++ * no synchronization of Bindings instances. It is up to the caller to ensure that the ScriptEngine
++ * and all its Bindings are used by a single thread at a time.
++ * </p>
++ * <p>
++ * The Rhino script engine includes some top-level built-in functions. See the Builtins class for
++ * more documentation.
++ * </p>
++ * <p>
++ * The engine supports a few configuration parameters that may be set at the "engine scope". Both
++ * are numbers that may be set to a String or Number object.
++ * </p>
++ * <ul>
++ * <li>javax.script.language_version: The version of the JavaScript language supported,
++ * which is an integer defined in the Context class. The default is the latest "ES6"
++ * version, defined as 200.</li>
++ * <li>org.mozilla.javascript.optimization_level: The level of optimization Rhino performs
++ * on the generated bytecode. Default is 9, which is the most. Set to -1 to use interpreted
++ * mode.</li>
++ * </ul>
++ */
++public class RhinoScriptEngine
++    extends AbstractScriptEngine
++    implements Compilable, Invocable {
++
++  /**
++   * Reserved key for the Rhino optimization level. Default is "9," for optimized and compiled code.
++   * Set this to "-1" to run Rhino in interpreted mode -- this is much much slower but the only
++   * option on platforms like Android that don't support class files.
++   */
++  public static final String OPTIMIZATION_LEVEL = "org.mozilla.javascript.optimization_level";
++
++  static final int DEFAULT_LANGUAGE_VERSION = Context.VERSION_ES6;
++  private static final int DEFAULT_OPT = 9;
++  private static final boolean DEFAULT_DEBUG = true;
++  private static final String DEFAULT_FILENAME = "eval";
++
++  private static final CtxFactory ctxFactory = new CtxFactory();
++
++  private final RhinoScriptEngineFactory factory;
++  private final Builtins builtins;
++  private ScriptableObject topLevelScope = null;
++
++  RhinoScriptEngine(RhinoScriptEngineFactory factory) {
++    this.factory = factory;
++    this.builtins = new Builtins();
++  }
++
++  private Scriptable initScope(Context cx, ScriptContext sc) throws ScriptException {
++    configureContext(cx);
++
++    if (topLevelScope == null) {
++      topLevelScope = cx.initStandardObjects();
++      // We need to stash this away so that the built in functions can find
++      // this engine's specific stuff that they need to work.
++      topLevelScope.associateValue(Builtins.BUILTIN_KEY, builtins);
++      builtins.register(cx, topLevelScope, sc);
++    }
++
++    Scriptable engineScope = new BindingsObject(
++        sc.getBindings(ScriptContext.ENGINE_SCOPE));
++    engineScope.setParentScope(null);
++    engineScope.setPrototype(topLevelScope);
++
++    if (sc.getBindings(ScriptContext.GLOBAL_SCOPE) != null) {
++      Scriptable globalScope = new BindingsObject(
++          sc.getBindings(ScriptContext.GLOBAL_SCOPE));
++      globalScope.setParentScope(null);
++      globalScope.setPrototype(topLevelScope);
++      engineScope.setPrototype(globalScope);
++    }
++
++    return engineScope;
++  }
++
++  @Override
++  public Object eval(String script, ScriptContext context) throws ScriptException {
++    Context cx = ctxFactory.enterContext();
++    try {
++      Scriptable scope = initScope(cx, context);
++      Object ret = cx.evaluateString(scope, script, getFilename(), 0, null);
++      return Context.jsToJava(ret, Object.class);
++    } catch (RhinoException re) {
++      throw new ScriptException(re.getMessage(), re.sourceName(), re.lineNumber(),
++          re.columnNumber());
++    } finally {
++      Context.exit();
++    }
++  }
++
++  @Override
++  public Object eval(Reader reader, ScriptContext context) throws ScriptException {
++    Context cx = ctxFactory.enterContext();
++    try {
++      Scriptable scope = initScope(cx, context);
++      Object ret = cx.evaluateReader(scope, reader, getFilename(), 0, null);
++      return Context.jsToJava(ret, Object.class);
++    } catch (RhinoException re) {
++      throw new ScriptException(re.getMessage(), re.sourceName(), re.lineNumber(),
++          re.columnNumber());
++    } catch (IOException ioe) {
++      throw new ScriptException(ioe);
++    } finally {
++      Context.exit();
++    }
++  }
++
++  @Override
++  public CompiledScript compile(String script) throws ScriptException {
++    Context cx = ctxFactory.enterContext();
++    try {
++      configureContext(cx);
++      Script s =
++          cx.compileString(script, getFilename(), 1, null);
++      return new RhinoCompiledScript(this, s);
++    } catch (RhinoException re) {
++      throw new ScriptException(re.getMessage(), re.sourceName(), re.lineNumber(),
++          re.columnNumber());
++    } finally {
++      Context.exit();
++    }
++  }
++
++  @Override
++  public CompiledScript compile(Reader script) throws ScriptException {
++    Context cx = ctxFactory.enterContext();
++    try {
++      configureContext(cx);
++      Script s =
++          cx.compileReader(script, getFilename(), 1, null);
++      return new RhinoCompiledScript(this, s);
++    } catch (RhinoException re) {
++      throw new ScriptException(re.getMessage(), re.sourceName(), re.lineNumber(),
++          re.columnNumber());
++    } catch (IOException ioe) {
++      throw new ScriptException(ioe);
++    } finally {
++      Context.exit();
++    }
++  }
++
++  Object eval(Script script, ScriptContext sc) throws ScriptException {
++    Context cx = ctxFactory.enterContext();
++    try {
++      Scriptable scope = initScope(cx, sc);
++      Object ret = script.exec(cx, scope);
++      return Context.jsToJava(ret, Object.class);
++    } catch (RhinoException re) {
++      throw new ScriptException(re.getMessage(), re.sourceName(), re.lineNumber(),
++          re.columnNumber());
++    } finally {
++      Context.exit();
++    }
++  }
++
++  @Override
++  public Object invokeFunction(String name, Object... args)
++      throws ScriptException, NoSuchMethodException {
++    return invokeMethod(null, name, args);
++  }
++
++  @Override
++  public Object invokeMethod(Object thiz, String name, Object... args)
++      throws ScriptException, NoSuchMethodException {
++    return invokeMethodRaw(thiz, name, Object.class, args);
++  }
++
++  Object invokeMethodRaw(Object thiz, String name, Class<?> returnType, Object... args)
++      throws ScriptException, NoSuchMethodException {
++    Context cx = ctxFactory.enterContext();
++    try {
++      Scriptable scope = initScope(cx, context);
++
++      Scriptable localThis;
++      if (thiz == null) {
++        localThis = scope;
++      } else {
++        localThis = Context.toObject(thiz, scope);
++      }
++
++      Object f = ScriptableObject.getProperty(localThis, name);
++      if (f == Scriptable.NOT_FOUND) {
++        throw new NoSuchMethodException(name);
++      }
++      if (!(f instanceof Callable)) {
++        throw new ScriptException("\"" + name + "\" is not a function");
++      }
++      Callable func = (Callable) f;
++
++      if (args != null) {
++        for (int i = 0; i < args.length; i++) {
++          args[i] = Context.javaToJS(args[i], scope);
++        }
++      }
++
++      Object ret = func.call(cx, scope, localThis, args);
++      if (returnType == Void.TYPE) {
++        return null;
++      }
++      return Context.jsToJava(ret, returnType);
++
++    } catch (RhinoException re) {
++      throw new ScriptException(re.getMessage(), re.sourceName(), re.lineNumber(),
++          re.columnNumber());
++    } finally {
++      Context.exit();
++    }
++  }
++
++  @Override
++  public <T> T getInterface(Class<T> clasz) {
++    if ((clasz == null) || !clasz.isInterface()) {
++      throw new IllegalArgumentException("Not an interface");
++    }
++    Context cx = ctxFactory.enterContext();
++    try {
++      Scriptable scope = initScope(cx, context);
++      if (methodsMissing(scope, clasz)) {
++        return null;
++      }
++    } catch (ScriptException se) {
++      return null;
++    } finally {
++      Context.exit();
++    }
++    return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
++        new Class<?>[]{clasz}, new RhinoInvocationHandler(this, null));
++  }
++
++  @Override
++  public <T> T getInterface(Object thiz, Class<T> clasz) {
++    if ((clasz == null) || !clasz.isInterface()) {
++      throw new IllegalArgumentException("Not an interface");
++    }
++    Context cx = ctxFactory.enterContext();
++    try {
++      Scriptable scope = initScope(cx, context);
++      Scriptable thisObj = Context.toObject(thiz, scope);
++      if (methodsMissing(thisObj, clasz)) {
++        return null;
++      }
++    } catch (ScriptException se) {
++      return null;
++    } finally {
++      Context.exit();
++    }
++    return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
++        new Class<?>[]{clasz}, new RhinoInvocationHandler(this, thiz));
++  }
++
++  @Override
++  public Bindings createBindings() {
++    return new SimpleBindings();
++  }
++
++  @Override
++  public ScriptEngineFactory getFactory() {
++    return factory;
++  }
++
++  private void configureContext(Context cx) throws ScriptException {
++    Object lv = get(ScriptEngine.LANGUAGE_VERSION);
++    if (lv != null) {
++      cx.setLanguageVersion(parseInteger(lv));
++    }
++    Object ol = get(OPTIMIZATION_LEVEL);
++    if (ol != null) {
++      cx.setOptimizationLevel(parseInteger(ol));
++    }
++  }
++
++  private int parseInteger(Object v) throws ScriptException {
++    if (v instanceof String) {
++      try {
++        return Integer.parseInt((String) v);
++      } catch (NumberFormatException nfe) {
++        throw new ScriptException("Invalid number " + v);
++      }
++    } else if (v instanceof Integer) {
++      return (Integer) v;
++    } else {
++      throw new ScriptException("Value must be a string or number");
++    }
++  }
++
++  private String getFilename() {
++    Object fn = get(ScriptEngine.FILENAME);
++    if (fn instanceof String) {
++      return (String) fn;
++    }
++    return DEFAULT_FILENAME;
++  }
++
++  private boolean methodsMissing(Scriptable scope, Class<?> clasz) {
++    for (Method m : clasz.getMethods()) {
++      if (m.getDeclaringClass() == Object.class) {
++        continue;
++      }
++      Object methodObj = ScriptableObject.getProperty(scope, m.getName());
++      if (!(methodObj instanceof Callable)) {
++        return true;
++      }
++    }
++    return false;
++  }
++
++  private static final class CtxFactory
++      extends ContextFactory {
++
++    @Override
++    protected void onContextCreated(Context cx) {
++      cx.setLanguageVersion(Context.VERSION_ES6);
++      cx.setOptimizationLevel(DEFAULT_OPT);
++      cx.setGeneratingDebug(DEFAULT_DEBUG);
++    }
++  }
++}
+--- /dev/null
++++ b/src/org/mozilla/javascript/engine/RhinoScriptEngineFactory.java
+@@ -0,0 +1,140 @@
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++package org.mozilla.javascript.engine;
++
++import java.util.Arrays;
++import java.util.Collections;
++import java.util.List;
++import javax.script.ScriptEngine;
++import javax.script.ScriptEngineFactory;
++import org.mozilla.javascript.Context;
++
++/**
++ * <p>
++ * This is an implementation of the standard Java "ScriptEngine" for Rhino. If the Rhino engine
++ * (typically in the form of the "rhino-engine" JAR) is in the classpath, then this script
++ * engine will be activated.
++ * </p>
++ * <p>
++ * See the list of constants in this class for the list of language names, file extensions, and
++ * MIME types that this engine supports. This list is essentially the same as the list supported
++ * in the Nashorn script engine that was included in Java 8.
++ * </p>
++ * <p>
++ * Since this engine and Nashorn support the same language and file extensions, then unless
++ * you are sure you are running in an environment that has Nashorn, the best way to get this
++ * engine is to call ScriptEngine.getEngineByName("rhino") to ask for Rhino directly.
++ * </p>
++ */
++public class RhinoScriptEngineFactory
++  implements ScriptEngineFactory {
++
++  public static final String NAME = "rhino";
++  public static final String LANGUAGE = "javascript";
++  public static final List<String> NAMES =
++      Arrays.asList("rhino", "Rhino", "javascript", "JavaScript");
++  public static final List<String> EXTENSIONS =
++      Collections.singletonList("js");
++  public static final List<String> MIME_TYPES =
++      Arrays.asList("application/javascript", "application/ecmascript",
++          "text/javascript", "text/ecmascript");
++  public static final String LANGUAGE_VERSION =
++      String.valueOf(RhinoScriptEngine.DEFAULT_LANGUAGE_VERSION);
++
++  @Override
++  public String getEngineName() {
++    return NAME;
++  }
++
++  @Override
++  public String getEngineVersion() {
++    Context cx = Context.enter();
++    try {
++      String v = cx.getImplementationVersion();
++      return (v == null ? "unknown" : v);
++    } finally {
++      Context.exit();
++    }
++  }
++
++  @Override
++  public List<String> getExtensions() {
++    return EXTENSIONS;
++  }
++
++  @Override
++  public List<String> getMimeTypes() {
++    return MIME_TYPES;
++  }
++
++  @Override
++  public List<String> getNames() {
++    return NAMES;
++  }
++
++  @Override
++  public String getLanguageName() {
++    return LANGUAGE;
++  }
++
++  @Override
++  public String getLanguageVersion() {
++    return LANGUAGE_VERSION;
++  }
++
++  @Override
++  public Object getParameter(String key) {
++    switch (key) {
++      case ScriptEngine.ENGINE:
++        return getEngineName();
++      case ScriptEngine.ENGINE_VERSION:
++        return getEngineVersion();
++      case ScriptEngine.LANGUAGE:
++        return getLanguageName();
++      case ScriptEngine.LANGUAGE_VERSION:
++        return getLanguageVersion();
++      case ScriptEngine.NAME:
++        return NAME;
++      case "THREADING":
++        // Engines are explicitly not thread-safe
++        return null;
++      default:
++        return null;
++    }
++  }
++
++  @Override
++  public String getMethodCallSyntax(String obj, String m, String... args) {
++    StringBuilder sb = new StringBuilder();
++    sb.append(obj).append('.').append(m).append('(');
++    for (int i = 0; i < args.length; i++) {
++      if (i > 0) {
++        sb.append(',');
++      }
++      sb.append(args[i]);
++    }
++    sb.append(");");
++    return sb.toString();
++  }
++
++  @Override
++  public String getOutputStatement(String toDisplay) {
++    return "print('" + toDisplay + "');";
++  }
++
++  @Override
++  public String getProgram(String... statements) {
++    StringBuilder sb = new StringBuilder();
++    for (String stmt : statements) {
++      sb.append(stmt).append(";\n");
++    }
++    return sb.toString();
++  }
++
++  @Override
++  public ScriptEngine getScriptEngine() {
++    return new RhinoScriptEngine(this);
++  }
++}
+--- /dev/null
++++ b/testsrc/org/mozilla/javascript/tests/scriptengine/BuiltinsTest.java
+@@ -0,0 +1,54 @@
++package org.mozilla.javascript.tests.scriptengine;
++
++import java.io.StringWriter;
++import javax.script.ScriptContext;
++import javax.script.ScriptEngine;
++import javax.script.ScriptEngineManager;
++import javax.script.ScriptException;
++import javax.script.SimpleScriptContext;
++import org.junit.Before;
++import org.junit.BeforeClass;
++import org.junit.Test;
++import org.mozilla.javascript.engine.RhinoScriptEngineFactory;
++
++import static org.junit.Assert.*;
++
++public class BuiltinsTest {
++
++  private static ScriptEngineManager manager;
++
++  private ScriptEngine engine;
++
++  @BeforeClass
++  public static void init() {
++    manager = new ScriptEngineManager();
++    manager.registerEngineName("rhino", new RhinoScriptEngineFactory());
++  }
++
++  @Before
++  public void setup() {
++    engine = manager.getEngineByName("rhino");
++  }
++
++  @Test
++  public void testPrintStdout() throws ScriptException {
++    engine.eval("print('Hello, World!');");
++  }
++
++  @Test
++  public void testPrintWriter() throws ScriptException {
++    StringWriter sw = new StringWriter();
++    ScriptContext sc = new SimpleScriptContext();
++    sc.setWriter(sw);
++    engine.eval("print('one', 2, true);", sc);
++    assertEquals(sw.toString(), "one2true\n");
++  }
++
++  @Test
++  public void testPrintWriterGeneric() throws ScriptException {
++    StringWriter sw = new StringWriter();
++    engine.getContext().setWriter(sw);
++    engine.eval(engine.getFactory().getOutputStatement("Display This!"));
++    assertEquals(sw.toString(), "Display This!\n");
++  }
++}
+--- /dev/null
++++ b/testsrc/org/mozilla/javascript/tests/scriptengine/FactoryTest.java
+@@ -0,0 +1,56 @@
++package org.mozilla.javascript.tests.scriptengine;
++
++import javax.script.ScriptEngine;
++import javax.script.ScriptEngineFactory;
++import javax.script.ScriptEngineManager;
++import org.junit.Test;
++import org.mozilla.javascript.engine.RhinoScriptEngine;
++import org.mozilla.javascript.engine.RhinoScriptEngineFactory;
++
++import static org.junit.Assert.*;
++
++/*
++ * A series of tests that depend on us having our engine registered with the
++ * ScriptEngineManager by default.
++ */
++public class FactoryTest {
++
++  @Test
++  public void findRhinoFactory() {
++    ScriptEngineManager manager = new ScriptEngineManager();
++    for (ScriptEngineFactory factory : manager.getEngineFactories()) {
++      if (factory instanceof RhinoScriptEngineFactory) {
++        assertEquals("rhino", factory.getEngineName());
++        assertEquals("rhino", factory.getParameter(ScriptEngine.ENGINE));
++        assertEquals("rhino", factory.getParameter(ScriptEngine.NAME));
++        // This could be "unknown" if we're not running from a regular JAR
++        assertFalse(factory.getEngineVersion().isEmpty());
++        assertEquals("javascript", factory.getLanguageName());
++        assertEquals("javascript", factory.getParameter(ScriptEngine.LANGUAGE));
++        assertEquals("200", factory.getLanguageVersion());
++        assertEquals("200", factory.getParameter(ScriptEngine.LANGUAGE_VERSION));
++        assertNull(factory.getParameter("THREADING"));
++        assertTrue(factory.getExtensions().contains("js"));
++        assertTrue(factory.getMimeTypes().contains("application/javascript"));
++        assertTrue(factory.getMimeTypes().contains("application/ecmascript"));
++        assertTrue(factory.getMimeTypes().contains("text/javascript"));
++        assertTrue(factory.getMimeTypes().contains("text/ecmascript"));
++        assertTrue(factory.getNames().contains("rhino"));
++        assertTrue(factory.getNames().contains("Rhino"));
++        assertTrue(factory.getNames().contains("javascript"));
++        assertTrue(factory.getNames().contains("JavaScript"));
++        return;
++      }
++    }
++    fail("Expected to find Rhino script engine");
++  }
++
++  @Test
++  public void testRhinoFactory() {
++    // This will always uniquely return our engine.
++    // In Java 8, other ways to find it may return Nashorn.
++    ScriptEngine engine = new ScriptEngineManager().getEngineByName("rhino");
++    assertTrue(engine instanceof RhinoScriptEngine);
++
++  }
++}
+--- /dev/null
++++ b/testsrc/org/mozilla/javascript/tests/scriptengine/InvocableTest.java
+@@ -0,0 +1,158 @@
++package org.mozilla.javascript.tests.scriptengine;
++
++import java.io.FileNotFoundException;
++import java.io.FileReader;
++import javax.script.Invocable;
++import javax.script.ScriptEngine;
++import javax.script.ScriptEngineManager;
++import javax.script.ScriptException;
++import org.junit.Before;
++import org.junit.BeforeClass;
++import org.junit.Test;
++import org.mozilla.javascript.engine.RhinoScriptEngineFactory;
++
++import static org.junit.Assert.*;
++
++public class InvocableTest {
++
++  private static ScriptEngineManager manager;
++
++  private ScriptEngine engine;
++  private Invocable iEngine;
++
++  @BeforeClass
++  public static void init() {
++    manager = new ScriptEngineManager();
++    manager.registerEngineName("rhino", new RhinoScriptEngineFactory());
++  }
++
++  @Before
++  public void setup() {
++    engine = manager.getEngineByName("rhino");
++    iEngine = (Invocable) engine;
++  }
++
++  @Test
++  public void invokeFunctionTest() throws ScriptException, NoSuchMethodException {
++    engine.eval("function foo(a, b) { return a + b; }");
++    Object result = iEngine.invokeFunction("foo", 2, 2);
++    assertEquals(result, 4L);
++  }
++
++  @Test
++  public void invokeScriptFunctionTest() throws ScriptException, NoSuchMethodException {
++    Object scriptObj = engine.eval("let o = {};\n"
++        + "o.test = function(x) { return x + 2; }\n"
++        + "o;");
++    assertEquals(4L, iEngine.invokeMethod(scriptObj, "test", 2));
++  }
++
++  @Test
++  public void invokeGenericFunctionTest() throws ScriptException, NoSuchMethodException {
++    engine.eval("let o = {};\n"
++        + "o.test = function(x) { return x + 2; }\n");
++    Object result = engine.eval(engine.getFactory().getMethodCallSyntax("o", "test", "1"));
++    assertEquals(3L, result);
++  }
++
++  @Test
++  public void invokeGenericFunctionTest2() throws ScriptException, NoSuchMethodException {
++    engine.eval("let o = {};\n"
++        + "o.test = function(x, y) { return x + y; }\n");
++    Object result = engine.eval(engine.getFactory().getMethodCallSyntax("o", "test", "1", "7"));
++    assertEquals(8L, result);
++  }
++
++  @Test
++  public void invokeMethodTest()
++      throws ScriptException, NoSuchMethodException, FileNotFoundException {
++    engine.eval(new FileReader("testsrc/assert.js"));
++    engine.eval("function FooObj() { this.x = 0; }\n"
++        + "FooObj.prototype.set = function(a, b) { this.x = a + b; }");
++    engine.eval("let f = new FooObj();\n"
++        + "assertEquals(f.x, 0);\n"
++        + "f.set(2, 2);\n"
++        + "assertEquals(f.x, 4);");
++
++    Object fooObj = engine.eval("let y = new FooObj(); y");
++    assertNotNull(fooObj);
++    iEngine.invokeMethod(fooObj, "set", 3, 3);
++    Object result = engine.eval("y.x");
++    assertEquals(result, 6L);
++  }
++
++  @Test
++  public void interfaceFunctionTest()
++      throws ScriptException, FileNotFoundException {
++    engine.eval(new FileReader("testsrc/assert.js"));
++    engine.eval("var foo = 'initialized';\n"
++        + "function setFoo(v) { foo = v; }\n"
++        + "function getFoo() { return foo; }\n"
++        + "function addItUp(a, b) { return a + b; }");
++    I tester = iEngine.getInterface(I.class);
++    assertEquals(tester.getFoo(), "initialized");
++    tester.setFoo("tested");
++    assertEquals(tester.getFoo(), "tested");
++    assertEquals(tester.addItUp(100, 1), 101);
++  }
++
++  @Test
++  public void interfaceMethodTest()
++      throws ScriptException, FileNotFoundException {
++    engine.eval(new FileReader("testsrc/assert.js"));
++    Object foo = engine.eval("function Foo() { this.foo = 'initialized' }\n"
++        + "Foo.prototype.setFoo = function(v) { this.foo = v; };\n"
++        + "Foo.prototype.getFoo = function() { return this.foo; };\n"
++        + "Foo.prototype.addItUp = function(a, b) { return a + b; };\n"
++        + "new Foo();");
++    I tester = iEngine.getInterface(foo, I.class);
++    assertEquals(tester.getFoo(), "initialized");
++    tester.setFoo("tested");
++    assertEquals(tester.getFoo(), "tested");
++    assertEquals(tester.addItUp(100, 1), 101);
++  }
++
++  @Test
++  public void interfaceFunctionMissingTest() {
++    I tester = iEngine.getInterface(I.class);
++    assertNull(tester);
++  }
++
++  @Test
++  public void interfaceMethodMissingTest()
++      throws ScriptException {
++    // Functions defined, but not on the right object
++    Object foo = engine.eval("var foo = 'initialized';\n"
++        + "function setFoo(v) { foo = v; }\n"
++        + "function getFoo() { return foo; }\n"
++        + "function addItUp(a, b) { return a + b; }\n"
++        + "function Foo() {}\n"
++        + "new Foo();");
++    I tester = iEngine.getInterface(foo, I.class);
++    assertNull(tester);
++  }
++
++  @Test
++  public void invokeNotFoundTest() {
++    assertThrows(NoSuchMethodException.class, () -> {
++      iEngine.invokeFunction("foo", 2, 2);
++    });
++  }
++
++  @Test
++  public void invokeNotFunctionTest() {
++    assertThrows(ScriptException.class, () -> {
++      engine.eval("foo = 'bar';");
++      iEngine.invokeFunction("foo", 2, 2);
++    });
++  }
++
++  interface I {
++
++    void setFoo(String v);
++
++    String getFoo();
++
++    int addItUp(int a, int b);
++  }
++}
+--- /dev/null
++++ b/testsrc/org/mozilla/javascript/tests/scriptengine/ScriptEngineTest.java
+@@ -0,0 +1,276 @@
++package org.mozilla.javascript.tests.scriptengine;
++
++import java.io.File;
++import java.io.FileReader;
++import java.io.IOException;
++import java.io.StringReader;
++import javax.script.Bindings;
++import javax.script.Compilable;
++import javax.script.CompiledScript;
++import javax.script.ScriptContext;
++import javax.script.ScriptEngine;
++import javax.script.ScriptEngineFactory;
++import javax.script.ScriptEngineManager;
++import javax.script.ScriptException;
++import javax.script.SimpleBindings;
++import javax.script.SimpleScriptContext;
++import org.junit.Before;
++import org.junit.BeforeClass;
++import org.junit.Test;
++import org.mozilla.javascript.engine.RhinoScriptEngine;
++import org.mozilla.javascript.engine.RhinoScriptEngineFactory;
++
++import static org.junit.Assert.*;
++
++public class ScriptEngineTest {
++
++  private static ScriptEngineManager manager;
++  private ScriptEngine engine;
++  private Compilable cEngine;
++
++  @BeforeClass
++  public static void initManager() {
++    manager = new ScriptEngineManager();
++    manager.registerEngineName("rhino", new RhinoScriptEngineFactory());
++  }
++
++  @Before
++  public void init() {
++    engine = manager.getEngineByName("rhino");
++    cEngine = (Compilable) engine;
++  }
++
++  @Test
++  public void testHello() throws ScriptException {
++    Object result = engine.eval("'Hello, World!';");
++    assertEquals(result, "Hello, World!");
++  }
++
++  @Test
++  public void testHelloInterpreted() throws ScriptException {
++    engine.put(RhinoScriptEngine.OPTIMIZATION_LEVEL, -1);
++    Object result = engine.eval("'Hello, World!';");
++    assertEquals(result, "Hello, World!");
++  }
++
++
++  @Test
++  public void testHelloReader() throws ScriptException {
++    String src = "1 + 1;";
++    StringReader sr = new StringReader(src);
++    Object result = engine.eval(sr);
++    assertEquals(result, 2L);
++  }
++
++  @Test
++  public void testGenericStatements() throws ScriptException {
++    Object result = engine.eval(engine.getFactory().getProgram(
++        "let x = 1;",
++        "let y = 2",
++        "x + y"
++    ));
++    assertEquals(3L, result);
++  }
++
++  @Test
++  public void testThrows() {
++    assertThrows(ScriptException.class, () -> {
++      engine.eval("throw 'This is an error'");
++    });
++  }
++
++  @Test
++  public void testEngineBindings() throws IOException, ScriptException {
++    engine.put("string", "Hello");
++    engine.put("integer", 123);
++    engine.put("a", "a");
++    engine.put("b", "b");
++    engine.put("c", "c");
++
++    // Ensure that stuff we just stuck in bindings made it to a global
++    engine.eval(new FileReader("testsrc/assert.js"));
++    engine.eval("assertEquals(string, 'Hello');\n"
++        + "assertEquals(integer, 123);\n"
++        + "string = 'Goodbye';\n"
++        + "assertEquals(string, 'Goodbye');");
++    assertEquals(engine.get("string"), "Goodbye");
++
++    // Make sure we can delete
++    engine.getBindings(ScriptContext.ENGINE_SCOPE).remove("string");
++    // This will throw because string is undefined
++    assertThrows(ScriptException.class, () -> {
++      engine.eval("let failing = string + '123';");
++    });
++  }
++
++  @Test
++  public void testEngineScope() throws IOException, ScriptException {
++    engine.put("string", "Hello");
++    engine.put("integer", 123);
++    engine.eval(new FileReader("testsrc/assert.js"));
++    engine.eval("assertEquals(string, 'Hello');"
++        + "assertEquals(integer, 123);");
++
++    // Additional things added to the context but old stuff still there
++    engine.put("second", true);
++    engine.put("integer", 99);
++    engine.eval("assertEquals(string, 'Hello');"
++        + "assertEquals(integer, 99);"
++        + "assertTrue(second);");
++  }
++
++  @Test
++  public void testScopedBindings() throws IOException, ScriptException {
++    ScriptContext sc = new SimpleScriptContext();
++
++    // We treat engine and global scope the same -- if the user actually
++    // uses both, then engine scope overrides global scope.
++    Bindings eb = new SimpleBindings();
++    sc.setBindings(eb, ScriptContext.ENGINE_SCOPE);
++    eb.put("engine", Boolean.TRUE);
++    eb.put("level", 2);
++
++    Bindings gb = new SimpleBindings();
++    sc.setBindings(gb, ScriptContext.GLOBAL_SCOPE);
++    gb.put("global", Boolean.TRUE);
++    gb.put("level", 0);
++
++    engine.eval(new FileReader("testsrc/assert.js"), sc);
++    engine.eval("assertTrue(engine);"
++        + "assertTrue(global);"
++        + "assertEquals(level, 2);", sc);
++  }
++
++  @Test
++  public void testReservedBindings() throws ScriptException {
++    engine.put(ScriptEngine.ENGINE, "engine");
++    engine.put(ScriptEngine.ENGINE_VERSION, "123");
++    engine.put(ScriptEngine.LANGUAGE, "foo");
++    engine.put(ScriptEngine.NAME, "nothing");
++
++    // Can't actually test for those invalid property names -- but
++    // at least they didn't break the script.
++    assertEquals(engine.eval("'success'"), "success");
++  }
++
++  @Test
++  public void testCompiled() throws ScriptException, IOException {
++    CompiledScript asserts =
++        cEngine.compile(new FileReader("testsrc/assert.js"));
++    CompiledScript tests =
++        cEngine.compile("assertEquals(compiled, true);");
++
++    // Fails because asserts have not been loaded
++    assertThrows(ScriptException.class, tests::eval);
++
++    asserts.eval();
++    // Fails because value has not been set
++    assertThrows(ScriptException.class, tests::eval);
++
++    engine.put("compiled", Boolean.TRUE);
++    tests.eval();
++  }
++
++  @Test
++  public void testCompiled2() throws ScriptException, IOException {
++    CompiledScript asserts =
++        cEngine.compile(new FileReader("testsrc/assert.js"));
++    CompiledScript init =
++        cEngine.compile("value = 0;");
++    CompiledScript tests =
++        cEngine.compile("assertEquals(value, expectedValue);"
++            + "value += 1;");
++
++    asserts.eval();
++    init.eval();
++    for (int i = 0; i <= 10; i++) {
++      engine.put("expectedValue", i);
++      tests.eval();
++    }
++  }
++
++  @Test
++  public void testCompiledThrows() throws ScriptException {
++    engine.put(ScriptEngine.FILENAME, "throws1.js");
++    CompiledScript throw1 = cEngine.compile("throw 'one';");
++    engine.put(ScriptEngine.FILENAME, "throws2.js");
++    CompiledScript throw2 = cEngine.compile("throw 'two';");
++
++    try {
++      throw1.eval();
++      fail("Expected a throw");
++    } catch (ScriptException se) {
++      assertTrue(se.getMessage().startsWith("one"));
++      assertEquals("throws1.js", se.getFileName());
++      assertEquals(1, se.getLineNumber());
++    }
++
++    try {
++      throw2.eval();
++      fail("Expected a throw");
++    } catch (ScriptException se) {
++      assertTrue(se.getMessage().startsWith("two"));
++      assertEquals("throws2.js", se.getFileName());
++      assertEquals(1, se.getLineNumber());
++    }
++  }
++
++  @Test
++  public void testCantCompile() {
++    assertThrows(ScriptException.class, () -> {
++      cEngine.compile("This is not JavaScript at all!");
++    });
++  }
++
++  @Test
++  public void testLanguageVersion() throws ScriptException {
++    // Default language version is modernish
++    ScriptEngine newEngine = manager.getEngineByName("rhino");
++    assertEquals(newEngine.eval("Symbol() == Symbol()"), Boolean.FALSE);
++
++    // Older language versions
++    ScriptEngine oldEngine = manager.getEngineByName("rhino");
++    oldEngine.put(ScriptEngine.LANGUAGE_VERSION, 120);
++    assertThrows(ScriptException.class, () -> {
++      oldEngine.eval("Symbol() == Symbol()");
++    });
++
++    // The same with a string
++    ScriptEngine olderEngine = manager.getEngineByName("rhino");
++    olderEngine.put(ScriptEngine.LANGUAGE_VERSION, "100");
++    assertThrows(ScriptException.class, () -> {
++      olderEngine.eval("Symbol() == Symbol()");
++    });
++  }
++
++  @Test
++  public void testBadLanguageVersion() {
++    assertThrows(ScriptException.class, () -> {
++      engine.put(ScriptEngine.LANGUAGE_VERSION, "Not a number");
++      engine.eval("print('Hi!');");
++    });
++    assertThrows(ScriptException.class, () -> {
++      engine.put(ScriptEngine.LANGUAGE_VERSION, 3.14);
++      engine.eval("print('Hi!');");
++    });
++  }
++
++  @Test
++  public void testFilename() {
++    engine.put(ScriptEngine.FILENAME, "test.js");
++    try {
++      engine.eval("throw 'This is an exception';");
++    } catch (ScriptException se) {
++      assertEquals(se.getFileName(), "test.js");
++    }
++  }
++
++  @Test
++  public void testJavaObject() throws ScriptException {
++    File f = new File("testsrc/assert.js");
++    String absVal = f.getAbsolutePath();
++    engine.put("file", f);
++    Object result = engine.eval("file.getAbsolutePath();");
++    assertEquals(absVal, result);
++  }
++}
+--- a/build.xml
++++ b/build.xml
+@@ -87,7 +87,9 @@
+          basedir="${classes}"
+          manifest="src/manifest"
+          compress="${jar-compression}"
+-     />
++    >
++      <fileset dir="src" includes="META-INF/**"/>
++    </jar>
+   </target>
+ 
+   <target name="console" depends="jar">


=====================================
debian/patches/series
=====================================
@@ -3,3 +3,5 @@
 06_preserve-backward-compatibility.patch
 07_fix-context-implementation-version.patch
 08_fix-jar-version-number.patch
+script-engine.patch
+


=====================================
debian/rules
=====================================
@@ -7,7 +7,7 @@ BUILDDIR = build/rhino$(VERSION)
 	dh $@ --with javahelper --with maven-repo-helper
 
 override_dh_auto_build:
-	dh_auto_build -- jar javadoc
+	dh_auto_build -- jar javadoc -Dsource-level=7 -Dtarget-jvm=7
 
 override_dh_installchangelogs:
 	dh_installchangelogs -- RELEASE-NOTES.md



View it on GitLab: https://salsa.debian.org/java-team/rhino/-/compare/c6de93800d99897e3535a600cea1db6aa5fcda0c...1b6855899385bb7a6561183976701c10616db41e

-- 
View it on GitLab: https://salsa.debian.org/java-team/rhino/-/compare/c6de93800d99897e3535a600cea1db6aa5fcda0c...1b6855899385bb7a6561183976701c10616db41e
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-java-commits/attachments/20210208/64aeb244/attachment.html>


More information about the pkg-java-commits mailing list