ts-java-last

1.4.1 • Public • Published

ts-java

Build Status

Create TypeScript declaration files for Java packages.

Typescript provides the ability for Javascript programmers to write typesafe code.

Java is a typesafe language that provides a Reflection API allowing introspection of Java classes to determine type information for classes and methods.

Node-java is a node.js package that provides a Bridge API for node.js applications to connect to Java APIs.

ts-java is a tool that generates Typescript declaration files for Java classes, allowing Typescript programmers to use Java classes in their node.js applications with type safety comparable to using Java directly.

Discusion Forum

Please use the node-java discussion forum for discussion of ts-java.

Status

ts-java is still evolving but is already proving useful in active projects. One major feature not yet included in ts-java is support for Java Generics. We expect Java Generics will map cleanly to Typescript Generics.

Installation

If you are able to, it's convenient to install ts-java globally:

$ npm install -g ts-java

But you can also install ts-java as a dev dependency and run it from ./node_modules/.bin/ts-java:

$ npm install ts-java --save-dev

Usage

ts-java is configured by adding a tsjava property to the package.json file:

{
 "name": "my-application",
 "version": "0.0.1",
 ...
 "tsjava": {
   "classpath": [
     "target/**/*.jar"
   ],
   "packages": [
     "java.util.*",
     "java.util.function.*",
     "java.math.*",
     "org.apache.tinkerpop.gremlin.**"
   ],
   "classes": [
      "java.lang.Boolean",
      "java.lang.Long",
   ],
 }
}

We'll explain what the properties classpath, classes, and packages mean below. Assuming they are properly specified, ts-java can be run in the project's root directory to generate a tsJavaModule.ts file (the name tsJavaModule.ts is customizable, see below):

$ ts-java
ts-java version 1.0.3
Generated tsJavaModule.ts with 13 classes.
Excluded 20 classes referenced as method parameters.
Excluded 6 classes referenced as *interfaces*.
Excluded 1 classes referenced as *superclasses*.
$

The tsjava property in the package.json must define four nested properties:

  • tsJavaModulePath: The file path for the output file. In this README we assume the file is named tsJavaModule.ts, but you're free to give it any name you choose, and to specify that it be written to a directory, e.g. ./lib/myJavaModule.ts.

  • classpath: an array of glob expressions for the files to be added to the java classpath. It should be the same classpath that you will use to initialize node-java to run your application. In addition to the classes specified in classpath, ts-java automatically includes the Java runtime library classes.

  • packages: an array of package expression strings. Each string is interpreted as matching a package or a family of nested packages. Expressions should be of one of these two forms:

    1. path.to.some.package.*
    2. path.to.some.package.**

    The first form matches all classes directly in path.to.some.package but does not match any nested packages. The second form matches all classes in all packages rooted at path.to.some.package.

  • classes: an array of full class paths for classes that ts-java will generate interfaces for. If you want some but not all classes in a package, specify those classes here.

The tsjava property may also define these optional nested properties:

  • asyncOptions: An object specifying the suffixes to use for the three variants of java methods created by node-java. See the AsyncOptions section of the node-java README. However, ts-java imposes constraints on the values that may be specified, due to the fact that generated tsJavaModule.ts must be compatible with the core node-java API defined in DefinitelyTyped/java/java.d.ts. The asyncOptions assumed by java/java.d.ts are:
    "asyncOptions": {
      "syncSuffix": "",
      "asyncSuffix": "A",
      "promiseSuffix": "P"
    }

If you don't need all three types of method variants (sync, async, promise) you may omit the corresponding suffix. In that case, ts-java will not generate declarations for methods of that variant. If you want to change the suffix type, you will need to create your own java/java.d.ts to use instead of the version maintained in DefinitelyTyped.

  • javaTypingsPath: The relative path to the java.d.ts. This defaults to typings/java/java.d.ts, as installed by tsd from DefinitelyTyped.

Command Line Options

$ ts-java --help

  Usage: ts-java [options]

  Options:

    -h, --help     output usage information
    -V, --version  output the version number
    -q, --quiet    Run silently with no output
    -d, --details  Output diagnostic details

  All configuration options must be specified in a node.js package.json file,
  in a property tsjava.

  See the README.md file for more information.

By default, ts-java outputs the short summary shown above. The --details option expands the output to show all of the class names for each of the categories. For example:

