jsweet/doc/jsweet-language-specifications.md
2018-05-10 15:47:42 +02:00

3555 lines
132 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

JSweet Language Specifications
==============================
Version: 2.x (snapshot)
Author : Renaud Pawlak
Author assistant: Louis Grignon
JSweet JavaDoc API: http://www.jsweet.org/core-api-javadoc/
Note: this markdown is automatically generated from the Latex source file. Do not modify directly.
Content
-------
- [Basic concepts](#basic-concepts)
- [Core types and objects](#core-types-and-objects)
- [Classes](#classes)
- [Interfaces](#interfaces)
- [Untyped objects (maps)](#untyped-objects-maps)
- [Enums](#enums)
- [Globals](#globals)
- [Optional parameters and
overloading](#optional-parameters-and-overloading)
- [Bridging to external JavaScript
elements](#bridging-to-external-javascript-elements)
- [Examples](#examples)
- [Rules for writing definitions (a.k.a.
bridges)](#rules-for-writing-definitions-a.k.a.-bridges)
- [Untyped accesses](#untyped-accesses)
- [Mixins](#mixins)
- [Generating JSweet candies from existing TypeScript
definitions](#generating-jsweet-candies-from-existing-typescript-definitions)
- [Auxiliary types](#auxiliary-types)
- [Functional types](#functional-types)
- [Object types](#object-types)
- [String types](#string-types)
- [Tuple types](#tuple-types)
- [Union types](#union-types)
- [Intersection types](#intersection-types)
- [Semantics](#semantics)
- [Main methods](#main-methods)
- [Initializers](#initializers)
- [Arrays initialization and
allocation](#arrays-initialization-and-allocation)
- [Asynchronous programming](#asynchronous-programming)
- [Name clashes](#name-clashes)
- [Testing the type of an object](#testing-the-type-of-an-object)
- [Variable scoping in lambda
expressions](#variable-scoping-in-lambda-expressions)
- [Scope of *this*](#scope-of-this)
- [Packaging](#packaging)
- [Use your files without any
packaging](#use-your-files-without-any-packaging)
- [Creating a bundle for a
browser](#creating-a-bundle-for-a-browser)
- [Packaging with modules](#packaging-with-modules)
- [Root packages](#root-packages)
- [Packaging a JSweet jar (candy)](#packaging-a-jsweet-jar-candy)
- [Extending the transpiler](#extending-the-transpiler)
- [Core annotations](#core-annotations)
- [Centralizing annotations in
`jsweetconfig.json`](#centralizing-annotations-in-jsweetconfig.json)
- [Programmatic tuning with
adapters](#programmatic-tuning-with-adapters)
- [Extension examples](#extension-examples)
- [Appendix 1: JSweet transpiler
options](#appendix-1-jsweet-transpiler-options)
- [Appendix 2: packaging and static
behavior](#appendix-2-packaging-and-static-behavior)
- [When main methods are invoked](#when-main-methods-are-invoked)
- [Static and inheritance
dependencies](#static-and-inheritance-dependencies)
Basic concepts
--------------
This section presents the JSweet language basic concepts. One must keep
in mind that JSweet, as a Java-to-JavaScript transpiler, is an extension
of Java at compile-time, and executes as JavaScript at runtime. JSweet
aims at being a trade-off between Java and JavaScript, by respecting as
much as possible the Java semantics, but without loosing
interoperability with JavaScript. So, in a way, JSweet can be seen as a
fusion between Java and JavaScript, trying to get the best of both
worlds in one unique and consistent language. In some cases, it is hard
to get the best of both worlds and JSweet makes convenient and practical
choices.
Because JSweet is an open JavaScript transpiler, the user can tune the
JavaScript generation without much efforts, thus making other choices
than default ones to map Java to JavaScript. For example, if the way
JSweet implements Java maps does not suit your context or use case, you
can program a JSweet extension to override the default strategy.
Programming and activating a JSweet extension is fully explained in
Section
<a href="#extending-the-transpiler" data-reference-type="ref" data-reference="extending-the-transpiler">6</a>.
### Core types and objects
JSweet allows the use of primitive Java types, core Java objects
(defined in `java.lang`, many JDK classes (especially `java.util` but
not only), and of core JavaScript objects, which are defined in the
`def.js` package. Next, we describe the use of such core types and
objects.
#### Primitive Java types
JSweet allows the use of Java primitive types (and associated literals).
- `int`, `byte`, `short`, `double`, `float` are all converted to
JavaScript numbers (TypeScript `number` type). Precision usually
does not matter in JSweet, however, casting to `int`, `byte`, or
`short` forces the number to be rounded to the right-length integer.
- `char` follows the Java typing rules but is converted to a
JavaScript `string` by the transpiler.
- `boolean` corresponds to the JavaScript `boolean`.
- `java.lang.String` corresponds to the JavaScript `string`. (not per
say a primitive type, but is immutable and used as the class of
string literals in Java)
A direct consequence of that conversion is that it is not always
possible in JSweet to safely overload methods with numbers or
chars/strings. For instance, the methods `pow(int, int)` and
`pow(double, double)` may raise overloading issues. With a JSweet
context, the transpiler will be able to select the right method, but the
JavaScript interoperability may be a problem. In short, since there is
no difference between `n instanceof Integer` and `n instanceof Double`
(it both means `typeof n === number`) calling `pow(number, number)`
from JavaScript will randomly select one implementation or the other.
This should not be always a problem, but in some particular cases, it
can raise subtle errors. Note that in these cases, the programmers will
be able to tune the JavaScript generation, as it is fully explained in
Section
<a href="#extending-the-transpiler" data-reference-type="ref" data-reference="extending-the-transpiler">6</a>.
Examples of valid statements:
``` java
// warning '==' behaves like JavaScript '===' at runtime
int i = 2;
assert i == 2;
double d = i + 4;
assert d == 6;
String s = "string" + '0' + i;
assert s == "string02";
boolean b = false;
assert !b;
```
The `==` operator behaves like the JavaScript strict equals operator
`===` so that it is close to the Java semantics. Similarly, `!=` is
mapped to `!==`. There is an exception to that behavior which is when
comparing an object to a `null` literal. In that case, JSweet translates
to the loose equality operators so that the programmers see no
distinction between `null` and `undefined` (which are different in
JavaScript but it may be confusing to Java programmers). To control
whether JSweet generates strict or loose operators, you can use the
following helper methods: `jsweet.util.Lang.$strict` and
`jsweet.util.Lang.$loose`. Wrapping a comparison operator in such a
macro will force JSweet to generate a strict or loose operator. For
example:
``` java
import static jsweet.util.Lang.$loose;
[...]
int i = 2;
assert i == 2; // generates i === 2
assert !((Object)"2" == i);
assert $loose((Object)"2" == i); // generates "2" == i
```
#### Allowed Java objects
By default, JSweet maps core Java objects and methods to JavaScript
through the use of built-in macros. It means that the Java code is
directly substituted with a valid JavaScript code that implements
similar behavior. A default mapping is implemented for most useful core
Java classes (`java.lang`, `java.util`). When possible (and when it
makes sense), some partial mapping is implemented for other JDK classes
such as input and output streams, locales, calendars, reflection, etc.
With the default behavior, we can point the following limitations:
- Extending a JDK class is in general not possible, except for some
particular contexts. If extending a JDK class is required, should
should consider to refactor your program, or use a JavaScript
runtime (such as J4TS), which would allow it.
- The Java reflection API (`java.lang.reflect`) is limited to very
basic operations. It is possible to access the classes and the
members, but it is not possible to access types. A more complete
support of Java reflection would be possible, but it would require a
JSweet extension.
- Java 8 streams are not supported yet, but it would be simple to
support them partially (contributions are welcome).
Examples of valid statements:
``` java
Integer i = 2;
assert i == 2;
Double d = i + 4d;
assert d.toString() == "6";
assert !((Object) d == "6");
BiFunction<String, Integer, String> f = (s, i) -> { return s.substring(i); };
assert "bc" == f.apply("abc", 1);
```
#### Getting more Java APIs
With JSweet, it is possible to add a runtime that implements Java APIs
in JavaScript, so that programmers can access more Java APIs and thus
share the same code between Java and JavaScript. The core project for
implementing Java APIs for JSweet is J4TS
(<https://github.com/cincheo/j4ts>) and contains a quite complete
implementation of `java.util.*` classes and other core package. J4TS is
based on a fork of the GWTs JRE emulation, but it is adapted to be
compiled with JSweet. Programmers can use J4TS as a regular JavaScript
library available in our Maven repository.
Although J4TS cannot directly implement the Java core types that
conflict with JavaScript ones (`Boolean`, `Byte`, `Short`, `Integer`,
`Long`, `Float`, `Double`, `Character`, `String`), J4TS contributes to
supporting the static part of them by providing helpers for each class
(`javaemul.internal.BooleanHelper`, `javaemul.internal.ByteHelper`,
...). When the JSweet transpiler meets a static Java method on a type
`java.lang.T` that is not supported as a built-in macro, it delegates to
`javaemul.internal.THelper`, which can provide a JavaScript
implementation for the given static method. That way, by using J4TS,
programmers can use even more of the core JRE API.
#### Java arrays
Arrays can be used in JSweet and are transpiled to JavaScript arrays.
Array initialization, accesses and and iteration are all valid
statements.
``` java
int[] arrayOfInts = { 1, 2, 3, 4};
assert arrayOfInts.length == 4;
assert arrayOfInts[0] == 1;
int i = 0;
for (int intItem : arrayOfInts) {
assert arrayOfInts[i++] == intItem;
}
```
#### Core JavaScript API
The core JavaScript API is defined in `def.js` (the full documentation
can be found at <http://www.jsweet.org/core-api-javadoc/>). Main
JavaScript classes are:
- `def.js.Object`: JavaScript Object class. Common ancestor for
JavaScript objects functions and properties.
- `def.js.Boolean`: JavaScript Boolean class. A wrapper for boolean
values.
- `def.js.Number`: JavaScript Number class. A wrapper for numerical
values.
- `def.js.String`: JavaScript String class. A wrapper and constructor
for strings.
- `def.js.Function`: JavaScript Function class. A constructor for
functions.
- `def.js.Date`: JavaScript Date class, which enables basic storage
and retrieval of dates and times.
- `def.js.Array<T>`: JavaScript Array class. It is used in the
construction of arrays, which are high-level, list-like objects.
- `def.js.Error`: JavaScript Error class. This class implements
`java.lang.RuntimeException` and can be thrown and caught with `try`
... `catch` statements.
When using JavaScript frameworks, programmers should use this API most
of the time, which is HTML5 compatible and follows the JavaScript latest
supported versions. However, for objects that need to be used with Java
literals (numbers, booleans, and strings), the use of the `java.lang`
package classes is recommended. For instance, the jQuery API declares
`$(java.lang.String)` instead of `$(def.js.String)`. This allows the
programmer to write expressions using literals, such as `$("a")` (for
selecting all links in a document).
With JSweet, programmers can easily switch from the Java to JavaScript
API (and conversely) depending on their needs. The `jsweet.util.Lang`
class defines convenient static methods to cast back and forth core Java
objects to their corresponding JavaScript objects. For instance the
`string(...)` method will allow the programmer to switch from the Java
to the JavaScript strings and conversely.
``` java
import static jsweet.util.Lang.string;
// str is a Java string, but is actually a JavaScript string at runtime
String str = "This is a test string";
// str is exactly the same string object, but shown through the JS API
def.js.String str2 = string(str);
// valid: toLowerCase it defined both in Java and JavaScript
str.toLowerCase();
// this method is not JS-compatible, so a macro generates the JS code
str.equalsIgnoreCase("abc");
// direct call to the JS substr method on the JavaScript string
string(str).substr(1);
// or
str2.substr(1);
```
Note: for code sharing between a JavaScript client and a Java server for
instance, it is better to use Java APIs only and avoid JavaScript ones.
JavaScript API will compile valid Java bytecode but trying to execute
them on a JVM will raise unsatisfied link errors.
Here is another example that shows the use of the `array` method to
access the `push` method available on JavaScript arrays.
``` java
import static jsweet.util.Lang.array;
String[] strings = { "a", "b", "c" };
array(strings).push("d");
assert strings[3] == "d";
```
### Classes
Classes in JSweet fully support all types of Java classes declarations.
For example:
``` java
public class BankAccount {
public double balance = 0;
public double deposit(double credit) {
balance += credit;
return this.balance;
}
}
```
Which is transpiled to the following JavaScript code:
``` java
var BankAccount = (function () {
function BankAccount() {
this.balance = 0;
}
BankAccount.prototype.deposit = function(credit) {
this.balance += credit;
return this.balance;
};
return BankAccount;
})();
```
Classes can define constructors, have super classes and be instantiated
exactly like in Java. Similarly to Java, inner classes and anonymous
classes are allowed in JSweet (since version 1.1.0). JSweet supports
both static and regular inner/anonymous classes, which can share state
with enclosing classes. Still like in Java, anonymous classes can access
final variables declared in their scope. For example, the following
declarations are valid in JSweet and will mimic the Java semantics at
runtime so that Java programmers can benefit all the features of the
Java language.
``` java
abstract class C {
public abstract int m();
}
public class ContainerClass {
// inner class
public class InnerClass {
public I aMethod(final int i) {
// anonymous class
return new C() {
@Override
public int m() {
// access to final variable i
return i;
}
}
}
}
}
```
### Interfaces
In JSweet, an interface can be use like in Java. However, on contrary to
Java, there is no associated class available as runtime. When using
interfaces, JSweet generates code to emulate specific Java behaviors
(such as `instanceof` on interfaces).
JSweet supports Java 8 static and default methods. However default
methods are experimental so far and you should use them at your own
risks.
In JSweet, interfaces are more similar to interfaces in TypeScript than
in Java. It means that they must be seen as object signatures, which can
specify functions, but also properties. In order to allow using fields
as properties when defining interfaces, JSweet allows the use of regular
classes annotated with `@jsweet.lang.Interface`. For example, the
following interface types an object `Point` with 2 properties.
``` java
@Interface
public class Point {
public double x;
public double y;
}
```
To Java programmers, this may look like a very odd way to define an
object, but you must remember that it is not a class, but a type for a
JavaScript object. As such, it does not go against the OOP principles.
We can create a JavaScript object typed after the interface. Note that
the following code is not actually creating an instance of the `Point`
interface, it is creating an object that conforms to the interface.
``` java
Point p1 = new Point() {{ x=1; y=1; }};
```
This object creation mechanism is a TypeScript/JavaScript mechanism and
shall not be confused with anonymous classes, which is a Java-like
construction. Because `Point` is annotated with `@Interface`, the
transpiled JavaScript code will be similar to:
``` java
var p1 = Object.defineProperty({ x:1, y:1 }, "_interfaces", ["Point"]);
```
Note that, for each object, JSweet keeps track of which interface it was
created from and of all the potential interfaces implemented by its
class. This interface tracking system is implemented as a special object
property called `__interfaces`. Using that property, JSweet allows the
use of the `instanceof` operator on interfaces like in Java.
#### Optional fields in interfaces
Interfaces can define *optional fields*, which are used to report errors
when the programmer forgets to initialize a mandatory field in an
object. Supporting optional fields in JSweet is done through the use of
`@jsweet.lang.Optional` annotations. For instance:
``` java
@Interface
public class Point {
public double x;
public double y;
@Optional
public double z = 0;
}
```
It is the JSweet compiler that will check that the fields are correctly
initialized, when constructing an object from an interface.
``` java
// no errors (z is optional)
Point p1 = new Point() {{ x=1; y=1; }};
// JSweet reports a compile error since y is not optional
Point p2 = new Point() {{ x=1; z=1; }};
```
#### Special JavaScript functions in interfaces
In JavaScript, objects can have properties and functions, but can also
(not exclusively), be used as constructors and functions themselves.
This is not possible in Java, so JSweet defines special functions for
handling these cases.
- `$apply` is used to state that the object can be used as a function.
- `$new` is used to state that the object can be used as a
constructor.
For instance, if an object `o` is of interface `O` that defines
`$apply()`, writing:
``` java
o.$apply();
```
Will transpile to:
``` java
o();
```
Similarly, if `O` defines `$new()`:
``` java
o.$new();
```
Will transpile to:
``` java
new o();
```
Yes, it does not make sense in Java, but in JavaScript it does!
### Untyped objects (maps)
In JavaScript, object can be seen as maps containing key-value pairs
(key is often called *index*, especially when it is a number). So, in
JSweet, all objects define the special functions (defined on
`def.js.Object`):
- `$get(key)` accesses a value with the given key.
- `$set(key,value)` sets or replace a value for the given key.
- `$delete(key)` deletes the value for the given key.
#### Reflective/untyped accesses
The functions `$get(key)`, `$set(key,value)` and `$delete(key)` can be
seen as a simple reflective API to access object fields and state. Note
also the static method `def.js.Object.keys(object)`, which returns all
the keys defined on a given object.
The following code uses this API to introspect the state of an object
`o`.
``` java
for(String key : def.js.Object.keys(o)) {
console.log("key=" + key + " value=" + o.$get(key));
});
```
When not having the typed API of a given object, this API can be useful
to manipulate the object in an untyped way (of course it should be
avoided as much as possible).
#### Untyped objects initialization
One can use the `$set(key,value)` function to create new untyped object.
For instance:
``` java
Object point = new def.js.Object() {{ $set("x", 1); $set("y", 1); }};
```
It transpiles also to:
``` java
var point = { "x": 1, "y": 1};
```
As a shortcut, one can use the `jsweet.util.Lang.$map` function, which
transpiles to the exact same JavaScript code:
``` java
import static jsweet.util.Lang.$map;
[...]
Object point = $map("x", 1, "y", 1);
```
#### Indexed objects
The type of keys and values can be overloaded for every object. For
example, the `Array<T>` class, will define keys as numbers and values as
objects conforming to type `T`.
In the case of objects indexed with number keys, it is allowed to
implement the `java.lang.Iterable` interface so that it is possible to
use they in *foreach* loops. For instance, the `NodeList` type (from the
DOM) defines an indexed function:
``` java
@Interface
class NodeList implements java.lang.Iterable {
public double length;
public Node item(double index);
public Node $get(double index);
}
```
In JSweet, you can access the node list elements with the `$get`
function, and you can also iterate with the *foreach* syntax. The
following code generates fully valid JavaScript code.
``` java
NodeList nodes = ...
for (int i = 0; i < nodes.length; i++) {
HTMLElement element = (HTMLElement) nodes.$get(i);
[...]
}
// same as:
NodeList nodes = ...
for (Node node : nodes) {
HTMLElement element = (HTMLElement) node;
[...]
}
```
### Enums
JSweet allows the definition of enums similarly to Java. The following
code declares an enum with tree possible values (`A`, `B`, and `C`).
``` java
enum MyEnum {
A, B, C
}
```
The following statements are valid statements in JSweet.
``` java
MyEnum e = MyEnum.A;
assert MyEnum.A == e;
assert e.name() == "A";
assert e.ordinal() == 0;
assert MyEnum.valueOf("A") == e;
assert array(MyEnum.values()).indexOf(MyEnum.valueOf("C")) == 2;
```
Like Java enums, additional methods, constructors and fields can be
added to enums.
``` java
enum ScreenRatio {
FREE_RATIO(null),
RATIO_4_3(4f / 3),
RATIO_3_2(1.5f),
RATIO_16_9(16f / 9),
RATIO_2_1(2f / 1f),
SQUARE_RATIO(1f);
private final Float value;
private MyComplexEnum(Float value) {
this.value = value;
}
public Float getValue() {
return value;
}
}
```
#### Enums portability notes
Simple enums are translated to regular TypeScript enums, that is to say
numbers. In JavaScript, at runtime, an enum instance is simple encode as
its ordinal. So, JSweet enums are easy to share with TypeScript enums
and a JSweet program can interoperate with a TypeScript program even
when using enums.
Enums with additional members are also mapped to TypeScript enums, but
an additional class is generated to store the additional information.
When interoperating with TypeScript, the ordinal will remain, but the
additional information will be lost. The programmers wanting to share
enums with TypeScript should be aware of that behavior.
### Globals
In Java, on contrary to JavaScript, there is no such thing as global
variables or functions (there are only static members, but even those
must belong to a class). Thus, JSweet introduces reserved `Globals`
classes and `globals` packages. These have two purposes:
- Generate code that has global variables and functions (this is
discouraged in Java)
- Bind to existing JavaScript code that defines global variables and
functions (as many JavaScript frameworks do)
In Globals classes, only static fields (global variables) and static
methods (global functions) are allowed. Here are the main constraints
applying to Globals classes:
- no non-static members
- no super class
- cannot be extended
- cannot be used as types like regular classes
- no public constructor (empty private constructor is OK)
- cannot use $get, $set and $delete within the methods
For instance, the following code snippets will raise transpilation
errors.
``` java
class Globals {
public int a;
// error: public constructors are not allowed
public Globals() {
this.a = 3;
}
public static void test() {
// error: no instance is available
$delete("key");
}
}
```
``` java
// error: Globals classes cannot be used as types
Globals myVariable = null;
```
One must remember that `Globals` classes and `global` packages are
erased at runtime so that their members will be directly accessible. For
instance `mypackage.Globals.m()` in a JSweet program corresponds to the
`mypackage.m()` function in the generated code and in the JavaScript VM
at runtime. Also, `mypackage.globals.Globals.m()` corresponds to *m()*.
In order to erase packages in the generated code, programmers can also
use the `@Root` annotation, which will be explained in Section
<a href="#packaging" data-reference-type="ref" data-reference="packaging">5</a>.
### Optional parameters and overloading
In JavaScript, parameters can be optional, in the sense that a parameter
value does not need to be provided when calling a function. Except for
varargs, which are fully supported in JSweet, the general concept of an
optional parameter does not exist in Java. To simulate optional
parameters, JSweet programmers can use method overloading, which is
supported in Java. Here are some examples of supported overloads in
JSweet:
``` java
String m(String s, double n) { return s + n; }
// simple overloading (JSweet transpiles to optional parameter)
String m(String s) { return m(s, 0); }
// complex overloading (JSweet generates more complex code to mimic the Java behavior)
String m(String s) { return s; }
```
Bridging to external JavaScript elements
----------------------------------------
It can be the case that programmers need to use existing libraries from
JSweet. In most cases, one should look up in the available candies,
a.k.a. bridges at <http://www.jsweet.org/jsweet-candies/>. When the
candy does not exist, or does not entirely cover what is needed, one can
create new definitions in the program just by placing them in the
`def.libname` package. Definitions only specify the types of external
libraries, but no implementations. Definitions are similar to
TypeScripts `*.d.ts` definition files (actually JSweet generates
intermediate TypeScript definition files for compilation purposes).
Definitions can also be seen as similar to `*.h` C/C++ header files.
### Examples
The following example shows the backbone store class made accessible to
the JSweet programmer with a simple definition. This class is only for
typing and will be generated as a TypeScript definition, and erased
during the JavaScript generation.
``` java
package def.backbone;
class Store {
public Store(String dbName) {}
}
```
Note that definition classes constructors must have an empty body. Also,
definition classes methods must be `native`. For instance:
``` java
package def.mylib;
class MyExternalJavaScriptClass {
public native myExternalJavaScriptMethod();
}
```
It is possible to define properties in definitions, however, these
properties cannot be initialized.
### Rules for writing definitions (a.k.a. bridges)
By convention, putting the classes in a `def.libname` package defines a
set of definitions for the `libname` external JavaScript library called
`libname`. Note that this mechanism is similar to the TypeScript `d.ts`
definition files.
Candies (bridges to external JavaScript libraries) use definitions. For
instance, the jQuery candy defines all the jQuery API in the
`def.jquery` package.
Here is a list of rules and constraints that need to be followed when
writing definitions.
- Interfaces are preferred over classes, because interfaces can be
merged and classes can be instantiated. Classes should be used only
if the API defines an explicit constructor (objects can be created
with `new`). To define an interface in JSweet, just annotate a class
with `@jsweet.lang.Interface`.
- Top-level functions and variables must be defined as `public static`
members in a `Globals` class.
- All classes, interfaces and packages, should be documented with
comments following the Javadoc standard.
- When several types are possible for a function parameter, method
overloading should be preferred over using union types. When method
overloading is not possible, it can be more convenient to simply use
the `Object` type. It is less strongly typed but it is easier to
use.
- One can use string types to provide function overloading depending
on a string parameter value.
- In a method signature, optional parameters can be defined with the
`@jsweet.lang.Optional` annotation.
- In an interface, optional fields can be defined with the
`@jsweet.lang.Optional` annotation.
Definitions can be embedded directly in a JSweet project to access an
external library in a typed way.
Definitions can also be packaged in a candy (a Maven artifact), so that
they can be shared by other projects. See the *Packaging* section for
full details on how to create a candy. Note that you do not need to
write definitions when a library is written with JSweet because the Java
API is directly accessible and the TypeScript definitions can be
automatically generated by JSweet using the `declaration` option.
### Untyped accesses
Sometimes, definitions are not available or are not correct, and just a
small patch is required to access a functionality. Programmers have to
keep in mind that JSweet is just a syntactic layer and that it is always
possible to bypass typing in order to access a field or a function that
is not explicitly specified in an API.
Although, having a well-typed API is the preferred and advised way, when
such an API is not available, the use of `def.js.Object.$get` allows
reflective access to methods and properties that can then be cast to the
right type. For accessing functions in an untyped way, one can cast to
`def.js.Function` and call the generic and untyped method `$apply` on
it. For example, here is how to invoke the jQuery `$` method when the
jQuery API is not available :
``` java
import def.dom.Globals.window;
[...]
Function $ = (Function)window.$get("$");
$.$apply("aCssSelector"):
```
The `$get` function is available on instances of `def.js.Object` (or
subclasses). For a `def.js.Object`, you can cast it using the
`jsweet.util.Lang.object` helper method. For example:
``` java
import static jsweet.dom.Lang.object;
[...]
object(anyObject).$get("$");
```
In last resort, the `jsweet.util.Lang.$insert` helper method allows the
user to insert any TypeScript expression within the program. Invalid
expressions will raise a TypeScript compilation error, but it is however
not recommended to use this technique.
``` java
import static jsweet.dom.Lang.$get;
import static jsweet.dom.Lang.$apply;
[...]
// generate anyObject["prop"]("param");
$apply($get(anyObject, "prop"), "param");
```
Finally, note also the use of the `jsweet.util.Lang.any` helper method,
which can be extremely useful to erase typing. Since the `any` method
generates a cast to the `any` type in TypeScript, it is more radical
than a cast to `Object` for instance. The following example shows how to
use the `any` method to cast an `Int32Array` to a Java `int[]` (and then
allow direct indexed accesses to it.
``` java
ArrayBuffer arb = new ArrayBuffer(2 * 2 * 4);
int[] array = any(new Int32Array(arb));
int whatever = array[0];
```
### Mixins
In JavaScript, it is common practice to enhance an existing class with
news elements (field and methods). It is an extension mechanism used
when a framework defines plugins for instance. Typically, jQuery plugins
add new elements to the `JQuery` class. For example the jQuery timer
plugin adds a `timer` field to the `JQuery` class. As a consequence, the
`JQuery` class does not have the same prototype if you are using jQuery
alone, or jQuery enhanced with its timer plugin.
In Java, this extension mechanism is problematic because the Java
language does not support mixins or any extension of that kind by
default.
#### Untyped accesses to mixins
Programmers can access the added element with `$get` accessors and/or
with brute-force casting.
Here is an example using `$get` for the timer plugin case:
``` java
((Timer)$("#myId").$get("timer")).pause();
```
Here is an other way to do it exampled through the use of the jQuery UI
plugin (note that this solution forces the use of `def.jqueryui.JQuery`
instead of `def.jquery.JQuery` in order to access the `menu()` function,
added by the UI plugin):
``` java
import def.jqueryui.JQuery;
[...]
Object obj = $("#myMenu");
JQuery jq = (JQuery) obj;
jq.menu();
```
However, these solutions are not fully satisfying because clearly unsafe
in terms of typing.
#### Typed accesses with mixins
When cross-candy dynamic extension is needed, JSweet defines the notion
of a mixin. A mixin is a class that defines members that will end up
being directly accessible within a target class (mixin-ed class). Mixins
are defined with a `@Mixin` annotation. Here is the excerpt of the
`def.jqueryui.JQuery` mixin:
``` java
package def.jqueryui;
import def.dom.MouseEvent;
import def.js.Function;
import def.js.Date;
import def.js.Array;
import def.js.RegExp;
import def.dom.Element;
import def.jquery.JQueryEventObject;
@jsweet.lang.Interface
@jsweet.lang.Mixin(target=def.jquery.JQuery.class)
public abstract class JQuery extends def.jquery.JQuery {
native public JQuery accordion();
native public void accordion(jsweet.util.StringTypes.destroy methodName);
native public void accordion(jsweet.util.StringTypes.disable methodName);
native public void accordion(jsweet.util.StringTypes.enable methodName);
native public void accordion(jsweet.util.StringTypes.refresh methodName);
...
native public def.jqueryui.JQuery menu();
...
```
One can notice the `@jsweet.lang.Mixin(target=def.jquery.JQuery.class)`
that states that this mixin will be merged to the `def.jquery.JQuery` so
that users will be able to use all the UI plugin members directly and in
a well-typed way.
#### How to use
TBD.
### Generating JSweet candies from existing TypeScript definitions
TBD.
Auxiliary types
---------------
JSweet uses most Java typing features (including functional types) but
also extends the Java type system with so-called *auxiliary types*. The
idea behind auxiliary types is to create classes or interfaces that can
hold the typing information through the use of type parameters (a.k.a
*generics*), so that the JSweet transpiler can cover more typing
scenarios. These types have been mapped from TypeScript type system,
which is much richer than the Java one (mostly because JavaScript is a
dynamic language and requires more typing scenarios than Java).
### Functional types
For functional types, JSweet reuses the `java.Runnable` and
`java.util.function` functional interfaces of Java 8. These interfaces
are generic but only support up to 2-parameter functions. Thus, JSweet
adds some support for more parameters in `jsweet.util.function`, since
it is a common case in JavaScript APIs.
Here is an example using the `Function` generic functional type:
``` java
import java.util.function.Function;
public class C {
String test(Function<String, String> f) {
f.apply("a");
}
public static void main(String[] args) {
String s = new C().test(p -> p);
assert s == "a";
}
}
```
We encourage programmers to use the generic functional interfaces
defined in the `jsweet.util.function` and `java.util.function` (besides
`java.lang.Runnable`). When requiring functions with more parameters,
programmers can define their own generic functional types in
`jsweet.util.function` by following the same template as the existing
ones.
In some cases, programmers will prefer defining their own specific
functional interfaces. This is supported by JSweet. For example:
``` java
@FunctionalInterface
interface MyFunction {
void run(int i, String s);
}
public class C {
void m(MyFunction f) {
f.run(1, "test");
}
public static void main(String[] args) {
new C().m((i, s) -> {
// do something with i and s
});
}
}
```
Important warning: it is to be noted here that, on contrary to Java, the
use of the `@FunctionInterface` annotation is mandatory.
Note also the possible use of the `apply` function, which is by
convention always a functional definition on the target object (unless
if `apply` is annotated with the `@Name` annotation). Defining/invoking
`apply` can done on any class/object (because in JavaScript any object
can become a functional object).
### Object types
Object types are similar to interfaces: they define a set of fields and
methods that are applicable to an object (but remember that it is a
compile-time contract). In TypeScript, object types are inlined and
anonymous. For instance, in TypeScript, the following method `m` takes a
parameter, which is an object containing an `index` field:
``` java
// TypeScript:
public class C {
public m(param : { index : number }) { ... }
}
```
Object types are a convenient way to write shorter code. One can pass an
object that is correctly typed by constructing an object on the fly:
``` java
// TypeScript:
var c : C = ...;
c.m({ index : 2 });
```
Obviously, object types are a way to make the typing of JavaScript
programs very easy to programmers, which is one of the main goals of
TypeScript. It makes the typing concise, intuitive and straightforward
to JavaScript programmers. In Java/JSweet, no similar inlined types
exist and Java programmers are used to defining classes or interfaces
for such cases. So, in JSweet, programmers have to define auxiliary
classes annotated with `@ObjectType` for object types. This may seem
more complicated, but it has the advantage to force the programmers to
name all the types, which, in the end, can lead to more readable and
maintenable code depending on the context. Note that similarily to
interfaces, object types are erased at runtime. Also `@ObjectType`
annotated classes can be inner classes so that they are used locally.
Here is the JSweet version of the previous TypeScript program.
``` java
public class C {
@ObjectType
public static class Indexed {
int index;
}
public void m(Indexed param) { ... }
}
```
Using an object type is similar to using an interface:
``` java
C c = ...;
c.m(new Indexed() {{ index = 2; }});
```
When object types are shared objects and represent a typing entity that
can be used in several contexts, it is recommended to use the
`@Interface` annotation instead of `@ObjectType`. Here is the
interface-based version.
``` java
@Interface
public class Indexed {
int index;
}
public class C {
public m(Indexed param) { ... }
}
C c = ...;
c.m(new Indexed {{ index = 2; }});
```
### String types
In TypeScript, string types are a way to simulate function overloading
depending on the value of a string parameter. For instance, here is a
simplified excerpt of the DOM TypeScript definition file:
``` java
// TypeScript:
interface Document {
[...]
getElementsByTagName(tagname: "a"): NodeListOf<HTMLAnchorElement>;
getElementsByTagName(tagname: "b"): NodeListOf<HTMLPhraseElement>;
getElementsByTagName(tagname: "body"): NodeListOf<HTMLBodyElement>;
getElementsByTagName(tagname: "button"): NodeListOf<HTMLButtonElement>;
[...]
}
```
In this code, the `getElementsByTagName` functions are all overloads
that depend on the strings passed to the `tagname` parameter. Not only
string types allow function overloading (which is in general not allowed
in TypeScript/JavaScript), but they also constrain the string values
(similarly to an enumeration), so that the compiler can automatically
detect typos in string values and raise errors.
This feature being useful for code quality, JSweet provides a mechanism
to simulate string types with the same level of type safety. A string
type is a public static field annotated with `@StringType`. It must be
typed with an interface of the same name declared in the same container
type.
For JSweet translated libraries (candies), all string types are declared
in a the `jsweet.util.StringTypes` class, so that it is easy for the
programmers to find them. For instance, if a `"body"` string type needs
to be defined, a Java interface called `body` and a static final field
called `body` are defined in a `jsweet.util.StringTypes`.
Note that each candy may have its own string types defined in the
`jsweet.util.StringTypes` class. The JSweet transpiler merges all these
classes at the bytecode level so that all the string types of all
candies are available in the same `jsweet.util.StringTypes` utility
class. As a result, the JSweet DOM API will look like:
``` java
@Interface
public class Document {
[...]
public native NodeListOf<HTMLAnchorElement> getElementsByTagName(a tagname);
public native NodeListOf<HTMLPhraseElement> getElementsByTagName(b tagname);
public native NodeListOf<HTMLBodyElement> getElementsByTagName(body tagname);
public native NodeListOf<HTMLButtonElement> getElementsByTagName(button tagname);
[...]
}
```
In this API, `a`, `b`, `body` and `button` are interfaces defined in the
`jsweet.util.StringTypes` class. When using one the method of
`Document`, the programmer just need to use the corresponding type
instance (of the same name). For instance:
``` java
Document doc = ...;
NodeListOf<HTMLAnchorElement> elts = doc.getElementsByTagName(StringTypes.a);
```
Note: if the string value is not a valid Java identifier (for instance
`"2d"` or `"string-with-dashes"`), it is then translated to a valid one
and annotated with `@Name("originalName")`, so that the JSweet
transpiler knows what actual string value must be used in the generated
code. For instance, by default, `"2d"` and `"string-with-dashes"` will
correspond to the interfaces `StringTypes._2d` and
`StringTypes.string_with_dashes` with `@Name` annotations.
Programmers can define string types for their own needs, as shown below:
``` java
import jsweet.lang.Erased;
import jsweet.lang.StringType;
public class CustomStringTypes {
@Erased
public interface abc {}
@StringType
public static final abc abc = null;
// This method takes a string type parameter
void m2(abc arg) {
}
public static void main(String[] args) {
new CustomStringTypes().m2(abc);
}
}
```
Note the use of the `@Erased` annotation, which allows the declaration
of the `abc` inner interface. This interface is used to type the string
type field `abc`. In general, we advise the programmer to group all the
string types of a program in the same utility class so that it is easy
to find them.
### Tuple types
Tuple types represent JavaScript arrays with individually tracked
element types. For tuple types, JSweet defines parameterized auxiliary
classes `TupleN<T0, ... TN-1>`, which define `$0`, `$1`, ... `$N-1`
public fields to simulate typed array accessed (field `$i` is typed with
`Ti`).
For instance, given the following tuple of size 2:
``` java
Tuple2<String, Integer> tuple = new Tuple2<String, Integer>("test", 10);
```
We can expect the following (well-typed) behavior:
``` java
assert tuple.$0 == "test";
assert tuple.$1 == 10;
tuple.$0 = "ok";
tuple.$1--;
assert tuple.$0 == "ok";
assert tuple.$1 == 9;
```
Tuple types are all defined (and must be defined) in the
`jsweet.util.tuple` package. By default classes `Tuple[2..6]` are
defined. Other tuples (&gt;6) are automatically generated when
encountered in the candy APIs. Of course, when requiring larger tuples
that cannot be found in the `jsweet.util.tuple` package, programmers can
add their own tuples in that package depending on their needs, just by
following the same template as existing tuples.
### Union types
Union types represent values that may have one of several distinct
representations. When such a case happens within a method signature (for
instance a method allowing several types for a given parameter), JSweet
takes advantage of the *method overloading* mechanism available in Java.
For instance, the following `m` method accept a parameter `p`, which can
be either a `String` or a `Integer`.
``` java
public void m(String p) {...}
public void m(Integer p) {...}
```
In the previous case, the use of explicit union types is not required.
For more general cases, JSweet defines an auxiliary interface
`Union<T1, T2>` (and `UnionN<T1, ... TN>`) in the `jsweet.util.union`
package. By using this auxiliary type and a `union` utility method,
programmers can cast back and forth between union types and union-ed
type, so that JSweet can ensure similar properties as TypeScript union
types.
The following code shows a typical use of union types in JSweet. It
simply declares a variable as a union between a string and a number,
which means that the variable can actually be of one of that types (but
of no other types). The switch from a union type to a regular type is
done through the `jsweet.util.Lang.union` helper method. This helper
method is completely untyped, allowing from a Java perspective any union
to be transformed to another type. It is actually the JSweet transpiler
that checks that the union type is consistently used.
``` java
import static jsweet.util.Lang.union;
import jsweet.util.union.Union;
[...]
Union<String, Number> u = ...;
// u can be used as a String
String s = union(u);
// or a number
Number n = union(u);
// but nothing else
Date d = union(u); // JSweet error
```
The `union` helper can also be used the other way, to switch from a
regular type back to a union type, when expected.
``` java
import static jsweet.util.Lang.union;
import jsweet.util.union.Union3;
[...]
public void m(Union3<String, Number, Date>> u) { ... }
[...]
// u can be a String, a Number or a Date
m(union("a string"));
// but nothing else
m(union(new RegExp(".*"))); // compile error
```
Note: the use of Java function overloading is preferred over union types
when typing function parameters. For example:
``` java
// with union types (discouraged)
native public void m(Union3<String, Number, Date>> u);
// with overloading (preferred way)
native public void m(String s);
native public void m(Number n);
native public void m(Date d);
```
### Intersection types
TypeScript defines the notion of type intersection. When types are
intersected, it means that the resulting type is a larger type, which is
the sum of all the intersected types. For instance, in TypeScript,
`A & B` corresponds to a type that defines both `A` and `B` members.
Intersection types in Java cannot be implemented easily for many
reasons. So, the practical choice being made here is to use union types
in place of intersection types. In JSweet, `A & B` is thus defined as
`Union<A, B>`, which means that the programmer can access both `A` and
`B` members by using the `jsweet.util.Lang.union` helper method. It is
of course less convenient than the TypeScript version, but it is still
type safe.
Semantics
---------
Semantics designate how a given program behaves when executed. Although
JSweet relies on the Java syntax, programs are transpiled to JavaScript
and do not run in a JRE. As a consequence, the JavaScript semantics will
impact the final semantics of a JSweet program compared to a Java
program. In this section, we discuss the semantics by focusing on
differences or commonalities between Java/JavaSript and JSweet.
### Main methods
Main methods are the program execution entry points and will be invoked
globally when a class containing a `main` method is evaluated. For
instance:
``` java
public class C {
private int n;
public static C instance;
public static void main(String[] args) {
instance = new C();
instance.n = 4;
}
public int getN() {
return n;
}
}
// when the source file containing C has been evaluated:
assert C.instance != null;
assert C.instance.getN() == 4;
```
The way main methods are globally invoked depends on how the program is
packaged. See the appendixes for more details.
### Initializers
Initializers behave like in Java.
For example:
``` java
public class C1 {
int n;
{
n = 4;
}
}
assert new C1().n == 4;
```
And similarly with static initializers:
``` java
public class C2 {
static int n;
static {
n = 4;
}
}
assert C2.n == 4;
```
While regular initializers are evaluated when the class is instantiated,
static initializers are lazily evaluated in order to avoid
forward-dependency issues, and mimic the Java behavior for initializers.
With JSweet, it is possible for a programmer to define a static field or
a static intializer that relies on a static field that has not yet been
initialized.
More details on this behavior can be found in the appendixes.
### Arrays initialization and allocation
Arrays can be used like in Java.
``` java
String[] strings = { "a", "b", "c" };
assert strings[1] == "b";
```
When specifying dimensions, arrays are pre-allocated (like in Java), so
that they are initialized with the right length, and with the right
sub-arrays in case of multiple-dimensions arrays.
``` java
String[][] strings = new String[2][2];
assert strings.length == 2;
assert strings[0].length == 2;
strings[0][0] = "a";
assert strings[0][0] == "a";
```
The JavaScript API can be used on an array by casting to a
`def.js.Array` with `jsweet.util.Lang.array`.
``` java
import static jsweet.util.Lang.array;
[...]
String[] strings = { "a", "b", "c" };
assert strings.length == 3;
array(strings).push("d");
assert strings.length == 4;
assert strings[3] == "d";
```
In some cases it is preferable to use the `def.js.Array` class directly.
``` java
Array<String> strings = new Array<String>("a", "b", "c");
// same as: Array<String> strings = array(new String[] { "a", "b", "c" });
// same as: Array<String> strings = new Array<String>(); strings.push("a", "b", "c");
assert strings.length == 3;
strings.push("d");
assert strings.length == 4;
assert strings.$get(3) == "d";
```
### Asynchronous programming
JSweet supports advanced asynchronous programming beyond the basic
callback concepts with the help of the ES2015+ Promise API.
#### Promises
It is very simple to define an asynchronous method by declaring a
`Promise` return type. The following methods `Promise` will be
*fulfilled* when millis milliseconds elapsed.
``` java
Promise<Void> delay(int millis) {
return new Promise<Void>((Consumer<Void> resolve, Consumer<Object> reject) -> {
setTimeout(resolve, millis);
});
}
```
You can then chain synchronous and asynchronous actions to be executed
once the promise is fulfilled.
``` java
delay(1000)
// chain with a synchronous action with "then". Here we just return a constant.
.then(() -> {
System.out.println("wait complete");
return 42;
})
// chain with an asynchronous action with "thenAsync". Here it is implied that anotherAsyncAction(String) returns a Promise<...>
.thenAsync((Integer result) -> {
System.out.println("previous task result: " + result); // will print "previous task result: 42"
return anotherAsyncAction("param");
})
// this chained action will be executed once anotherAsyncAction finishes its execution.
.then((String result) -> {
System.out.println("anotherAsyncAction returned " + result);
})
// catch errors during process using this method
.Catch(error -> {
System.out.println("error is " + error);
});
```
This allows a totally type-safe and fluent asynchronous programming
model.
#### async/await
`Promise`s are really interesting to avoid callback but writing it still
requires a lot of boilerplate code. It is better than pure callbacks but
less readable and straightforward than linear programming. Thats where
`async/await` comes to help.
With the `await` keyword, you can tell the runtime to wait for a
`Promise` to be fulfilled without having to write a then method. The
code after the `await` "is" the `then` part. The result is that you can
write your asynchronous code with linear programming.
``` java
import static jsweet.util.Lang.await;
// wait for the Promise returned by the delay method to be fulfilled
await(delay(1000));
System.out.println("wait complete");
```
It goes the same for error handling. You can just use the plain old
`try / catch` idiom to handle your exceptions.
``` java
import static jsweet.util.Lang.await;
import def.js.Error;
try {
Integer promiseResult = await(getANumber());
assert promiseResult == 42;
} catch(Error e) {
System.err.println("something unexpected happened: " + e);
}
```
You have to declare as `async` every asynchronous method / lambda (i.e.
every method which would await something).
``` java
import static jsweet.util.Lang.await;
import static jsweet.util.Lang.async;
import static jsweet.util.Lang.function;
import jsweet.lang.Async;
import def.js.Function;
@Async
Promise<Integer> findAnswer() {
await(delay(1000)); // won't compile if the enclosing method isn't @Async
return asyncReturn(42); // converts to Promise
}
@Async
void askAnswerThenVerifyAndPrintIt() {
try {
Integer answer = await(findAnswer());
// lambda expressions can be async
Function verifyAnswerAsync = async(function(() -> {
return await(answerService.verifyAnswer(answer));
}))
Boolean verified = await(verifyAnswerAsync.$apply());
if (!verified) {
throw new Error("cannot verify this answer");
}
console.log("answer found: " + answer);
} catch (Error e) {
console.error(e, "asynchronous process failed");
}
}
```
Sweet, isnt it? ;)
### Name clashes
On contrary to TypeScript/JavaScript, Java makes a fundamental
difference between methods, fields, and packages. Java also support
method overloading (methods having different signatures with the same
name). In JavaScript, object variables and functions are stored within
the same object map, which basically means that you cannot have the same
key for several object members (this also explains that method
overloading in the Java sense is not possible in JavaScript). Because of
this, some Java code may contain name clashes when generated as is in
TypeScript. JSweet will avoid name clashes automatically when possible,
and will report sound errors in the other cases.
#### Methods and fields names clashes
JSweet performs a transformation to automatically allow methods and
private fields to have the same name. On the other hand, methods and
public fields of the same name are not allowed within the same class or
within classes having a subclassing link.
To avoid programming mistakes due to this JavaScript behavior, JSweet
adds a semantics check to detect duplicate names in classes (this also
takes into account members defined in parent classes). As an example:
``` java
public class NameClashes {
// error: field name clashes with existing method name
public String a;
// error: method name clashes with existing field name
public void a() {
return a;
}
}
```
#### Method overloading
On contrary to TypeScript and JavaScript (but similarly to Java), it is
possible in JSweet to have several methods with the same name but with
different parameters (so-called overloads). We make a distinction
between simple overloads and complex overloads. Simple overloading is
the use of method overloading for defining optional parameters. JSweet
allows this idiom under the condition that it corresponds to the
following template:
``` java
String m(String s, double n) { return s + n; }
// valid overloading (JSweet transpiles to optional parameter)
String m(String s) { return m(s, 0); }
```
In that case, JSweet will generate JavaScript code with only one method
having default values for the optional parameters, so that the behavior
of the generated program corresponds to the original one. In this case:
``` java
function m(s, n = 0) { return s + n; }
```
If the programmer tries to use overloading differently, for example by
defining two different implementations for the same method name, JSweet
will fallback on a complex overload, which consists of generating a root
implementation (the method that hold the more parameters) and one
subsidiary implementation per overloading method (named with a suffix
representing the method signature). The root implementation is generic
and dispatches to other implementations by testing the values and types
of the given parameters. For example:
``` java
String m(String s, double n) { return s + n; }
String m(String s) { return s; }
```
Generates the following (slightly simplified) JavaScript code:
``` java
function m(s, n) {
if(typeof s === 'string' && typeof n === 'number') {
return s + n;
} else if(typeof s === 'string' && n === undefined) {
return this.m$java_lang_String(s);
} else {
throw new Error("invalid overload");
}
}
function m$java_lang_String(s) { return s; }
```
#### Local variable names
In TypeScript/JavaScript, local variables can clash with the use of a
global method. For instance, using the `alert` global method from the
DOM (`jsweet.dom.Globals.alert`) requires that no local variable hides
it:
``` java
import static jsweet.dom.Globals.alert;
[...]
public void m1(boolean alert) {
// JSweet compile error: name clash between parameter and method call
alert("test");
}
public void m2() {
// JSweet compile error: name clash between local variable and method call
String alert = "test";
alert(alert);
}
```
Note that this problem also happens when using fully qualified names
when calling the global methods (that is because the qualification gets
erased in TypeScript/JavaScript). In any case, JSweet will report sound
errors when such problems happen so that programmers can adjust local
variable names to avoid clashes with globals.
### Testing the type of an object
To test the type of a given object at runtime, one can use the
`instanceof` Java operator, but also the `Object.getClass()` function.
#### `instanceof`
The `instanceof` is the advised and preferred way to test types at
runtime. JSweet will transpile to a regular `instanceof` or to a
`typeof` operator depending on the tested type (it will fallback on
`typeof` for `number`, `string`, and `boolean` core types).
Although not necessary, it is also possible to directly use the `typeof`
operator from JSweet with the `jsweet.util.Lang.typeof` utility method.
Here are some examples of valid type tests:
``` java
import static jsweet.util.Lang.typeof;
import static jsweet.util.Lang.equalsStrict;
[...]
Number n1 = 2;
Object n2 = 2;
int n3 = 2;
Object s = "test";
MyClass c = new MyClass();
assert n1 instanceof Number; // transpiles to a typeof
assert n2 instanceof Number; // transpiles to a typeof
assert n2 instanceof Integer; // transpiles to a typeof
assert !(n2 instanceof String); // transpiles to a typeof
assert s instanceof String; // transpiles to a typeof
assert !(s instanceof Integer); // transpiles to a typeof
assert c instanceof MyClass;
assert typeof(n3) == "number";
```
From JSweet version 1.1.0, the `instanceof` operator is also allowed on
interfaces, because JSweet keeps track of all the implemented interfaces
for all objects. This interface tracking is ensured through an
additional hidden property in the objects called `__interfaces` and
containing the names of all the interfaces implemented by the objects
(either directly or through its class inheritance tree determined at
compile time). So, in case the type argument of the `instanceof`
operator is an interface, JSweet simply checks out if the objects
`__interfaces` field exists and contains the given interface. For
example, this code is fully valid in JSweet when `Point` is an
interface:
``` java
Point p1 = new Point() {{ x=1; y=1; }};
[...]
assert p1 instanceof Point
```
#### `Object.getClass()` and `X.class`
In JSweet, using the `Object.getClass()` on any instance is possible. It
will actually return the constructor function of the class. Using
`X.class` will also return the constructor if `X` is a class. So the
following assertion will hold in JSweet:
``` java
String s = "abc";
assert String.class == s.getClass()
```
On a class, you can call the `getSimpleName()` or `getName()` functions.
``` java
String s = "abc";
assert "String" == s.getClass().getSimpleName()
assert String.class.getSimpleName() == s.getClass().getSimpleName()
```
Note that `getSimpleName()` or `getName()` functions will also work on
an interface. However, you have to be aware that `X.class` will be
encoded in a string (holding the interfaces name) if `X` is is an
interface.
#### Limitations and constraints
Since all numbers are mapped to JavaScript numbers, JSweet make no
distinction between integers and floats for example. So,
`n instanceof Integer` and `n instanceof Float` will always give the
same result whatever the actual type of `n` is. The same limitation
exists for strings and chars, which are not distinguishable at runtime,
but also for functions that have the same number of parameters. For
example, an instance of `IntFunction<R>` will not be distinguishable at
runtime from a `Function<String,R>`.
These limitations have a direct impact on function overloading, since
overloading uses the `instanceof` operator to decide which overload to
be called.
Like it is usually the case when working in JavaScript, serialized
objects must be properly "revived" with their actual classes so that the
`instanceof` operator can work again. For example a point object created
through `Point p = (Point)JSON.parse("{x:1,y:1}")` will not work with
regard to the `instanceof` operator. In case you meet such a use case,
you can contact us to get some useful JSweet code to properly revive
object types.
### Variable scoping in lambda expressions
JavaScript variable scoping is known to pose some problems to the
programmers, because it is possible to change the reference to a
variable from outside of a lambda that would use this variable. As a
consequence, a JavaScript programmer cannot rely on a variable declared
outside of a lambda scope, because when the lambda is executed, the
variable may have been modified somewhere else in the program. For
instance, the following program shows a typical case:
``` java
NodeList nodes = document.querySelectorAll(".control");
for (int i = 0; i < nodes.length; i++) {
HTMLElement element = (HTMLElement) nodes.$get(i); // final
element.addEventListener("keyup", (evt) -> {
// this element variable will not change here
element.classList.add("hit");
});
}
```
In JavaScript (note that EcmaScript 6 fixes this issue), such a program
would fail its purpose because the `element` variable used in the event
listener is modified by the for loop and does not hold the expected
value. In JSweet, such problems are dealt with similarly to final Java
variables. In our example, the `element` variable is re-scoped in the
lambda expression so that the enclosing loop does not change its value
and so that the program behaves like in Java (as expected by most
programmers).
### Scope of *this*
On contrary to JavaScript and similarly to Java, using a method as a
lambda will prevent loosing the reference to `this`. For instance, in
the `action` method of the following program, `this` holds the right
value, even when `action` was called as a lambda in the `main` method.
Although this seem logical to Java programmers, it is not a given that
the JavaScript semantics ensures this behavior.
``` java
package example;
import static jsweet.dom.Globals.console;
public class Example {
private int i = 8;
public Runnable getAction() {
return this::action;
}
public void action() {
console.log(this.i); // this.i is 8
}
public static void main(String[] args) {
Example instance = new Example();
instance.getAction().run();
}
}
```
It is important to stress that the `this` correct value is ensured
thanks to a similar mechanism as the ES5 `bind` function. A consequence
is that function references are wrapped in functions, which means that
function pointers (such as `this::action`) create wrapping functions on
the fly. It has side effects when manipulating function pointers, which
are well described in this issue
<https://github.com/cincheo/jsweet/issues/65>.
Packaging
---------
Packaging is one of the complex point of JavaScript, especially when
coming from Java. Complexity with JavaScript packaging boils down to the
fact that JavaScript did not define any packaging natively. As a
consequence, many *de facto* solutions and guidelines came up along the
years, making the understanding of packaging uneasy for regular Java
programmers. JSweet provides useful options and generates code in order
to simplify the life of Java programmers by making the packaging issues
much more transparent and as "easy" as in Java for most cases. In this
section, we will describe and explain typical packaging scenarios.
### Use your files without any packaging
The most common and simple case for running a program is just to include
each generated file in an HTML page. This is the default mode when not
precising any packaging options. For example, when your program defines
two classes `x.y.z.A` and `x.y.z.B` in two separated files, you can use
them as following:
```
<script type="text/javascript" src="target/js/x/y/z/A.js"></script>
<script type="text/javascript" src="target/js/x/y/z/B.js"></script>
[...]
<!-- access a method later in the file -->
<script type="text/javascript">x.y.z.B.myMethod()</script>
```
When doing so, programmers need to be extremely cautious to avoid
forward static dependencies between the files. In other words, the `A`
class cannot use anything from `B` in static fields, static
initializers, or static imports, otherwise leading to runtime errors
when trying to load the page. Additionally, the `A` class cannot extend
the `B` class. These constraints come from JavaScript/TypeScript and
have nothing to do with JSweet.
As you can imagine, running simple programs with this manual technique
is fine, but can become really uncomfortable for developing complex
applications. Complex applications most of the time bundle and/or
package the program with appropriate tools in order to avoid having to
manually handle dependencies between JavaScript files.
### Creating a bundle for a browser
To avoid having to take care of the dependencies manually, programmers
use bundling tools to bundle up their classes into a single file. Such a
bundle is included in any web page using something like this:
```
<script type="text/javascript" src="target/js/bundle.js"></script>
[...]
<!-- access a method later in the file -->
<script type="text/javascript">x.y.z.B.myMethod()</script>
```
JSweet comes with such a bundling facility. To create a bundle file,
just set to `true` the `bundle` option of JSweet. Note that you can also
set to `true` the `declaration` option that will ask JSweet to generate
the TypeScript definition file (`bundle.d.ts`). This file allows you to
use/compile your JSweet program from TypeScript in a well-typed way.
The "magic" with JSweet bundling option is that it analyzes the
dependencies in the source code and takes care of solving forward
references when building the bundle. In particular, JSweet implements a
lazy initialization mechanism for static fields and initializers in
order to break down static forward references across the classes. There
are no specific additional declarations to be made by the programmers to
make it work (on contrary to TypeScript).
Note that there are still some minor limitations to it (when using inner
and anonymous classes for instance), but these limitations will be
rarely encountered and will be removed in future releases.
Note also that JSweet will raise an error if you specify the `module`
option along with the `bundle` option.
### Packaging with modules
First, let us start by explaining modules and focus on the difference
between Java *packages* (or TypeScript *namespaces*) and *modules*. If
you feel comfortable with the difference, just skip this section.
Packages and modules are two similar concepts but for different
contexts. Java packages must be understood as compile-time *namespaces*.
They allow a compile-time structuration of the programs through name
paths, with implicit or explicit visibility rules. Packages have usually
not much impact on how the program is actually bundled and deployed.
Modules must be understood as deployment / runtime "bundles", which can
be `required` by other modules. The closest concept to a module in the
Java world would probably be an OSGi bundle. A module defines imported
and exported elements so that they create a strong runtime structure
that can be used for deploying software components independently and
thus avoiding name clashes. For instance, with modules, two different
libraries may define a `util.List` class and be actually running and
used on the same VM with no naming issues (as long as the libraries are
bundled in different modules).
Nowadays, a lot of libraries are packaged and accessible through
modules. The standard way to use modules in a browser is the AMD, but in
Node.js it is the commonjs module system.
#### Modules in JSweet
JSweet supports AMD, commonjs, and UMD module systems for packaging.
JSweet defines a `module` option (value: `amd`, `commonjs` or `umd`).
When specifying this option, JSweet automatically creates a default
module organization following the simple rule: one file = one module.
For example, when packaged with the `module` option set to `commonjs`,
one can write:
```
> node target/js/x/y/z/MyMainClass.js
```
Where `MyMainClass` contains a `main` method.
The module system will automatically take care of the references and
require other modules when needed. Under the hood, JSweet analysis the
Java import statements and transform them to `require` instructions.
Note: once the program has been compiled with the `module` option, it is
easy to package it as a bundle using appropriate tools such as
Browserify, which would give similar output as using the `bundle` option
of JSweet. Note also that JSweet will raise an error when specifying
both `module` and `bundle`, which are exclusive options.
#### External modules
When compiling JSweet programs with the `module` options, all external
libraries and components must be required as external modules. JSweet
can automatically require modules, simply by using the `@Module(name)`
annotation. In JSweet, importing or using a class or a member annotated
with `@Module(name)` will automatically require the corresponding module
at runtime. Please not that it is true only when the code is generated
with the `module` option. If the `module` option is off, the `@Module`
annotations are ignored.
``` java
package def.jquery;
public final class Globals extends def.js.Object {
...
@jsweet.lang.Module("jquery")
native public static def.jquery.JQuery $(java.lang.String selector);
...
}
```
The above code shows an excerpt of the JSweet jQuery API. As we can
notice, the `$` function is annotated with `@Module("jquery")`. As a
consequence, any call to this function will trigger the require of the
`jquery` module.
Note: the notion of manual require of a module may be available in
future releases. However, automatic require is sufficient for most
programmers and hides the complexity of having to require modules
explicitly. It also brings the advantage of having the same code whether
modules are used or not.
Troubleshooting: when a candy does not define properly the `@Module`
annotation, it is possible to force the declaration within the comment
of a special file called `module_defs.java`. For example, to force the
`BABYLON` namespace of the Babylonjs candy to be exported as a
`babylonjs` module, you can write the following file:
``` java
package myprogram;
// declare module "babylonjs" {
// export = BABYLON;
// }
```
Note that a JSweet project can only define one `module_defs.java` file,
which shall contain all the module declarations in a comment. Note also
that it is a hack and the preferred method would be to contribute to the
candy to fix the problem.
### Root packages
Root packages are a way to tune the generated code so that JSweet
packages are erased in the generated code and thus at runtime. To set a
root package, just define a `package-info.java` file and use the `@Root`
annotation on the package, as follows:
``` java
@Root
package a.b.c;
```
The above declaration means that the `c` package is a root package, i.e.
it will be erased in the generated code, as well as all its parent
packages. Thus, if `c` contains a package `d`, and a class `C`, these
will be top-level objects at runtime. In other words, `a.b.c.d` becomes
`d`, and `a.b.c.C` becomes `C`.
Note that since that packaged placed before the `@Root` package are
erased, there cannot be any type defined before a `@Root` package. In
the previous example, the *a* and *b* packages are necessarily empty
packages.
#### Behavior when not using modules (default)
By default, root packages do not change the folder hierarchy of the
generated files. For instance, the `a.b.c.C` class will still be
generated in the `<jsout>/a/b/c/C.js` file (relatively to the `<jsout>`
output directory). However, switching on the `noRootDirectories` option
will remove the root directories so that the `a.b.c.C` class gets
generated to the `<jsout>/C.js` file.
When not using modules (default), it is possible to have several `@Root`
packages (but a `@Root` package can never contain another `@Root`
package).
#### Behavior when using modules
When using modules (see the *module* option), only one `@Root` package
is allowed, and when having one `@Root` package, no other package or
type can be outside of the scope of that `@Root` package. The generated
folder/file hierarchy then starts at the root package so that all the
folders before it are actually erased.
### Packaging a JSweet jar (candy)
A candy is a Maven artifact that contains everything required to easily
access a JavaScript library from a JSweet client program. This library
can be an external JavaScript library, a TypeScript program, or another
JSweet program.
#### Anatomy of a candy
Like any Maven artifact, a candy has a group id, a artifact id (name),
and a version. Besides, a typical candy should contain the following
elements:
1. The compiled Java files (\*.class), so that your client program that
uses the candy can compile.
2. A `META-INF/candy-metadata.json` file that contains the expected
target version of the transpiler (to be adapted to your target
transpiler version).
3. The programs declarations in `d.ts` files, to be placed in the
`src/typings` directory of the jar. Note that these definitions are
not mandatory if you intend to use JSweet for generating TypeScript
source code (`tsOnly` option). In that case, you may delegate the
JavaScript generation to an external `tsc` compiler and access the
TypeScript definitions from another source.
4. Optionally, the JavaScript bundle of the library, which can in turn
be automatically extracted and used by the JSweet client programs.
JSweet expects the JavaScript to be packaged following the Webjars
conventions: <http://www.webjars.org/>. When packaged this way, a
JSweet transpiler using your candy will automatically extract the
bundled JavaScript in a directory given by the `candiesJsOut` option
(default: `js/candies`).
Here is an example of the `META-INF/candy-metadata.json` file:
``` java
{
"transpilerVersion": "2.0.0"
}
```
#### How to create a candy from a JSweet program
A typical use case when building applications with JSweet, is to share a
common library or module between several other JSweet
modules/applications. Note that since a JSweet candy is a regular Maven
artifact, it can also be used by a regular Java program as long as it
does not use any JavaScript APIs.
So, a typical example in a project is to have a *commons* library
containing DTOs and common utility functions, which can be shared
between a Web client written in JSweet (for example using the angular or
knockout libraries) and a mobile client written also in JSweet (for
example using the ionic library). The great news is that this *commons*
library can also be used by the Java server (JEE, Spring, ...) as is,
because the DTOs do not use any JavaScript, and that the compiled Java
code packaged in the candy can run on a Java VM. This this extremely
helpful, because it means that when you develop this project in your
favorite IDE, you will be able to refactor some DTOs and common APIs,
and it will directly impact your Java server code, your Web client code,
and your mobile client code!
We provide a quick start project to help you starting with such a use
case: <https://github.com/cincheo/jsweet-candy-quickstart>
#### How to create a candy for an existing JavaScript or TypeScript library
We provide a quick start project to help you starting with such a use
case: <https://github.com/cincheo/jsweet-candy-js-quickstart>
Extending the transpiler
------------------------
JSweet is an Open Transpiler from Java to TypeScript. It means that it
provides ways for programmers to tune/extend how JSweet generates the
intermediate TypeScript code. Tuning the transpiler is a solution to
avoid repetitive tasks and automatize them.
For instance, say you have a legacy Java code that uses the Java API for
serializing objects (`writeObject`/`readObject`). With JSweet, you can
easily erase these methods from your program, so that the generated
JavaScript code is free from any Java-specific serialization idioms.
As another example, say you have a Java legacy code base that uses a
Java API, which is close to (but not exactly) a JavaScript API you want
to use in your final JavaScript code. With JSweet, you can write an
adapter that will automatically map all the Java calls to the
corresponding JavaScript calls.
Last but not least, you can tune JSweet to take advantage of some
specific APIs depending on the context. For instance, you may use ES6
maps if you know that your targeted browser supports them, or just use
an emulation or a simpler implementation in other cases. You may adapt
the code to avoid using some canvas or WebGL primitives when knowing
they are not well supported for a given mobile browser. An so on...
Using an Open Transpiler such as JSweet has many practical applications,
which you may or may not have to use, but in any case it is good to be
aware of what is possible.
Tuning can be done declaratively (as opposed to programmatically) using
annotations. Annotations can be added to the Java program (hard-coded),
or they can be centralized in a unique configuration file so that they
dont even appear in the Java code (we call them soft annotations).
Using annotations is quite simple and intuitive. However, when complex
customization of the transpiler is required, it is most likely that
annotations are not sufficient anymore. In that case, programmers shall
use the JSweet extension API, which entails all the tuning that can be
done with annotations, and much more. The extension API gives access to
a Java AST (Abstract Syntax Tree) within the context of so-called
*printer adapters*. Printer adapters follow a decorator pattern so that
they can be chained to extend and/or override the way JSweet will print
out the intermediate TypeScript code.
### Core annotations
The package `jsweet.lang` defines various annotations that can be used
to tune the way JSweet generates the intermediate TypeScript code. Here
we explain these annotations and give examples on how to use them.
- `@Erased`: This annotation type is used on elements that should be
erased at generation time. It can be applied to any program element.
If applied to a type, casts and constructor invocations will
automatically be removed, potentially leading to program
inconsistencies. If applied to a method, invocations will be
removed, potentially leading to program inconsistency, especially
when the invocations result is used in an expression. Because of
potential inconsistencies, programmers should use this annotation
carefully, and applied to unused elements (also erasing using
elements).
- `@Root`: This package annotation is used to specify a root package
for the transpiled TypeScript/JavaScript, which means that all
transpiled references in this package and subpackages will be
relative to this root package. As an example, given the
`org.mycompany.mylibrary` root package (annotated with `@Root`), the
class `org.mycompany.mylibrary.MyClass` will actually correspond to
`MyClass` in the JavaScript runtime. Similarly, the
`org.mycompany.mylibrary.mypackage.MyClass` will transpile to
`mypackage.MyClass`.
- `@Name(String value)`: This annotation allows the definition of a
name that will be used for the final generated code (rather than the
Java name). It can be used when the name of an element is not a
valid Java identifier. By convention, JSweet implements a built-in
convention to save the use of @Name annotations: `Keyword` in Java
transpiles to `keyword`, when `keyword` is a Java keyword (such as
`catch`, `finally`, `int`, `long`, and so forth).
- `@Replace(String value)`: This annotation allows the programmer to
substitute a method body implementation by a TypeScript
implementation. The annotations value contains TypeScript which is
generated as is by the JSweet transpiler. The code will be checked
by the TypeScript transpiler. The replacing code can contain
variables substituted using a mustache-like convention
(`{{variableName`}}). Here is the list of supported variables:
- `{{className}}`: the current class.
- `{{methodName}}`: the current method name.
- `{{body}}`: the body of the current method. A typical use of
this variable is to wrap the original behavior in a lambda. For
instance:
`/* before code */ let _result = () => { {{body}} }(); /* after code */ return _result;`.
- `{{baseIndent}}`: the indentation of the replaced method. Can be
used to generate well-formatted code.
- `{{indent}}`: substituted with an indentation. Can be used to
generate well-formatted code.
#### Example
The following example illustrates the use of the `@Erased` and
`@Replace` annotations. Here, the `@Erased` annotation is used to remove
the `readObject` method from the generated code, because it does not
make sense in JavaScript (it is a Java-serialization specific method).
The `@Replace` annotation allows defining a direct TypeScript/JavaScript
implementation for the `searchAddress` method.
``` java
class Person {
List<String> addresses = new ArrayList<String>();
@Erased
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
[...]
}
@Replace("return this.addresses.filter(address => address.match(regex))[0]")
public String searchAddress(String regex) {
Optional<String> match = addresses.stream().filter(address -> address.matches(regex)).findFirst();
return match.isPresent()?match.get():null;
}
}
```
Using JSweet annotations makes it possible to share classes between Java
and JavaScript in a flexible way. Useless methods in JavaScript are
erased, and some methods can have different implementations for Java and
JavaScript.
### Centralizing annotations in `jsweetconfig.json`
JSweet supports the definition of annotations within a unique
configuration file (`jsweetconfig.json`). There are many reasons why
programmers would want to define annotations within that file instead of
defining annotations in the Java source code.
1. Annotations or annotation contents may differ depending on the
context. It may be convenient to have different configuration files
depending on the context. It is easier to switch a configuration
file with another than having to change all the annotations in the
program.
2. Adding annotations to the Java program is convenient to tune the
program locally (on a given element). However, in some cases,
similar annotations should apply on a set of program elements, in
order to automatize global tuning of the program. In that case, it
is more convenient to install annotations by using an expression,
that will match a set of program elements at once. This mechanism is
similar to the *pointcut* mechanism that can be found in Aspect
Oriented Software Design. It allows capturing a global modification
in a declarative manner.
3. Using annotations in the Java source code entails a reference to the
JSweet API (the `jsweet.lang` package) that may be seen as an
unwanted dependency for some programmers who want their Java code to
remain as "pure" as possible.
The JSweet configuration file (`jsweetconf.json`) is a JSON file
containing a list of configuration entries. Among the configuration
entries, so-called global filters can be defined using the following
structure:
``` java
<annotation>: {
"include": <match_expressions>,
"exclude": <match_expressions>
}
```
Where `<annotation>` is the annotation to be added, with potential
parameters, and `include` and `exclude` are lists of match expressions.
An element in the program will be annotated with the annotation, if its
signature matches any of the expressions in the `include` list and does
not match any the expressions in the `exclude` list.
A match expression is a sort of simplified regular expression,
supporting the following wildcards:
1. `*` matches any token or token sub-part in the signature of the
program element (a token is an identifier part of a signature, for
instance `A.m(java.lang.String)` contains the tokens `A`, `m`, and
`java.lang.String`).
2. `**` matches any list of tokens in signature of the program element.
3. `..` matches any list of tokens in signature of the program element.
(same as `**`)
4. `!` negates the expression (first character only).
For example:
``` java
// all the elements and subelements (fields, methods, ...) in the x.y.z package
x.y.z.**
// all the methods in the x.y.z.A class
x.y.z.A.*(..)
// all the methods taking 2 arguments in the \texttt{x.y.z.A} class
x.y.z.A.*(*,*)
// all fields called aField in all the classes of the program
**.aField
```
Here is a more complete example with a full `jsweetconfig.json`
configuration file.
``` java
{
// all classes and packages in x.y.z will become top level
"@Root": {
"include": [ "x.y.z" ]
},
// do not generate any TypeScript code for Java-specific methods
"@Erased": {
"include": [ "**.writeObject(..)", "**.readObject(..)", "**.hashCode(..)" ]
},
// inject logging in all setters and getters of the x.y.z.A class
"@Replace('console.info('entering {{methodName}}'); let _result = () => { {{body}} }(); console.info('returning '+_result); return _result;')": {
"include": [ "x.y.z.A.set*(*)", "x.y.z.A.get*()", "x.y.z.A.is*()" ]
}
}
```
Note that annotations are defined with simple names only. Thats because
they are core JSweet annotations (defined in `jsweet.lang`). Non-core
annotations can be added the same way, but the programmer must use fully
qualified names.
### Programmatic tuning with adapters
Declarative tuning through annotation rapidly hits limitations when
tuning the generation for specific purposes (typically when supporting
additional Java libraries). Hence, JSweet provides an API so that
programmers can extend the way JSweet generates the intermediate
TypeScript code. Writing such an adaptation program is similar to
writing a regular Java program, except that it will apply to your
programs to transform them. As such, it falls into the category of
so-called meta-programs (i.e. programs use other programs as data).
Since programmers may write extensions that leads to invalid code, that
is where it becomes really handy to have an intermediate compilation
layer. If the generated code is invalid, the TypeScript to JavaScript
compilation will raise errors, thus allowing the programmer to fix the
extension code.
#### Introducing the extension API
The extension API is available in the `org.jsweet.transpiler.extension`
package. It is based on a factory pattern
(`org.jsweet.transpiler.JSweetFactory`) that allows the programmer to
adapt all the main components of the transpiler by subclassing them. In
practice, most adaptations can be done by creating new printer adapters,
as subclasses of `org.jsweet.transpiler.extension.PrinterAdapter`.
Adapters are the core extension mechanism because they are chainable and
can be composed (it is a sort of decorator pattern). JSweet uses default
adapters in a default adaptation chain and tuning JSweet will then
consist in adding new adapters to the chain.
An adapter will typically perform three kinds of operations to tune the
generated code:
1. Map Java types to TypeScript ones.
2. Add annotations to the program either in a declarative way (with
global filters) or in a programmatic way (with annotation managers).
3. Override printing methods defined in `PrinterAdapter` in order to
override the TypeScript core that is generated by default. Printing
methods take program elements, which are based on the standard
`javax.lang.model.element` API. It provides an extension of that API
for program elements that are expressions and statements
(`org.jsweet.transpiler.extension.model`).
The following template shows the typical sections when programming an
adapter. First, an adapter must extend `PrinterAdapter` or any other
adapter. It must define a constructor taking the parent adapter, which
will be set by JSweet when inserting the adapter in the chain.
``` java
public class MyAdapter extends PrinterAdapter {
public MyAdapter(PrinterAdapter parent) {
super(parent);
...
```
In the constructor, an adapter typically maps Java types to TypeScript
types.
``` java
// will change the type in variable/parameters declarations
addTypeMapping("AJavaType", "ATypeScriptType");
// you may want to erase type checking by mapping to 'any'
addTypeMapping("AJavaType2", "any");
[...]
```
In the constructor, an adapter can also add annotations in a more
flexible way than when using the `jsweetconfig.json` syntax.
``` java
// add annotations dynamically to the AST, with global filters
addAnnotation("jsweet.lang.Erased", //
"**.readObject(..)", //
"**.writeObject(..)", //
"**.hashCode(..)");
// or with annotation managers (see the Javadoc and the example below)
addAnnotationManager(new AnnotationManager() { ... });
}
```
Most importantly, an adapter can override substitution methods for most
important AST elements. By overriding these methods, an adapter will
change the way JSweet generates the intermediate TypeScript code. To
print out code, you can use the `print` method, which is defined in the
root `PrinterAdapter` class. For example, the following code will
replace all `new AJavaType(...)` with `new ATypeScriptType(...)`.
``` java
@Override
public boolean substituteNewClass(NewClassElement newClass) {
// check if the 'new' applies to the right class
if ("AJavaType".equals(newClass.getTypeAsElement().toString())) {
// the 'print' method will generate intermediate TypeScript code
print("new ATypeScriptType(")
.printArgList(newClass.getArguments()).print(")");
// once some code has been printed, you should return true to break
// the adapter chain, so your code will replace the default one
return true;
}
// if not substituted, delegate to the adapter chain
return super.substituteNewClass(newClass);
}
```
Most useful substitution method remains invocation substitution, which
is typically used to map a Java API to a similar JavaScript API.
``` java
@Override
public boolean substituteMethodInvocation(MethodInvocationElement invocation) {
// substitute potential method invocation here
[...]
// delegate to the adapter chain
return super.substituteMethodInvocation(invocation);
}
}
```
Note also a special method to insert code after a Java type has been
printed out:
``` java
@Override
public void afterType(TypeElement type) {
super.afterType(type);
// insert whatever TypeScript you need here
[...]
}
```
There are many applications to adapters (see the examples below).
Besides tuning the code generation and supporting Java APIs at
compile-time, adapters can also be used to raise errors when the
compiled code does not conform to expected standards depending on the
target context. Another very useful use case it to allow the generation
of proxies. For instance one can write an adapter that will generate
JavaScript stubs to invoke Java services deployed with JAX-RS.
#### Installing and activating adapters
Once you have written an adapter, you need to compile it and add it to
the adapter chain. The simplest way to do it with JSweet is to put it in
the `jsweet_extension` directory that you need to create at the root of
your project JSweet. In that directory, you can directly add Java source
files for adapters, that will be compiled by JSweet on the fly. For
instance, you may add two custom adapters `CustomAdapter1.java` and
`CustomAdapter2.java` in `jsweet_extension/com/mycompany/`.
Then, in order to activate that adapter, you just need to add the
`jsweetconfig.json` file at the root of the project and define the
`adapters` configuration option, like this:
``` java
{
// JSweet will add the declared adapters at the beginning of the default
// chain... you can add as many adapters as you need
adapters: [ "com.mycompany.CustomAdapter1", "com.mycompany.CustomAdapter2" ]
}
```
#### Hello world adapter
Here, we will step through how to tune the JSweet generation to generate
strings in place of dates when finding `java.util.Date` types in the
Java program.
First, create the `HelloWorldAdapter.java` file in the
`jsweet_extension` directory at the root of your project. Copy and paste
the following code in that file:
``` java
import org.jsweet.transpiler.extension.PrinterAdapter;
public class HelloWorldAdapter extends PrinterAdapter {
public HelloWorldAdapter(PrinterAdapter parent) {
super(parent);
addTypeMapping(java.util.Date.class.getName(), "string");
}
}
```
Second, in the projects root directory, create the `jsweetconfig.json`
file with the following configuration:
``` java
{
adapters: [ "HelloWorldAdapter" ]
}
```
Done. Now you can just try this extension on the following simple Java
DTO:
``` java
package source.extension;
import java.util.Date;
/**
* A Hello World DTO.
*
* @author Renaud Pawlak
*/
public class HelloWorldDto {
private Date date;
/**
* Gets the date.
*/
public Date getDate() {
return date;
}
/**
* Sets the date.
*/
public void setDate(Date date) {
this.date = date;
}
}
```
The generated code should look like:
``` java
/* Generated from Java with JSweet 2.XXX - http://www.jsweet.org */
namespace source.extension {
/**
* A Hello World DTO.
*
* @author Renaud Pawlak
* @class
*/
export class HelloWorldDto {
/*private*/ date : string;
public constructor() {
this.date = null;
}
/**
* Gets the date.
* @return {string}
*/
public getDate() : string {
return this.date;
}
/**
* Sets the date.
* @param {string} date
*/
public setDate(date : string) {
this.date = date;
}
}
HelloWorldDto["__class"] = "source.extension.HelloWorldDto";
}
```
Note that all the date types have been translated to strings as
expected. By the way, note also the JSDoc support, which makes JSweet a
powerful tool to create well-documented JavaScript APIs from Java (doc
comments are also tunable in adapters!).
### Extension examples
The following sections illustrate the use of JSweet adapters with 5
real-life examples. Most of these adapters are built-in with JSweet (in
the `org.jsweet.transpiler.extension` package) and can just be activated
by adding them to the adapter chain as explained above. If you want to
modify the adapters, just copy-paste the code in the `jsweet_extension`
directory and change the names.
#### Example 1: an adapter to rename private fields
This simple adapter renames non-public members by adding two underscores
as a prefix. Note that this could be dangerous to use for protected
fields if wanting to access them from subclasses declared in other
JSweet projects. So you may want to use carefully or to modify the code
for your own needs.
This adapter is a good example for demonstrating how to use annotation
managers. Annotation managers are used to add (soft) annotations to
program elements driven by some Java code (programmatically). Annotation
managers are added to the context and will be chained to other existing
annotation managers (potentially added by other adapters). An annotation
manager must implement the `manageAnnotation` method, that will tell if
a given annotation should be added, removed, or left unchanged on a
given element. If the annotation has parameters, an annotation manager
shall implement the `getAnnotationValue` in order to specify the values.
In this example, the annotation manager adds the `@jsweet.lang.Name`
annotation to all non-public elements in order to rename them and add
the underscores to the initial name.
``` java
package org.jsweet.transpiler.extension;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import org.jsweet.transpiler.util.Util;
public class AddPrefixToNonPublicMembersAdapter extends PrinterAdapter {
public AddPrefixToNonPublicMembersAdapter(PrinterAdapter parentAdapter) {
super(parentAdapter);
// add a custom annotation manager to the chain
addAnnotationManager(new AnnotationManager() {
@Override
public Action manageAnnotation(Element element, String annotationType) {
// add the @Name annotation to non-public elements
return "jsweet.lang.Name".equals(annotationType)
&& isNonPublicMember(element) ? Action.ADD : Action.VOID;
}
@Override
public <T> T getAnnotationValue(Element element,
String annotationType, String propertyName,
Class<T> propertyClass, T defaultValue) {
// set the name of the added @Name annotation (value)
if ("jsweet.lang.Name".equals(annotationType) && isNonPublicMember(element)) {
return propertyClass.cast("__" + element.getSimpleName());
} else {
return null;
}
}
private boolean isNonPublicMember(Element element) {
return (element instanceof VariableElement || element instanceof ExecutableElement)
&& element.getEnclosingElement() instanceof TypeElement
&& !element.getModifiers().contains(Modifier.PUBLIC)
&& Util.isSourceElement(element);
}
});
}
}
```
#### Example 2: an adapter to use ES6 Maps
JSweet default implementation of maps behaves as follows:
- If the key type is a string, the map is transpiled to a regular
JavaScript object, where property names will be the keys.
- If the key type is an object (any other than a string), the map is
transpiled as a list of entries. The implementation is quite
inefficient because finding a key requires iterating over the
entries to find the right entry key.
In some contexts, you may want to get more efficient map
implementations. If you target modern browsers (or expect to have the
appropriate polyfill available), you can simply use the `Map` object,
which was standardized with ES6. Doing so requires an adapter that
performs the following actions:
- Erase the Java `Map` type and replace it with the JavaScript `Map`
type, or actually the `any` type, since you may want to keep the
`Object` implementation when keys are strings.
- Substitute the construction of a map with the corresponding
JavaScript construction.
- Substitute the invocations on Java maps with the corresponding
JavaScript invocations.
Note that the following adapter is a partial implementation that shall
be extended to support more cases and adapted to your own requirements.
Additionally, this implementation generates untyped JavaScript in order
to avoid having to have the ES6 API in the compilation path.
``` java
package org.jsweet.transpiler.extension;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.TreeMap;
import javax.lang.model.element.Element;
import org.jsweet.transpiler.model.MethodInvocationElement;
import org.jsweet.transpiler.model.NewClassElement;
public class MapAdapter extends PrinterAdapter {
// all the Java types that will be translated to ES6 maps
static String[] mapTypes = {
Map.class.getName(), HashMap.class.getName(),
TreeMap.class.getName(), Hashtable.class.getName() };
public MapAdapter(PrinterAdapter parent) {
super(parent);
// rewrite all Java map and compatible map implementations types
// note that we rewrite to 'any' because we don't want to require the
// ES6 API to compile (all subsequent accesses will be untyped)
for (String mapType : mapTypes) {
addTypeMapping(mapType, "any");
}
}
@Override
public boolean substituteNewClass(NewClassElement newClass) {
String className = newClass.getTypeAsElement().toString();
// map the map constructor to the global 'Map' variable (untyped access)
if (Arrays.binarySearch(mapTypes, className) >= 0) {
// this access is browser/node-compatible
print("new (typeof window == 'undefined'?global:window)['Map'](")
.printArgList(newClass.getArguments()).print(")");
return true;
}
// delegate to the adapter chain
return super.substituteNewClass(newClass);
}
@Override
public boolean substituteMethodInvocation(MethodInvocationElement invocation) {
if (invocation.getTargetExpression() != null) {
Element targetType = invocation.getTargetExpression().getTypeAsElement();
if (Arrays.binarySearch(mapTypes, targetType.toString()) >= 0) {
// Java Map methods are mapped to their JavaScript equivalent
switch (invocation.getMethodName()) {
case "put":
printMacroName(invocation.getMethodName());
print(invocation.getTargetExpression()).print(".set(")
.printArgList(invocation.getArguments())
.print(")");
return true;
// although 'get' has the same name, we still rewrite it in case
// another extension would provide it's own implementation
case "get":
printMacroName(invocation.getMethodName());
print(invocation.getTargetExpression()).print(".get(")
.printArgList(invocation.getArguments())
.print(")");
return true;
case "containsKey":
printMacroName(invocation.getMethodName());
print(invocation.getTargetExpression()).print(".has(")
.printArgList(invocation.getArguments())
.print(")");
return true;
// we use the ES6 'Array.from' method in an untyped way to
// transform the iterator in an array
case "keySet":
printMacroName(invocation.getMethodName());
print("(<any>Array).from(")
.print(invocation.getTargetExpression()).print(".keys())");
return true;
case "values":
printMacroName(invocation.getMethodName());
print("(<any>Array).from(")
.print(invocation.getTargetExpression()).print(".values())");
return true;
// in ES6 maps, 'size' is a property, not a method
case "size":
printMacroName(invocation.getMethodName());
print(invocation.getTargetExpression()).print(".size");
return true;
}
}
}
// delegate to the adapter chain
return super.substituteMethodInvocation(invocation);
}
}
```
#### Example 3: an adapter to support Java BigDecimal
Javas BigDecimal API is a really good API to avoid typical floating
point precision issues, especially when working on currencies. This API
is not available by default in JavaScript and would be quite difficult
to emulate. GWT provides an emulation of the BigDecimal API, which is
implemented with Java, but JSweet proposes another way to do it, which
consists of mapping the BigDecimal API to an existing JavaScript API
called Big.js. Mapping to an existing JS library has several advantages
compared to emulating an API:
1. The implementation is already available in JavaScript, so there is
less work emulating the Java library.
2. The implementation is pure JavaScript and is made specifically for
JavaScript. So we can assume that will be more efficient that an
emulation, and even more portable.
3. The generated code is free from any Java APIs, which makes it more
JavaScript friendly and more inter-operable with existing JavaScript
programs (legacy JavaScript clearly uses Big.js objects, and if not,
we can decide to tune the adapter).
The following code shows the adapter that tunes the JavaScript
generation to map the Javas BigDecimal API to the Big JavaScript
library. This extension requires the big.js candy to be available in the
JSweet classpath: https://github.com/jsweet-candies/candy-bigjs.
``` java
package org.jsweet.transpiler.extension;
import java.math.BigDecimal;
import javax.lang.model.element.Element;
import org.jsweet.transpiler.extension.PrinterAdapter;
import org.jsweet.transpiler.model.MethodInvocationElement;
import org.jsweet.transpiler.model.NewClassElement;
public class BigDecimalAdapter extends PrinterAdapter {
public BigDecimalAdapter(PrinterAdapter parent) {
super(parent);
// all BigDecimal types are mapped to Big
addTypeMapping(BigDecimal.class.getName(), "Big");
}
@Override
public boolean substituteNewClass(NewClassElement newClass) {
String className = newClass.getTypeAsElement().toString();
// map the BigDecimal constructors
if (BigDecimal.class.getName().equals(className)) {
print("new Big(").printArgList(newClass.getArguments()).print(")");
return true;
}
// delegate to the adapter chain
return super.substituteNewClass(newClass);
}
@Override
public boolean substituteMethodInvocation(MethodInvocationElement invocation) {
if (invocation.getTargetExpression() != null) {
Element targetType = invocation.getTargetExpression().getTypeAsElement();
if (BigDecimal.class.getName().equals(targetType.toString())) {
// BigDecimal methods are mapped to their Big.js equivalent
switch (invocation.getMethodName()) {
case "multiply":
printMacroName(invocation.getMethodName());
print(invocation.getTargetExpression())
.print(".times(").printArgList(invocation.getArguments())
.print(")");
return true;
case "add":
printMacroName(invocation.getMethodName());
print(invocation.getTargetExpression())
.print(".plus(").printArgList(invocation.getArguments())
.print(")");
return true;
case "scale":
printMacroName(invocation.getMethodName());
// we assume that we always have a scale of 2, which is a
// good default if we deal with currencies...
// to be changed/implemented further
print("2");
return true;
case "setScale":
printMacroName(invocation.getMethodName());
print(invocation.getTargetExpression())
.print(".round(").print(invocation.getArguments().get(0))
.print(")");
return true;
case "compareTo":
printMacroName(invocation.getMethodName());
print(invocation.getTargetExpression()).print(".cmp(")
.print(invocation.getArguments().get(0))
.print(")");
return true;
case "equals":
printMacroName(invocation.getMethodName());
print(invocation.getTargetExpression()).print(".eq(")
.print(invocation.getArguments().get(0))
.print(")");
return true;
}
}
}
// delegate to the adapter chain
return super.substituteMethodInvocation(invocation);
}
}
```
#### Example 4: an adapter to map enums to strings
This example tunes the JavaScript generation to remove enums and replace
them with strings. It only applies to enums that are annotated with
@`jsweet.lang.StringType`.
For instance: `@StringType enum MyEnum { A, B, C }` will be erased and
all subsequent accesses to the enum constants will be mapped to simple
strings (`MyEnum.A => "A", MyEnum.B => "B", MyEnum.C => "C"`).
Typically, a method declaration such as `void m(MyEnum e) {...}` will be
printed as `void m(e : string) {...}`. And of course, the invocation
`xxx.m(MyEnum.A)` will be printed as `xxx.m("A")`.
``` java
package org.jsweet.transpiler.extension;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import org.jsweet.JSweetConfig;
import org.jsweet.transpiler.model.CaseElement;
import org.jsweet.transpiler.model.ExtendedElement;
import org.jsweet.transpiler.model.MethodInvocationElement;
import org.jsweet.transpiler.model.VariableAccessElement;
public class StringEnumAdapter extends PrinterAdapter {
private boolean isStringEnum(Element element) {
// note: this function could be improved to exclude enums that have
// fields or methods other than the enum constants
return element.getKind() == ElementKind.ENUM
&& hasAnnotationType(element, JSweetConfig.ANNOTATION_STRING_TYPE);
}
public StringEnumAdapter(PrinterAdapter parent) {
super(parent);
// eligible enums will be translated to string in JS
addTypeMapping((typeTree, name) ->
isStringEnum(typeTree.getTypeAsElement()) ? "string" : null);
// ignore enum declarations with a programmatic annotation manager
addAnnotationManager(new AnnotationManager() {
@Override
public Action manageAnnotation(Element element, String annotationType) {
// add the @Erased annotation to string enums
return JSweetConfig.ANNOTATION_ERASED.equals(annotationType)
&& isStringEnum(element) ? Action.ADD : Action.VOID;
}
});
}
@Override
public boolean substituteMethodInvocation(MethodInvocationElement invocation) {
if (invocation.getTargetExpression() != null) {
Element targetType = invocation.getTargetExpression().getTypeAsElement();
// enum API must be erased and use plain strings instead
if (isStringEnum(targetType)) {
switch (invocation.getMethodName()) {
case "name":
printMacroName(invocation.getMethodName());
print(invocation.getTargetExpression());
return true;
case "valueOf":
printMacroName(invocation.getMethodName());
print(invocation.getArgument(0));
return true;
case "equals":
printMacroName(invocation.getMethodName());
print("(").print(invocation.getTargetExpression()).print(" == ")
.print(invocation.getArguments().get(0)).print(")");
return true;
}
}
}
return super.substituteMethodInvocation(invocation);
}
@Override
public boolean substituteVariableAccess(VariableAccessElement variableAccess) {
// accessing an enum field is replaced by a simple string value
// (MyEnum.A => "A")
if (isStringEnum(variableAccess.getTargetElement())) {
print("\"" + variableAccess.getVariableName() + "\"");
return true;
}
return super.substituteVariableAccess(variableAccess);
}
@Override
public boolean substituteCaseStatementPattern(CaseElement caseStatement,
ExtendedElement pattern) {
// map enums to strings in case statements
if (isStringEnum(pattern.getTypeAsElement())) {
print("\"" + pattern + "\"");
return true;
}
return super.substituteCaseStatementPattern(caseStatement, pattern);
}
}
```
#### Example 5: an adapter to generate JavaScript JAX-RS proxies/stubs
It is a common use case to implement a WEB or mobile application with
Java on the server and JavaScript on the client. Typically, a
JEE/Jackson server will expose a REST API through the JAX-RS
specifications, and the HTML5 client will have to invoke this API using
`XMLHttpRequest` or higher-level libraries such as jQuery. However,
manually coding the HTTP invocations comes with many drawbacks:
- It requires the use of specific APIs (XHR, jQuery), which is not
easy for all programmers and may imply different programming styles
that would make the code more difficult to read and maintain.
- It requires the programmers to handle manually the
serialization/deserialization, while it can be done automatically
trough the use of annotation-driven generative programming.
- It leads to unchecked invocations, which means that it is easy for
the programmer to make an error in the name of the
service/path/parameters, and in the expected DTOs. No refactoring
and content-assist is available.
With a JSweet adapter, using the `afterType` method it is easy to
automatically generate a TypeScript stub that is well-typed and performs
the required operations for invoking the REST service, simply by using
the service API and the JAX-RS annotations. This type of tooling falls
in the category of so-called Generative Programming.
The following code is only a partial implementation of an adapter that
would introspect the programs model and generate the appropriate stubs
in TypeScript. It is not meant to be operational, so you need to modify
to fit your own use case.
``` java
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
class JaxRSStubAdapter extends PrinterAdapter {
public JaxRSStubAdapter(PrinterAdapter parent) {
super(parent);
// erase service classes (server-side only)
addAnnotationManager(new AnnotationManager() {
@Override
public Action manageAnnotation(Element element, String annotationType) {
return JSweetConfig.ANNOTATION_ERASED.equals(annotationType)
&& hasAnnotationType(element, Path.class.getName()) ?
Action.ADD : Action.VOID;
}
});
}
@Override
public void afterType(TypeElement type) {
super.afterType(type);
if (hasAnnotationType(type, Path.class.getName())) {
// actually generates the JAX-RS stub
println().printIndent();
print("class ").print(type.getSimpleName()).print(" {");
startIndent();
String typePathAnnotationValue = getAnnotationValue(type,
Path.class.getName(), String.class, null);
String typePath = typePathAnnotationValue != null ? typePathAnnotationValue : "";
for (Element e : type.getEnclosedElements()) {
if (e instanceof ExecutableElement
&& hasAnnotationType(e, GET.class.getName(),
PUT.class.getName(), Path.class.getName())) {
ExecutableElement method = (ExecutableElement) e;
println().printIndent().print(method.getSimpleName().toString())
.print("(");
for (VariableElement parameter : method.getParameters()) {
print(parameter.getSimpleName())
.print(" : ").print(getMappedType(parameter.asType()))
.print(", ");
}
print("successHandler : (");
if (method.getReturnType().getKind() != TypeKind.VOID) {
print("result : ").print(getMappedType(method.getReturnType()));
}
print(") => void, errorHandler?: () => void").print(") : void");
print(" {").println().startIndent().printIndent();
String pathAnnotationValue = getAnnotationValue(e, Path.class.getName(),
String.class, null);
String path = pathAnnotationValue != null ? pathAnnotationValue : "";
String httpMethod = "POST";
if(hasAnnotationType(e, GET.class.getName())) {
httpMethod = "GET";
}
if(hasAnnotationType(e, POST.class.getName())) {
httpMethod = "POST";
}
String[] consumes = getAnnotationValue(e, "javax.ws.rs.Consumes",
String[].class, null);
if (consumes == null) {
consumes = new String[] { "application/json" };
}
// actual code to be done
print("// modify JaxRSStubAdapter to generate an HTTP invocation here")
.println().printIndent();
print("// - httpMethod: " + httpMethod).println().printIndent();
print("// - path: " + typePath + path).println().printIndent();
print("// - consumes: " + consumes[0]);
println().endIndent().printIndent().print("}");
}
}
println().endIndent().printIndent().print("}");
}
}
}
```
NOTE: for compilation, you need the JAX-RS API in your classpath.
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>2.1-m07</version>
</dependency>
As an example, let us consider the following JAX-RS service.
``` java
@Path("/hello")
public class HelloWorldService {
@GET
@Path("/{param}")
@Produces(MediaType.APPLICATION_JSON)
public HelloWorldDto getMsg(@PathParam("param") String msg) {
String output = "service says : " + msg;
return new HelloWorldDto(output);
}
}
```
Using the following DTO:
``` java
public class HelloWorldDto {
private String msg;
public HelloWorldDto(String msg) {
super();
this.msg = msg;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
```
If you apply JSweet enhanced with `JaxRSStubAdapter`, you will get the
following TypeScript code (and corresponding JavaScript):
``` java
export class HelloWorldDto {
/*private*/ msg : string;
public constructor(msg : string) {
this.msg = msg;
}
public getMsg() : string {
return this.msg;
}
public setMsg(msg : string) {
this.msg = msg;
}
}
HelloWorldDto["__class"] = "HelloWorldDto";
class HelloWorldService {
getMsg(msg : string,
successHandler : (result : HelloWorldDto) => void,
errorHandler?: () => void) : void {
// modify JaxRSStubAdapter to generate an HTTP invocation here
// - httpMethod: GET
// - path: /hello/{param}
// - consumes: application/json
}
}
```
So, all you need to do is to modify the code of the adapter to generate
the actual invocation code in place of the comment. Once it is done, you
can use the generated JavaScript code as a bundle to access your service
in a well-typed way. Moreover, you can use JSweet to generate the
TypeScript definitions of your services and DTOs, so that your
TypeScript client are well-typed (see the JSweets `declaration`
option).
#### Example 6: an adapter to disallow global variables
This is a quite special adapter since it does not really generate any
code, but it reports errors when the source code does not conform to
certain coding standards. Here, we implement a simple constraint that
reports errors when the user tries to declare global variables (i.e. in
JSweet non-final static field declared in a `Globals` class).
``` java
package org.jsweet.transpiler.extension;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import org.jsweet.transpiler.JSweetProblem;
public class DisallowGlobalVariablesAdapter extends PrinterAdapter {
public DisallowGlobalVariablesAdapter(PrinterAdapter parentAdapter) {
super(parentAdapter);
}
@Override
public void afterType(TypeElement type) {
// we only check for static variables that are in a Globals class but
// this could be generalized to any static variable
if (!type.getQualifiedName().toString().startsWith("def.")
&& type.getSimpleName().toString().equals("Globals")) {
for (Element member : type.getEnclosedElements()) {
if (member.getKind() == ElementKind.FIELD) {
VariableElement field = (VariableElement) member;
// only non-final static variable have side effect
if (field.getModifiers().contains(Modifier.STATIC)
&& !field.getModifiers().contains(Modifier.FINAL)) {
report(field, JSweetProblem.USER_ERROR, "global variables are not allowed");
}
}
}
}
super.afterType(type);
}
}
```
This adapter falls into the category of static analysis, which can be
useful (along with code generation) to check that the input complies to
the expected coding guidelines. It would be easy to enhance this adapter
to add check on any static fields. A nice feature would be to disable
the check when the field is annotated with a specific annotation (for
instance `@AllowSideEffect`).
Appendix 1: JSweet transpiler options
-------------------------------------
[-h|--help]
[-w|--watch]
Start a process that watches the input directories for changes and
re-run transpilation on-the-fly.
[-v|--verbose]
Turn on all levels of logging.
[--encoding <encoding>]
Force the Java compiler to use a specific encoding (UTF-8, UTF-16, ...).
(default: UTF-8)
[--jdkHome <jdkHome>]
Set the JDK home directory to be used to find the Java compiler. If not
set, the transpiler will try to use the JAVA_HOME environment variable.
Note that the expected JDK version is greater or equals to version 8.
(-i|--input) input1:input2:...:inputN
An input directory (or column-separated input directories) containing
Java files to be transpiled. Java files will be recursively looked up in
sub-directories. Inclusion and exclusion patterns can be defined with
the 'includes' and 'excludes' options.
[--includes includes1:includes2:...:includesN ]
A column-separated list of expressions matching files to be included
(relatively to the input directory).
[--excludes excludes1:excludes2:...:excludesN ]
A column-separated list of expressions matching files to be excluded
(relatively to the input directory).
[(-d|--defInput) defInput1:defInput2:...:defInputN ]
An input directory (or column-separated input directories) containing
TypeScript definition files (*.d.ts) to be used for transpilation.
Definition files will be recursively looked up in sub-diredctories.
[--noRootDirectories]
Skip the root directories (i.e. packages annotated with
@jsweet.lang.Root) so that the generated file hierarchy starts at the
root directories rather than including the entire directory structure.
[--tsout <tsout>]
Specify where to place generated TypeScript files. (default: .ts)
[(-o|--jsout) <jsout>]
Specify where to place generated JavaScript files (ignored if jsFile is
specified). (default: js)
[--disableSinglePrecisionFloats]
By default, for a target version >=ES5, JSweet will force Java floats to
be mapped to JavaScript numbers that will be constrained with ES5
Math.fround function. If this option is true, then the calls to
Math.fround are erased and the generated program will use the JavaScript
default precision (double precision).
[--tsOnly]
Do not compile the TypeScript output (let an external TypeScript
compiler do so).
[--ignoreDefinitions]
Ignore definitions from def.* packages, so that they are not generated
in d.ts definition files. If this option is not set, the transpiler
generates d.ts definition files in the directory given by the tsout
option.
[--declaration]
Generate the d.ts files along with the js files, so that other programs
can use them to compile.
[--dtsout <dtsout>]
Specify where to place generated d.ts files when the declaration option
is set (by default, d.ts files are generated in the JavaScript output
directory - next to the corresponding js files).
[--candiesJsOut <candiesJsOut>]
Specify where to place extracted JavaScript files from candies.
(default: js/candies)
[--sourceRoot <sourceRoot>]
Specify the location where debugger should locate Java files instead of
source locations. Use this flag if the sources will be located at
run-time in a different location than that at design-time. The location
specified will be embedded in the sourceMap to direct the debugger where
the source files will be located.
[--classpath <classpath>]
The JSweet transpilation classpath (candy jars). This classpath should
at least contain the core candy.
[(-m|--module) <module>]
The module kind (none, commonjs, amd, system or umd). (default: none)
[-b|--bundle]
Bundle up all the generated code in a single file, which can be used in
the browser. The bundle files are called 'bundle.ts', 'bundle.d.ts', or
'bundle.js' depending on the kind of generated code. NOTE: bundles are
not compatible with any module kind other than 'none'.
[(-f|--factoryClassName) <factoryClassName>]
Use the given factory to tune the default transpiler behavior.
[--sourceMap]
Generate source map files for the Java files, so that it is possible to
debug Java files directly with a debugger that supports source maps
(most JavaScript debuggers).
[--enableAssertions]
Java 'assert' statements are transpiled as runtime JavaScript checks.
[--header <header>]
A file that contains a header to be written at the beginning of each
generated file. If left unspecified, JSweet will generate a default
header.
[--workingDir <workingDir>]
The directory JSweet uses to store temporary files such as extracted
candies. JSweet uses '.jsweet' if left unspecified.
[--targetVersion <targetVersion>]
The EcmaScript target (JavaScript) version. Possible values: [ES3, ES5,
ES6] (default: ES3)
Appendix 2: packaging and static behavior
-----------------------------------------
This appendix explains some static behavior with regards to packaging.
### When main methods are invoked
When main methods are invoked depends on the way the program is
packaged.
- `module`: off, `bundle`: off. With default packaging, one Java
source file corresponds to one generated JavaScript file. In that
case, when loading a file in the browser, all the main methods will
be invoked right at the end of the file.
- `module`: off, `bundle`: on. When the `bundle` option is on and the
`module` option is off, main methods are called at the end of the
bundle.
- `module`: on, `bundle`: off. With module packaging (`module`
option), one Java package corresponds to one module. With modules,
it is mandatory to have only one main method in the program, which
will be the global entry point from which the module dependency
graph will be calculated. The main module (the one with the main
method) will use directly or transitively all the other modules. The
main method will be invoked at the end of the main module
evaluation.
Because of modules, it is good practice to have only one main method in
an application.
### Static and inheritance dependencies
In TypeScript, programmers need to take care of the ordering of classes
with regards to static fields and initializers. Typically, a static
member cannot be initialized with a static member of a class that has
not yet been defined. Also, a class cannot extend a class that has not
been defined yet. This forward-dependency issue triggers runtime errors
when evaluating the generated JavaScript code, which can be quite
annoying for the programmers and may requires the use of external
JavaScript bundling tools, such as Browserify.
JSweets statics lazy initialization allows static forward references
within a given file, and within an entire bundle when the `bundle`
option is set. Also, when bundling a set of files, JSweet analyses the
inheritance tree and performs a partial order permutation to eliminate
forward references in the inheritance tree. Note that TypeScript bundle
provide a similar feature, but the references need to be manually
declared, which is not convenient for programmers.
To wrap it up, here are the guidelines to be followed by the programmers
depending on the packaging method:
- `module`: off, `bundle`: off. One JavaScript file is generated per
Java file. The programmer must take care of including the files in
the right order in the HTML page, so that there are no forward
references with regard to inheritance and statics. Within a given
file, static forward references are allowed, but inheritance forward
reference are not supported yet (this will be supported in coming
releases).
- `module`: off, `bundle`: on. This configuration produces a unique
browser-compatible bundle file that can be included in an HTML page.
Here, the programmer does not have to take care at all of the
forward references across files. Exactly like in Java, the order
does not matter. Within a single file, the programmer still have to
take care of the inheritance forward references (in other words, a
subclass must be declared after its parent class) (this will be
supported in coming releases).
- `module`: commonjs, amd or umd, `bundle`: off. This configuration
produces one module file per Java package so that they can be used
within a module system. For instance, using the `commonjs` module
kind will allow the program to run on Node.js. In that
configuration, the program should contain one main method and only
the module file containing the main method should be loaded (because
it will take care loading all the other modules). This configuration
imposes the same constraint within a single file (no
forward-references in inheritance).