Object-namer launchers
The abstract class ObjectNamerLauncher
is
the base class for object-namer launchers. An object-namer launcher
processes input strings, files, or streams that contain YAML or JSON
objects. These can contain expressions written in the
ESP scripting language,
which is executed in a mode that disables import
statements. While the import
statement is disabled,
the Java classes that can be used (and how they are used) can be
configured when an object-namer is created. If a simulation or
animation service is provided via a web server, for example,
limiting the classes that a user may access has security
advantages. Object-namer launchers do not have to be used
explicitly: the program yrunner
will handle these for
the user.
The following sections describe the following:
- Instantiating an object-namer launcher.
- Using an object-namer launcher.
- Subclassing an object-namer launcher.
- Configuring object-namer launchers.
- SPIs for accessing launchers and additional classes.
- Generating class documentation.
Instantiating an object namer
While a constructor can be used directly, normally and object-namer
launcher will be created by using a newInstance
method:
-
ObjectNamerLauncher.newInstance(String)
. This method creates an object-namer launcher, looked up by name, for which scripting-language expressions will allow the use of a specific set of Java classes. -
ObjectNamerLauncher.newInstance(String,String...)
. This method creates an object-namer launcher, looked up by names, for which the scripting-language expressions will allow the use of not only the classes associated with the object-namer launcher, but also additional collections of classes specified by the optional arguments and called launcher-data additions.
yrunner
program, but it is easy to
use the newInstance
methods directly. A
yrunner
option will print a table showing the names for
launchers and collections of additional classes.
Object namers have a constraint: there can be only one active per
thread. This property is enforced by constructors. An object namer
is active until its ObjectNamerLauncher.close()
method is called. Because object namers implement the
AutoCloseable
interface, they may be used in a Java
try-with-resources block, in which case the call to
ObjectNamerLauncher.close()
is implicit.
Using an object namer
An object namer processes text in either JSON or YAML format. The methods that process text are
-
ObjectNamerLauncher.process(java.io.Reader,boolean)
. -
ObjectNamerLauncher.process(String,boolean)
. -
ObjectNamerLauncher.process(String,java.io.Reader,boolean)
. -
ObjectNamerLauncher.process(String,String,boolean)
. -
ObjectNamerLauncher.process(String,org.bzdev.util.JSOps,boolean,java.util.Map)
.
yrunner
command is used. Regardless, the input will a text file using the
format specified by YAML or JSON, with a UTF-8 character encoding. For
the YAML case, tabs are not allowed. The text file should contain
an object, or an array whose elements are objects (the parser actually
allows arrays of arrays, but the values that are not arrays must be
objects). Each object (whether represented with JSON or YAML syntax)
may have the following properties:
- execute. The value may be an object or a list of objects
with types supported by JSON or YAML. When a value is a
string using the syntax specified for
ExpressionParser
, the string is interpreted as follows:- a statement starting with "=" will be evaluated.
- a "var" statement will set a variable with a specified name, and its variant using "?=" will provide a default values if the variable is not set externally. The variant using "??=" will provide a default value if the variable is either not defined or has the value null.
- a "function" or "synchronized function" statement will define a function, giving it a name.
- For YAML, once can force the value to be an string by
using "!!str" tag. For example
If the string is a quoted string (delimited by double quotes) or a YAML multi-line string, it is assumed to be a string. To make such a string an expression, on may use a tag:execute: !!str var foo = 10
execute: !bzdev!esp "var foo = 10"
- For JSON, one can use "=" with a quoted string. For example
execute: "= \"var foo = 10\""
- factories. The value is either an object or a sequence of objects. Each of these objects contains a "context" property and a sequence of properties whose names are variable names and whose values are the simple class names of factories. The value for a context is a list with two elements. The first element is a variable name for an object namer. The second is the package in which the factories provided by that context's factories are located.
- define. This section allows one to provide YAML anchors to avoid repetition and is otherwise ignored. This is needed because the variable provided by an expression parser cannot be used to define a common subtree for part of JSON or YAML object: a YAML anchor and alias is used instead.
- create. The value is either an object or a list of objects.
For each object, there are four properties:
- var. The value is the variable storing the object that will be created.
- name. The value is name of the object.
- factory. The value is the variable storing the factory that will be used to create the object.
- configuration. The value is either another object
or a list of objects. This value is factory dependent.
The syntax for an object follows that provided in
the documentation for
NamedObjectFactory
(that documentation describes the use of ECMAScript, but ECMAScript is similar to JSON syntactically and YAML is (approximately) a superset of JSON.
It is sometimes necessary to manipulate ESP variables. The methods
ObjectNamerLauncher.clear()
ObjectNamerLauncher.exists(String)
ObjectNamerLauncher.get(String)
ObjectNamerLauncher.remove(String)
ObjectNamerLauncher.set(String,Object)
ObjectNamerLauncher.variables()
yrunner
command, for
example, has options that set scripting-language variables and some
of these methods are used by yrunner for that purpose.
Subclassing an object-namer launcher
A subclass of ObjectNamerLauncher
is
expected to have two constructors: one with no arguments and one
with a single argument, an instance of
JSObject
. The directory containing the
source code for the subclass should also contain a YAML file using
the syntax shown below.
If LAUNCHER
is the class name of an object-namer launcher
and LAUNCHER_SUPERCLASS
is its immediate superclass, then
class definition for LAUNCHER
(with these two identifiers
replaced with their actual values) is
The methodspublic class LAUNCHER extends LAUNCHER_SUPERCLASS { public static InputStream getResourceStream() { return LAUNCHER.class.getResourceAsStream("LAUNCHER.yaml"); } public LAUNCHER(JSObject initializer) throws ClassNotFoundException, IOException, IllegalAccessException { super(combine(loadFromStream(LAUNCHER.class, LAUNCHER.getResourceAsStream(), 8), initializer)); } public LAUNCHER() throws ClassNotFoundException, IOException, IllegalAccessException { this(null); } }
ObjectNamerLauncher.combine(org.bzdev.util.JSObject,org.bzdev.util.JSObject...)
ObjectNamerLauncher.loadFromStream(Class,java.io.InputStream,int)
Configuring object-namer launchers
The YAML file loaded by a constructor lists classes and how they can be used in an object-namer launcher's scripting environment. A a more detailed description is provided by the API documentation forExpressionParser
, but that documentation
does not describe a YAML representation of the same data.
The YAML file, generally a resource in a JAR file, specifies an a YAML object with the following properties:
- argumentTypes - a
JSArray
providing a list of strings giving the fully qualified class names for arguments used by constructors, functions, and methods. The types String, int, double, Integer, or Double should not be used, as these are allowed by default. - fieldClasses - a
JSArray
providing a list of strings giving the fully qualified class names for classes containing fields that can be used. The types of the fields that will be included are boolean, int, long, double,String
, or an enumeration. - functionClasses - a
JSArray
providing a list of strings the fully qualified class names for classes whose public, static methods returning an allowable type have a fixed number of arguments whose types are boolean, int, long, double, or a type provided by the argumentTypes property. - methodClasses - a
JSArray
providing a list of strings giving the fully qualified class names classes whose instance methods returning an allowable type have a fixed number of arguments with types int, double, long, boolean,String
, or a type provided by the argumentTypes property. - returnTypes - a
JSArray
providing a list of strings giving the fully qualified class names for objects that the parser can return or can construct. The constructors that will be provided are those with a fixed number of arguments whose types are int, long, double, boolean,String
, or a type provided by the argumentTypes property. - define - this property (actually any property other than the ones listed above) is ignored. If provided it should appear first, and should be used merely to provide anchors so that YAML aliases can be used to minimize the replication of class names.
The SPIs for object-namer launchers
Bothyrunner
and the
newInstance
methods will
look up object-namer launchers, and collections of classes, by name.
A name is provided by an SPI (Service Provider Interface). There are
two that are applicable:
Methods common to both and that must be implemented are:
ONLauncherData.getName()
.ONLauncherData.getInputStream()
. This method returns an input stream containing UTF-8 text, possibly with tabs, and providing a YAML input file describing classes and how they are used as described above.ONLauncherData.description()
. This is a one-line description of the data.ONLauncherData.getTabSpacing()
. The tab spacing accepted when the YAML input stream is read. The default value is 8.
The interface ONLauncherProvider
adds one
additional method:
{ONLauncherProvider.onlClass()
. This class
provides the class name of the object-namer launcher that matches the
name provided by ONLauncherData.getName()
.
Generally, the SPIs should not be in the same directory as the
classes they provide: otherwise the SPI classes might appear in the
API documentation generated with the javadoc command. These SPIs
typically have trivial implementations. For example, the SPI for
the org.bzdev.math
package, an implementation of
ONLauncherData
, is only a few lines of
code:
A file in the same directory namedpackage org.bzdev.providers.math; import java.io.InputStream; import java.util.ResourceBundle; import org.bzdev.lang.spi.ONLauncherData; public class MathLauncherData implements ONLauncherData { public MathLauncherData() {} public String getName() { return "math"; } public InputStream getInputStream() { return getClass().getResourceAsStream("MathLauncherData.yaml"); } @Override public String description() { return ResourceBundle .getBundle("org.bzdev.providers.math.lpack.MathLauncherData") .getString("description"); } }
MathLauncherData.yaml
contains a list of classes that this service provider makes
accessible. The use of a resource bundle allows the description to be
localized so that multiple languages can be supported.
Similarly for the org.bzdev.devqsim
package the implantation
of ONLauncherProvider
. is also only a few
lines of code long:
In this case,package org.bzdev.providers.devqsim; import java.io.InputStream; import java.util.ResourceBundle; import org.bzdev.obnaming.spi.ONLauncherProvider; import org.bzdev.devqsim.SimulationLauncher; public class SimulationLauncherProvider implements ONLauncherProvider { @Override public String getName() { return "devqsim"; } @Override public Class
onlClass() { return SimulationLauncher.class; } @Override public InputStream getInputStream() { return SimulationLauncher.getResourceStream(); } @Override public String description() { return ResourceBundle .getBundle("org.bzdev.providers.devqsim.lpack.SimulationLauncher") .getString("description"); } }
SimulationLauncher
is placed in the
org.bzdev.devqsim
package along with the resource
SimulationLauncher.yaml
, primarily because Java 11
makes it difficult to read resources from a different package
when Java modules are used.
The module-info.jar file for org.bzdev.math contains the statement
and the file META-INF/services/org.bzdev.lang.spi.ONLaucherData will contain the lineprovides org.bzdev.lang.spi.ONLauncherData with org.bzdev.providers.math.MathLauncherData, ...
org.bzdev.providers.math.MathLauncherData
Similarly the module-info.jar file for org.bzdev.devqsim contains the statement
and the file META-INF/services/org.bzdev.obnaming.spi.ONLauncherProvider contains the lineprovides org.bzdev.obnaming.spi.ONLauncherProvider with org.bzdev.providers.devqsim.SimulationLauncherProvider;
org.bzdev.providers.devqsim.SimulationLauncherProvider
Providing both a META-INF/services file and an entry in a module-info.jar file should be redundant, but some releases of Java, or at least of openjdk, need both if the service provider is to work as expected whether or not Java modules are used.
Generating class documentation
The class ObjectNamerLauncher
contains
methods for querying its expression parser to recover information
about the classes it supports. While one can use the following classes
to generate documentation, the program yrunner already provides this
capability. The methods described below can be used to generate
documentation in a different format than that provided by
yrunner
.
The following describes the lower-level methods used to generate such documentation or extract other information about the Java classes that can be used.
Several methods return lists of classes based on their usage:
ObjectNamerLauncher.getArgumentClasses()
.ObjectNamerLauncher.getConstants()
.ObjectNamerLauncher.getConstructors()
.ObjectNamerLauncher.getFunctions()
.ObjectNamerLauncher.getMethods()
.ObjectNamerLauncher.getReturnClasses()
.
The method ObjectNamerLauncher.createAPIMap(List)
will set
up a map that associated class names with a URL for their API
documentation. It's argument is a list of URLs that point to the
top-level directories for API documentation generated by
javadoc. Several methods return a
TemplateProcessor.KeyMapList
that will
contain key maps for each class:
ObjectNamerLauncher.keylistForArgumentClasses()
.ObjectNamerLauncher.keylistForConstants()
.ObjectNamerLauncher.keylistForFunctions()
.ObjectNamerLauncher.keylistForMethods()
.ObjectNamerLauncher.keylistForMethods(boolean)
.ObjectNamerLauncher.keylistForReturnClasses()
.
TemplateProcessor
can then be used to generate
documentation.
Finally, several methods,
ObjectNamerLauncher.getProviderKeyMap()
ObjectNamerLauncher.getLauncherNames()
ObjectNamerLauncher.getLauncherDataNames()
ObjectNamerLauncher.getProviderKeyMap()
will provide
key maps that include a name and a description of each launcher
or launcher-data addition, and can be used to generate
formatted documentation.