$ ts-java --details
ts-java version 1.0.3
Generated classes:
   java.lang.Boolean
   java.lang.Class
   java.lang.ClassLoader
   java.lang.Integer
   java.lang.Object
   java.lang.String
   java.lang.reflect.AccessibleObject
   java.lang.reflect.Constructor
   java.lang.reflect.Executable
   java.lang.reflect.Field
   java.lang.reflect.Method
   java.lang.reflect.Parameter
   java.lang.reflect.Type
Classes that were referenced, but excluded by the current configuration:
   java.io.InputStream
   java.lang.CharSequence
   java.lang.Double
   java.lang.Float
   java.lang.Iterable
   java.lang.Long
   java.lang.Package
   java.lang.Short
   java.lang.StringBuffer
   java.lang.StringBuilder
   java.lang.annotation.Annotation
   java.lang.reflect.AnnotatedType
   java.lang.reflect.TypeVariable
   java.net.URL
   java.nio.charset.Charset
   java.security.ProtectionDomain
   java.util.Comparator
   java.util.Enumeration
   java.util.Locale
   java.util.stream.IntStream
Classes that were referenced as *interfaces*, but excluded by the current configuration:
   java.io.Serializable
   java.lang.CharSequence
   java.lang.Comparable
   java.lang.reflect.AnnotatedElement
   java.lang.reflect.GenericDeclaration
   java.lang.reflect.Member
Classes that were referenced as *superclasses*, but excluded by the current configuration:
   java.lang.Number

These details are useful while refining the configuration to select the exact set of classes your application needs available. The classes listed in both of the 'referenced but excluded' categories may include classes your application needs, in which case you should add those classes to the classes list.

Why not simply include all classes?

You certainly can. But we expect that most node applications that use Java libraries will only use a subset of the classes implemented in the libraries. Including all classes results in a larger tsJavaModule.ts file, which can slow down Typescript compilation, and also make it more cumbersome to use a tsJavaModule.ts file as documentation for the Java library's API.

What happens with excluded classes?

If an excluded class is referenced as a method parameter, ts-java replaces the type with the Typescript type any. A future release may provide an option to instead omit methods that use excluded types.

If the excluded class is referenced as an interface or superclass, the Typescript declaration won't include that relationship, though the implemented methods of the excluded interface will be accessible. So, the only real limitation is that you won't be able to use the excluded type to declare polymorphic variables.

What about Java Runtime classes?

ts-java always includes java.lang.Object and java.lang.String, but any other Java Runtime classes you need must be specified in either the packages or classes sections of the tsjava configuration.

The structure of the generated tsJavaModule.ts file

ts-java generates a Typescript source file which you must arrange to compile with the rest of your Typescript sources. The file declares interfaces for all of the Java classes specified in your package.json tsjava configuration, along with a few helper functions that you can use to import Java classes.

All Java classes are declared to exist in the Typescript module Java. Each Java package is mapped to a Typescript module. For example, a class such as java.lang.String is declared in nested Typescript modules as:

declare module Java {
  ...
  export module java.lang {
    export interface String extends Java.java.lang.Object {
      ...
    }
  }
  ...
}

In your Typescript application, you can refer to Java classes using fully qualified type paths such as Java.java.lang.String. Clearly this is too verbose. ts-java addresses this by declaring type aliases for all classes that have unique class names. The tsJavaModule.ts file includes a section like this:

declare module Java {
  ...
  export import Object = java.lang.Object;
  export import String = java.lang.String;
  ...
}

This allows you to write just Java.String instead of Java.java.lang.String.

To get access to a class via node-java, you typically use java.import(), such as:

  import java = require('java');
  ...
  var String = java.import('java.lang.String');

ts-java provides wrapper function importClass() that you must use instead to import Java classes. This function may be called with either the full class path, or with just the class name, whenever that class name uniquely determines one class from the set of classes you configured.

You application will typically use the tsJavaModule.ts file as follows:

    import hellojava = require('../tsJavaModule');
    import Java = hellojava.Java;
 
    Java.ensureJvm().then((): void => {
        var HelloJava = Java.importClass('HelloJava');
        ...
    });

See featureset/features/auto_import.feature for more information about using importClass() with short class names.

Functions exported in the tsJavaModule.ts

The generated tsJavaModule.ts file will re-export most of the function exported by the node-java module, as specified in java.d.ts. In addition, the tsJavaModule.ts file includes:

getJava(): NodeJavaAPI

Returns the underlying java module.

ensureJvm(): Promise<void>

Ensures that the JVM has been created. Idempotent, i.e 2nd and subsequent calls are no-ops.

fullyQualifiedName(className: string): string

Given a short class name (or a fully qualified class name) return the fully qualified classname. If the class name string is unrecognized, return undefined.

importClass(className: string): <javaclasstype>

Given a short or fully qualified classname, import the class and return its Static interface. Throws an exception for unrecognized class names. For example, either importClass('java.lang.Object') or importClass('Object') will import the class and return the proxy object whose type is Java.java.lang.Object.Static. See featureset/features/autoImport.feature for more information.

asInstanceOf(obj: any, className: string): <javainstancetype>

Given an object and a short or fully qualified classname, return the object casted to the given class type, or throw an exception if the cast is not valid.

L(n: number): Java.longValue_t

Given a number, return a value of type longValue_t, capable of representing a 64-bit integer. Defined only when java.lang.Long is included in the configuration.

isLongValue(obj: any): boolean

Returns true if obj is a longValue_t. Defined only when java.lang.Long is included in the configuration.

isJavaObject(obj: any): boolean

Returns true if obj is a Java object instance.

instanceOf(obj: any, className: string): boolean

Returns true if obj is a Java object instance of the specified class.

forEach(javaIterator: Java.Iterator, consumer: ConsumeObject)

Like array.forEach(). Applies the consumer function to each element returned by the iterator. See the documentation in the generated tsJavaModule.ts file for the definition of the ConsumeObject interface. See also featureset/features/utilityFunctions.feature for an example of use. Defined only when java.util.Iterator is included in the configuration.

Composing two or more Java libraries

If you are developing a large node application using multiple Java libraries you have a choice on how you use ts-java. You may generate a single tsJavaModule.ts file with all Java classes you use, or you may generate multiple modules reflecting the logical separation of the Java libraries you use. Each generated tsJavaModule.ts file is self-contained and can peacefully coexist with other tsJavaModule.ts files in one node process. The only subtlety we want to point out here has to to with passing objects between Java modules. Two independently generated tsJavaModule.ts files will contain some Java classes in common. At the very least they will each include java.lang.Object and java.lang.String, but they will likely include other classes. All such overlapping classes will generate interfaces that are largely compatible, but it is possible that there will be minor differences that will cause Typescript to think the classes are not compatible.

For example, suppose in one library you include java.lang.Class, and in the other library you do not. This will cause ts-java to generate different signatures for the getClass() method of java.lang.Object. Because of this, if code in your application needs to pass a Java object obtained from one ts-java module to another ts-java module you may need to use type casts/assertions. See integration/features/composability.feature for more information.

Examples

The directories hellojava, featureset, reflection and tinkerpop contain working examples of using ts-java.

Three of these examples include Cucumber tests. The .feature files document various aspects of how to use ts-java and what to expect from the generated Typescript declarations.

hellojava

This is a tiny test built from one trivial Java class HelloJava implementing one static method. See hellojava/features/hellojava.feature for Hello World style examples of using ts-java.

featureset

This directory contains Cucumber .feature files that are intended to provide good coverage of the ts-java feature set. The files in featureset/features/*.feature provide examples and documentation for most of the ts-java feature set. If you find some aspect of ts-java is not adequately covered in these .feature files, we encourage you to submit an issue.

reflection

ts-java is written in Typescript and uses Java Reflection, so it needs a tsJavaModule.ts file (in lib/reflection.ts). The reflection/ directory uses ts-java to generate this reflection/tsJavaModule.ts file. The project Makefile contains rules to declare the build broken if the reflection/tsJavaModule.ts differs from lib/reflection.ts, forcing us to keep the file up to date.

tinkerpop

Tinkerpop3 is an An Open Source Graph Computing Framework and is the primary use case that motivated us to create ts-java. If you are also a Tinkerpop user, check out ts-tinkerpop, a small library with utilities built on top of the Tinkerpop interfaces exposed by the java.d.ts declaration file.

integration

The directory integration/ contains a cucumber feature file that illustrates the integration of multiple independently generated ts-java modules, by using the ts-java modules created for hellojava, featureset, and reflection.

Package Sidebar

Install

npm i ts-java-last

Weekly Downloads

1

Version

1.4.1

License

MIT

Last publish

Collaborators

  • kol-93