Cross-Language Interoperability

C Language Interoperability

unsafe Context

Because C language is prone to cause insecurity, Cangjie stipulates that all functionalities that interoperate with C language can only occur in the unsafe context. The unsafe context is introduced using the unsafe keyword.

The unsafe keyword can be used in the following ways:

  1. Modifying a code block. The type of the unsafe expression is the type of the code block.
  2. Modifying a function.

All unsafe functions and CFunc functions must be called in the unsafe context.

All unsafe functions cannot be used as first-class citizens. For example, they cannot be assigned to variables, used as arguments or return values, or used as expressions. They can be called only in the unsafe context.

The syntax is defined as follows:

unsafeExpression
    : 'unsafe' '{' expressionOrDeclarations '}'
    ;

unsafeFunction
    : 'unsafe' functionDefinition
    ;

Calling C Functions

foreign Keyword and @C

To call a C function in Cangjie, you need to declare the function in the Cangjie code and use the foreign keyword to modify the function. The foreign function exists only in the top-level scope and is visible only in the package. Therefore, no other function modifiers can be used.

@C supports only the foreign function, non-general functions in the top-level scope, and the struct type. When the foreign function is modified, @C can be omitted. Unless otherwise specified, all foreign functions in the C language interoperability section are considered to be foreign functions modified by @C.

Modified by @C, the foreign keyword can only be used to modify non-general functions in the top-level scope. The foreign function is only a declaration and does not have a function body. Its parameters and return types cannot be omitted. Functions modified by foreign use the native C application binary interface (ABI) and do not have name modification.

foreign func foo(): Unit
foreign var a: Int32 = 0 // compiler error
foreign func bar(): Unit { // compiler error
    return
}

Multiple foreign function declarations can be grouped together using a foreign block. A foreign block is a sequence of declarations enclosed in curly braces after the foreign keyword. The foreign block can contain only functions. Adding annotations to the foreign block is equivalent to adding the annotation to each member in the foreign block.

foreign {
    func foo(): Unit
    func bar(): Unit
}

The foreign function must be able to link to the C function with the same name, and the parameters and return types must be the same. Only types that meet the CType constraint can be used for the parameters and return types of the foreign function. For the definition of CType, see "CType Interface."

The foreign function does not support named parameters or default parameter values. The foreign function allows variable-length parameters, which are expressed by ... and can be used only at the end of the parameter list. Variable-length parameters must meet the CType constraint, but they do not need to be of the same type.

CFunc

In Cangjie, CFunc refers to the function that can be called by C language code. There are three forms:

  1. foreign function modified by @C.
  2. Cangjie function modified by @C.
  3. lambda expression of the CFunc type.
    • Different from common lambda expressions, CFunc lambda cannot capture variables.
// Case 1
foreign func free(ptr: CPointer<Int8>): Unit

// Case 2
@C
func callableInC(ptr: CPointer<Int8>) {
    print("This function is defined in Cangjie.")
}

// Case 3
let f1: CFunc<(CPointer<Int8>) -> Unit> = { ptr =>
	print("This function is defined with CFunc lambda.")
}

The type of functions declared or defined in the preceding three forms is CFunc<(CPointer<Int8>) -> Unit>. CFunc corresponds to the function pointer type of the C language. This type is a generic type. Its generic parameter indicates the type of the CFunc input parameter and return value. The usage is as follows:

foreign func atexit(cb: CFunc<()->Unit>)

Similar to the foreign function, the parameters and return types of other CFunc functions must meet the CType constraint and do not support named parameters and default parameter values.

When CFunc is called in Cangjie code, it must be in the unsafe context.

Cangjie can convert a variable of the CPointer<T> type to a specific CFunc. The generic parameter T of CPointer can be any type that meets the CType constraint. The method is as follows:

main() {
	var ptr = CPointer<Int8>()
	var f = CFunc<() -> Unit>(ptr)
	unsafe { f() } // core dumped when running, because the pointer is nullptr.
}

The CFunc parameters and return types cannot depend on external generic parameters.

func call<T>(f: CFunc<(T) -> Unit>, x: T) where T <: CType { // error
    unsafe { f(x) }
}

func f<T>(x: T) where T <: CType {
    let g: CFunc<(T) -> T> = { x: T => x } // error
}

class A<T> where T <: CType {
    let x: CFunc<(T) -> Unit> // error
}

inout Parameter

When CFunc is called in Cangjie, the parameter can be modified by the inout keyword to form a reference value transfer expression. In this case, the parameter is transferred by reference. The type of the referenced value transfer expression is CPointer<T>, where T is the type of the expression modified by inout.

The value transfer expression by reference has the following restrictions:

  • It can only be used to call CFunc.
  • The type of the modifier object must meet the CType constraint, but cannot be CString.
  • The modifier object must be a variable defined by var.
  • The pointer transferred to the C side by using the value transfer expression on the Cangjie side is valid only during function calling. That is, in this scenario, the C side should not save the pointer for future use.

Variables modified by inout can be variables defined in top-level scope, local variables, and member variables in struct, but cannot be directly or indirectly derived from instances of class.

foreign func foo1(ptr: CPointer<Int32>): Unit

@C
func foo2(ptr: CPointer<Int32>): Unit {
    let n = unsafe { ptr.read() }
    println("*ptr = ${n}")
}

let foo3: CFunc<(CPointer<Int32>) -> Unit> = { ptr =>
    let n = unsafe { ptr.read() }
    println("*ptr = ${n}")
}

struct Data {
    var n: Int32 = 0
}

class A {
    var data = Data()
}

main() {
    var n: Int32 = 0
    unsafe {
        foo1(inout n)  // OK
        foo2(inout n)  // OK
        foo3(inout n)  // OK
    }
    var data = Data()
    var a = A()
    unsafe {
        foo1(inout data.n)   // OK
        foo1(inout a.data.n) // Error, n is derived indirectly from instance member variables of class A
    }
}

Calling Conventions

Function calling conventions describe how the caller and callee call functions (for example, how parameters are transferred and who clears the stack). The caller and callee must use the same calling conventions to run properly. Cangjie uses @CallingConv to indicate various calling conventions. The supported calling conventions are as follows:

  • CDECL: The default calling conventions used by the C compiler of Clang on different platforms.

  • STDCALL: The calling conventions used by the Win32 API.

If a C function is called using the C language interoperability mechanism, the default CDECL calling conventions is used when no calling convention is specified. The following is an example of calling the clock function in the C standard library:

@CallingConv[CDECL]   // Can be omitted in default.
foreign func clock(): Int32

main() {
	println(clock())
}

@CallingConv can only be used to modify the foreign block, a single foreign function, and a CFunc function in the top-level scope. When @CallingConv modifies the foreign block, the same @CallingConv modification is added to each function in the foreign block.

Type Mapping

When a foreign function is declared In Cangjie, the types of parameters and return values must be the same as those of the C function to be called. The Cangjie programming language type is different from the C language type. Some simple value types, such as the int32_t type of the C language, can correspond to Int32 of the Cangjie programming language. However, some complex types, such as structs, need to be declared with the same memory layout on the Cangjie side. The types that can be used by Cangjie to interact with C meet the CType constraint. They can be classified into basic types and complex types. Basic types include integer, floating-point, Bool, CPointer, and Unit. Complex types include the struct and CFunc types modified by @C.

Base Types

When parameters are transferred between Cangjie and C, the basic value types, such as int and short, are copied. The following table lists the mapping of basic types between Cangjie and C.

Cangjie TypeC typeSize
Unitvoid0
Boolbool1
Int8int8_t1
UInt8uint8_t1
Int16int16_t2
UInt16uint16_t2
Int32int32_t4
UInt32uint32_t4
Int64int64_t8
UInt64uint64_t8
IntNativessize_tplatform dependent
UIntNativesize_tplatform dependent
Float32float4
Float64double8

Note:

  1. Due to the uncertainty of the int and long types on different platforms, the corresponding Cangjie programming language type need to be specified.
  2. In C interoperability scenarios, the IntNative and UIntNative are the same as the ssize_t and size_t in the C language, respectively.
  3. In C interoperability scenarios, similar to the C language, the Unit type can only be used as the return type in CFunc and the generic parameter of CPointer.

In Cangjie, the types of the parameters and return values of the foreign function must correspond to those of the C function. For types that have clear type mappings and are irrelevant to the platform (see the mapping of basic types), you can directly use the corresponding basic types of the Cangjie programming language. For example, in the C language, an add function is declared as follows:

int64_t add(int64_t X,  int64_t Y) { return X+Y; }

Call the add function In Cangjie. The sample code is as follows:

foreign func add(x: Int64, y: Int64): Int64

main() {
	let x1: Int64 = 42
	let y1: Int64 = 42
	var ret1 = unsafe { add(x1, y1) }
	...
}

Pointer

Cangjie provides the CPointer<T> type that corresponds to the T* type of the C language. T must meet the CType constraint.

The CPointer type must meet the following requirements:

  • Its size and alignment depend on the platform.
  • The addition and subtraction operations and memory read and write operations need to be performed in an unsafe context.
  • The CPointer<T1> can be forcibly converted to the CPointer<T2> type in the unsafe context.

The CPointer has the following member methods:

func isNull() : bool

// operator overloading
unsafe operator func + (offset: int64) : CPointer<T>
unsafe operator func - (offset: int64) : CPointer<T>

// read and write access
unsafe func read() : T
unsafe func write(value: T) : Unit

// read and write with offset
unsafe func read(idx: int64) : T
unsafe func write(idx: int64, value: T) : Unit

The CPointer can use a type name to construct an instance. The value of the CPointer corresponds to the NULL in the C language.

func test() {
    let p = CPointer<Int64>()
    let r1 = p.isNull() // r1 == true
}

Cangjie can convert a variable of the CFunc type to a CPointer type. The generic parameter T of CPointer can be any type that meets the CType constraint. The method is as follows:

foreign func rand(): Int32
main() {
	var ptr = CPointer<Int8>(rand)
	0
}

Character String

In the C language, a character string is actually a one-dimensional character array terminated with '\0'. Cangjie provides CString to match the C language character string. For a C language character string created using the constructor of CString or mallocCString of LibC, to release it on the Cangjie end, call the free method of LibC.

When declaring a foreign function, you need to determine the function name, parameter types, and return value types based on the declaration of the C language function to be called. The declaration of the printf function of the C language standard library is as follows:

int printf(const char *format, ...)

The const char * type corresponds to the CString type of Cangjie. The return type Int32 corresponds to the int type of Cangjie. The following is an example of creating a character string and calling the printf function:

package demo

foreign func printf(fmt: CString, ...): Int32

main() {
    unsafe {
        let str: CString = LibC.mallocCString("hello world!\n")
        printf(str)
        LibC.free(str)
    }
}

Array Type

The Array type of Cangjie does not meet the CType constraint. Therefore, it cannot be used for the parameters and return values of the foreign function. However, when the internal element type of Array meets the CType constraint, Cangjie can use the following two functions to obtain and release the pointers pointing to the internal elements of the array.

unsafe func acquireArrayRawData<T>(arr: Array<T>): CPointerHandle<T> where T <: CType
unsafe func releaseArrayRawData<T>(h: CPointerHandle<T>): Unit where T <: CType

struct CPointerHandle<T> {
    let pointer: CPointer<T>
    let array: Array<T>
}

For example, to write an Array<UInt8> to a file, do as follows:

foreign func fwrite(buf: CPointer<UInt8>, size: UIntNative, count: UIntNative, stream: CPointer<Unit>): UIntNative

func writeFile(buffer: Array<UInt8>, file: CPointer<Unit>) {
    unsafe {
        let h = acquireArrayRawData(buffer)
        fwrite(h.pointer, 1, buffer.size, file)
        releaseArrayRawData(h)
    }
}

VArray Type

Cangjie uses the VArray type to map to the array type of C. When the element type T in VArray<T, $N> meets the CType constraint, the VArray<T, $N> type also meets the CType constraint.

struct A {} // A is not a CType.
let arr1: VArray<A, $2> // arr1 is not a CType.
let arr2: VArray<Int64, $2> // arr2 is a CType.

VArray can be used as the parameter type of the CFunc signature, but cannot be the return type.

If the parameter type in the CFunc signature is declared as VArray<T, $N>, the corresponding argument can only be an expression of the VArray<T, $N> type modified by inout. However, the parameter is still passed as CPointer<T>.

If the parameter type in the CFunc signature is declared as CPointer<T>, the corresponding argument can be an expression of the VArray<T, $N> type modified by inout. When the parameter is passed, the type is still CPointer<T>.

CPointer<VArray<T, $N>> is equivalent to CPointer<T>.

foreign func foo1(a: VArray<Int32, $3>): Unit
foreign func foo2(a: CPointer<Int32>): Unit

var a: VArray<Int32, $3> = [1, 2, 3]
unsafe {
    foo1(inout a) // Ok.
    foo2(inout a) // Ok.
}

Structure

If the signature of the foreign function contains the structure type, you need to define the struct with the same memory layout on the Cangjie side and use @C to modify it.

As shown in the following example, a C graphics library (libskialike.so) has a function distance for calculating the distance between two points. The related structure and function declaration in the C language header file are as follows:

struct Point2D {
    float x;
    float y;
};
float distance(struct Point2D start, struct Point2D end);

When declaring a foreign function, you need to determine the function name, parameter types, and return value types based on the declaration of the C language function to be called. When creating a structure on the C side, you need to determine the name and type of each member of the structure. The sample code is as follows:

package demo

@C
struct Point2D {
    var x: Float32
    var y: Float32
}

foreign func distance(start: Point2D, end: Point2D): Float32

The struct modified by @C must meet the following requirements:

  • The type of a member variable must meet the CType constraint.
  • interfaces cannot be implemented or extended.
  • To be used as an associated value type of enum is not allowed.
  • Closure capture is not allowed.
  • Generic parameters are not allowed.

The struct modified by @C automatically meets the CType constraint.

Ensure that the memory layout of the VArray member variables in @C struct is the same as that of the array in C.

For example, for the following C structure types:

struct S {
    int a[2];
    int b[0];
}

In Cangjie, the following structure can be declared to correspond to the C code:

@C
struct S {
    var a: VArray<Int32, $2> = VArray<Int32, $2>(item: 0)
    var b: VArray<Int32, $0> = VArray<Int32, $0>(item: 0)
}

Note that in the C language, the last field of a structure can be an array whose length is not specified. The array is called a flexible array. Cangjie does not support the mapping of structures that contain flexible arrays.

Function

The function type in Cangjie does not meet the CType constraint. Therefore, CFunc is provided as the mapping of the function pointer in the C language. The function pointer type defined in the following C language code can be mapped to CFunc<() -> Unit> in Cangjie.

// Function pointer in C.
typedef void (*FuncPtr) ();

For details about CFunc, see the preceding section on CFunc.

CType Interface

The CType interface is a built-in empty interface of the language. It is the specific implementation of the CType constraint. All types supporting interoperability with the C language implicitly implement this interface. Therefore, all types supporting interoperability with the C language can be used as subtypes of the CType type.

@C
struct Data {}

@C
func foo() {}

main() {
    var c: CType = Data() // ok
    c = 0 // ok
    c = true // ok
    c = CString(CPointer<UInt8>()) // ok
    c = CPointer<Int8>() // ok
    c = foo // ok
}

The CType interface is an interface type in Cangjie and does not meet the CType constraint. In addition, the CType interface cannot be inherited, explicitly implemented, or extended.

The CType interface does not break the usage restrictions of subtypes.

@C
struct A {} // implicit implement CType

class B <: CType {} // error

class C {} // error

extend C <: CType {} // error

class D<T> where T <: CType {}

main() {
    var d0 = D<Int8>() // ok
    var d1 = D<A>() // ok
}

Java Interoperability

Interoperability Modes

Cangjie Calling Java

In Cangjie, you can directly use the import statement to import the top-level types of Java packages, including classes and interfaces (such as enumerations and annotations). Only Java types, member variables, or methods that are visible outside the package (public or protected) can be used in Cangjie.

When a Java type is being imported, the following syntaxes are supported.

  • Use import packageName.* to import all types in the Java package.
  • Use import packageName.name to import a specific type in the Java package.
  • Use import packageName.name as newName to rename the file to avoid conflicts or simplify the writing.

The contents of the Java standard library are obtained from the java8 module in Cangjie. Import java.lang.Math to Cangjie. The sample code is as follows:

// Cangjie code
from java8 import java.lang.Math

main(){
    Math.sin(Math.PI/2.0)  // invoke static method and access static field
    0
}

The imported Java classes and interfaces support the following usages in Cangjie.

  • Create class instances, access class member variables, and call class methods.
  • Inherit the class or implement the interface through <: (the @Java annotation is required).

When Cangjie calls Java code, the called Java code is executed on a Java thread. There is a one-to-one correspondence between Cangjie thread and Java thread, and the correspondence does not change during running of the Cangjie program. If Java interoperability cannot be performed in the current execution environment, the exception JavaNotSupportedException is thrown when Java code is executed for the first time.

The imported Java classes and interfaces must comply with certain mapping rules (such as type and modifier mapping) and usage restrictions. For details, see the following sections.

Java Calling Cangjie

In Java, the import statement can be used to import the classes and interface types modified by @Java on the Cangjie side. (For details about how to use @Java, see [@Java Classes and Interfaces].)

Assume that a Cangjie class is defined as follows:

// Cangjie code
package cangjie.example

import std.ffi.java.*

@Java
public class CJClass {
    public func foo(str: JString): Unit {
        // do something
    }
}

To import the Cangjie class to Java using the import statement, do as follows:

// Java code
package com.company;

import cangjie.example.CJClass;

public class Main {
    public static void main(String[] args) {
        CJClass obj = new CJClass();
        obj.foo("this is a java string");
    }
}

Java also supports access to classes and interface types modified by @Java on the Cangjie side through reflection (Class.forName or ClassLoader.loadClass). The preceding Cangjie class is used in the following example of using the reflection mechanism in Java:

// Java code
package com.company;

import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        Class<?> clz = Class.forName("cangjie.example.CJClass"); // Get the class through forName
        Method method = clz.getMethod("foo", String.class);   // Get the method
        method.invoke(clz.newInstance(), "this is a string"); // Create instance and invoke the method
    }
}

Type Mapping

The types of member variables, method parameters, and return values in imported Java classes or interfaces must follow specific type mapping rules when used in Cangjie. The mapping types are as follows:

  • Types that are similar between Java and Cangjie, with minimal differences, for example, the basic type byte in Java.

    For details about these Java types, see the mapping table.

  • Types that exist in both Java and Cangjie, but with significant semantic differences, such as Java character strings and arrays, which are greatly different from the built-in character strings and arrays of Cangjie.

    These Java types are supported by providing additional built-in types, such as ffi.java.JString and ffi.java.JArray.

  • Types that exist in Java but not in Cangjie, which may be supported by some built-in special usages.

  • Types that are unique to Cangjie, such as tuple and struct. These type cannot be used as member variables, method parameters, or return value types of classes modified by @Java, but can be used in methods of those classes.

For details about the mapping rules and restrictions of each type and the usage of common classes, interfaces, and generic types on the Java side, see the following sections.

Java Basic Types and Their Wrapper Classes

When Java basic types are used as class member variables, method parameters, and return value types, they can be used based on the types in the mapping table. The following table describes the mapping.

JavaCangjie
byteInt8
shortInt16
charUInt16
intInt32
longInt64
floatFloat32
doubleFloat64
booleanBool

The Cangjie numeric types that are not supported by Java cannot be used as the member variables, method parameters, and return value types of the class modified by @Java. These types include UInt8, UInt32, UInt64, and Float16.

The wrapper type corresponding to each basic type in Java is regarded as a common Java class in Cangjie and does not provide additional mapping and automatic packing and unpacking operations. When using Java code whose input parameter or return value type is wrapper in Cangjie, you need to import the corresponding wrapper class and manually create an instance.

The sample code is as follows:

// Java code
package com.example;
public class BoxedExample {
    // foo receive the packing type of the basic type.
    public void foo(Integer i){
        System.out.println(i.toString());
    }
}

Import BoxedExample to Cangjie and create an instance to call the foo function. The code is as follows:

import com.example.*
from java8 import java.lang.Integer;

main(){
    var instance = BoxedExample()
    instance.foo(1)  // compile error
    var i = Integer(1)
    instance.foo(i)
}

@Java Classes and Interfaces

@Java can be used to modify class and interface. By default, the types modified by @Java are the subtypes of java.lang.Object. Only types modified by @Java can be used in Java. These types can also be used in classes or interfaces that are not modified by @Java.

Note that types modified by @Java have the following restrictions:

  • For types modified by @Java, the types of its member variables, input parameters of member functions, and return values cannot contain types that are unique to Cangjie. Types that are unique to Cangjie include Tuple, Array, enum, struct, function type (except CFunc), Range, String, Nothing, Rune, UInt8, UInt32, UInt64, Float16, and Unit. (Unit can be used as the return value type. For details, see [Java void].)

  • The class or interface modified by @Java can inherit and implement the following:

    • Imported java classes and interfaces
    • Other types modified by @Java
    • interface type of Cangjie
open class A{}

@Java
class B <: A {} // compile error, A is neither modified by @Java nor an imported java type.

If no superclass is specified for a class modified by @Java, the superclass is java.lang.Object. The instance of the class modified by @Java can directly call the java.lang.Object member method. The sample code is as follows:

@Java
class Foo{}
main(){
    var instance = Foo()
    instance.hashCode() // Call java.lang.Object method
}

In Java, class loaders are responsible for dynamically loading Java classes into VMs. By default, the class modified by @Java is loaded by Java Application Classloader, which is equivalent to @Java["app"]. If @Java["ext"] is used, the class will be loaded by the custom Classloader.

The usage of @Java["ext"] and @Java["app"] is the same. The only difference is that classes modified by @Java["ext"] can be referenced only by other classes modified by @Java["ext"]. The function in the class can reference other Cangjie code normally. Classes modified by @Java["ext"] and @Java["app"] can be accessed through reflection in Java.

Java Object

In Cangjie, Object is the superclass of java.lang.Object of Java.

Java void

When a Java method is called in Cangjie, the return value of void is of the Unit type. The value of Unit is allocated by the Cangjie calling side. If a Cangjie class inherits the imported Java class and rewrites the method of the Java class (the method returns void), the Unit type is not created when the rewritten method is called and executed on the Java side.

Unit cannot be used in the member variables and member function parameters of the type modified by @Java except that it is used as the return type of a function.

Note: If the value of Unit is allocated on the calling side, var a: Unit = JObject.callJMethod() is equivalent to JObject.callJMethod(); var a: Unit = ().

@Java
class UnitExample <: SomeJClass {
   overrride func foo():Unit{
       // other code
        return ()       // When the foo function is called on the Java side,
    }                   // the Unit will not be created.
}

main(){
    var instance = UnitExample()
    var a = instance.foo() // Same as instance.foo(); var a: Unit = ()
}

Java String

Cangjie provides the ffi.java.JString type mapping java.lang.String. This type has all methods of java.lang.String. The two types are the same during running. In the imported Java type, if the member variables and input parameters and return value types of member methods are java.lang.String, they are mapped to ffi.java.JString. The native String type of Cangjie cannot be used as the type of member variables, member method input parameter or return value of the class modified by @Java.

The ffi.java.JString literal supports only single-line character literals. It is defined by double quotation marks starting with an uppercase letter J (for example, J"singleLineStringLiteral"). The content in the double quotation marks can be any number of characters and can be written in only one line.

// Cangjie code
import std.ffi.java.JString

@Java
class JStrExample {
    var a:String  = "Cangjie string" // compiler error
    var b:JString = J"JString" // Single line literal
}

main(){
    var obj = Foo()
    var jstr: JString = J"Java String"
    jstr.equal(J"Java String")   // Call the equal method to determine whether the string content is equal
}

In addition to all methods of java.lang.String, ffi.java.JString also supports judgment (== and !=) and concatenation (+).

Cangjie provides the conversion between the native String type and ffi.java.JString type. The conversion functions are as follows:

public func toJString(str: String) : JString
public func toString(jstr: JString) : String

Java Array

Cangjie provides the ffi.java.JArray<T> type to map the built-in array type of Java. The ffi.java.JArray<T> type is a generic type of Cangjie. The type variable T must be a type that can be mapped to Java, including basic types (Int8, Int16, UInt16, Int32, Int64, Float32, Float64, and Bool), type modified by @Java, JString type, and JArray<T> type. All the preceding types implement the JType interface, and T has the constraint where T <: JType.

You can create ffi.java.JArray<T> in either of the following ways:

  • Use the Array<T> extension function toJArray.

    let a: JArray<Int64> = [1, 2, 3, 3, 2, 1].toJArray()
    
  • Use ffi.java.JArray<T>() or ffi.java.JArray<T>(arrayExpr: Array<T>).

    let a: JArray<Int64> = JArray<Int64>([0, 1, 2, 3, 4, 5])
    

Similar to Array, for the arr instance of the ffi.java.JArray<T> type, the array length is n. The arr[i] method can be used to access the element in a specific location. The i is the Int32 type from 0 to the array length n-1.

Note: The type of the i index of JArray is Int32, which is consistent with the int index of Java.

Members of the ffi.java.JArray<T> type are the same as those of the Java built-in array type, including the length variable, clone method, and other methods inherited from java.lang.Object.

In Java, the direct supertype of Integer[] is Number[]. However, when Integer[] is used in Cangjie, ffi.java.JArray<Integer> and ffi.java.JArray<Number> have no supertype-subtype relationship. For details, see [Generics].

Java Enum

The enumeration type of Java is a special class. In Cangjie, the enumeration type of Java is directly mapped to the subclass of java.lang.Enum. The use of this class complies with the same mapping rules and restrictions as those of common Java classes.

The enumeration class Day on the Java side is defined as follows:

// Java code
package com.example;

public enum Day {
    MONDAY, TUESDAY, WEDNESDAY,
    THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

The classes mapped by the Day enumeration type on the Cangjie side have the following features:

  • By default, this class inherits the Java.lang.Enum<E> abstract class, where E is a generic type variable. For example, if the enumeration class Day is used, E is the Day class.

  • All enumeration members are of the Day type and have public and static member variables. (open corresponds to final on the Java side, therefore, it is not included.)

  • By default, this class implements the following member functions with public and static:

    • public static func values(): JArray<E>

    The values function returns all constant values in the enumeration as an array.

    • public static func valueOf(name: JString): E

    The valueOf function returns enumeration constants with specified names. (These constants are initialized as MONDAY = new Day("MONDAY", 0) in Java.)

Import the Day enumeration class to Cangjie. The example is as follows:

// Cangjie code
import com.example.*

main(){
    var day = Day.MONDAY
    var values = Day.values()
    day = values[0]             // day's value is Day.MONDAY, values
    day = Day.valueOf("MONDAY") // day's value is Day.MONDAY
}

Java lambda

The type of the lambda expression on the Java side is a specific functional interface type. A functional interface in Java refers to an interface that has only one abstract method (except the Object method). In Java, you can create an instance of the functional interface by declaring and instantiating the class and lambda expression.

In Cangjie, you can directly import the functional interface on the Java side. You can also declare an interface with only one abstract method and add the @Java modifier to use the interface as the functional interface on the Java side. In Cangjie, instances of functional interfaces can be created only by class instantiation. The class must be modified by @Java and the corresponding functional interface must be implemented.

In Cangjie, when calling a method whose input parameter is of the java lambda type, you need to pass the corresponding function interface instance.

// Java code
package com.example;

public interface MathOperation {
    int operation(int a, int b);
}
public class LambdaExample{
    public MathOperation add = (int a, int b) -> a + b // The Java side can be created directly using a lambda expression.
    public int foo(int a, int b, MathOperation op){
        return op.operation(a, b);
    }
}

The sample code for accessing add and foo in Cangjie is as follows:

// Cangjie code
import com.example.*

@Java
class Sub <: MathOperation {
    func operation(a: Int32,b: Int32): Int32{
        return a - b
    }
}
main(){
    var instance = LambdaExample()
    var add = instance.add          // Direct access to Java-side functional interface type properties.
    instance.foo(1,2,add)           // Transfer the instance obtained from the Java side.
    var sub = Sub()                 // Create an instance.
    instance.foo(2,1,sub)           // Result is 1.
}

Note: The native lambda type in Cangjie does not directly map to lambda in Java.

Java Variable-length Arguments (varargs)

In Cangjie, the varargs of Java are mapped to the ffi.java.JArray<T> array type of the corresponding T type. When calling the vararrgs, you need to manually create the corresponding ffi.java.JArray<T> array instance and pass it.

When calling, the code for creating the parameter array and passing it is as follows:

// Java code
package com.example;
public class JavaVarargsExample{
    public void foo(int... ele){
        // some code
    }
}

The code for calling in Cangjie is as follows:

// Cangjie code
import com.example.*
import std.ffi.java

main(){
    var varargsExample = JavaVarargsExample()
    var array64: Array<Int64> = [0, 1, 2, 3, 4, 5]
    var array32: Array<Int32> = [0, 1, 2, 3, 4, 5]
    var argsArray = ffi.java.JArray<Int32>([0, 1, 2, 3, 4, 5])
    varargsExample.foo(argsArray) // The compiler needs to pass this type as a variable type.
}

Java Modifiers

When the imported Java classes, interfaces, and their members are used in Cangjie, their modifiers are mapped according to the rules in the following table. For example, when a class member modified by protected on the Java side is imported to Cangjie, the class member can be accessed only in its subclasses.

Modifier kindJavaCangjie
class/interface/method/fieldpublicpublic
method/fieldprotectedprotected
classabstractabstract (class)
method/fieldstaticstatic
class/methodfinal/(non final)(non open)/open
fieldfinal/(non final)let/var
method@Overrideoverride

Note: The strictfp modifier is not supported currently. By default, the precision of the floating-point result in Java depends on the hardware. Using the strictfp to modify classes, interfaces or methods can ensure platform-independent floating-point computation.


Java can use transient to declare member variables of a class. These member variables will be ignored in the persistence (serialization) operation. In Cangjie, a member variable cannot be explicitly marked as transient. However, fields inherited from Java that are marked as transient will retain their original features in Cangjie. For example, when the serialization method on the Java side is called in Cangjie, the member variables are ignored in the same way as they are in Java. Similarly, the original semantics of the synchronized and native methods on the Java side are retained when the methods are called in Cangjie. However, Cangjie does not support explicit declaration of a member method as synchronized or native.

Note that Cangjie does not support the access to volatile member variables. An error will be reported during compilation for the access to such variables. To access such variables, the corresponding operation method must be encapsulated on the Java side for use in Cangjie.

Nullable Types

For any Java type that may be null, you can use it in Cangjie in either of the following ways:

  • Directly use it as non-empty types in Cangjie.

    When the nullable variable obtained from Java is stored in any non-nullable variable, if the value is null, JavaException is thrown during running. This exception encapsulates the underlying NullPointerException that occurred.

    For a reference variable whose value is null, JavaException is thrown during running when you directly access its members or calls its member methods. The nullable variables obtained from Java refer to imported member variables or method parameters of the Java type.

  • Store it in a variable of the Option type.

    The compiler automatically encapsulates the value into the Option type. If the value is null during running, the value is mapped to None of the Option type. Otherwise, the value is mapped to Some constructor.

// Java code
package com.example;

import java.lang.Integer;

public class JavaClass {
    public Integer a = null;

    public Integer foo(Integer a) {
        Integer b = 100 + a;
        return b;
    }
}
// Cangjie code
import com.example.JavaClass
from java8 import java.lang.Integer

main(){
    var obj = JavaClass()

    obj.a.toString()            // The value of obj.a is null at runtime. The `JavaException` will be thrown at runtime.

    var a1: Integer = obj.a     // The value of obj.a is null at runtime. The null value is assigned to a non-Option variable. In this case, the `JavaException` will be thrown.

    var a2: ?Integer = obj.a    // The compiler automatically encapsulates obj.a as an optional type. The actual value of a2 is Option<Integer>.None.
    a2?.toString()              // a2 = Option<Integer>.None, the `toString()` method will not be invoked.

    var a3: ?Integer = Option<Integer>.None
    obj.foo(a3.getOrThrow())      // `a3.getOrThrow()` will throw an `NoneValueException` exception.

    var ret: ?Integer = obj.foo(obj.a)  // The value of obj.a is null at runtime, `JavaException` will be thrown at runtime.
}

Exceptions

When Java code is called on the Cangjie side, all exceptions thrown by Java are mapped to the Cangjie class JavaException and can be captured by Cangjie. Similarly, when the Cangjie code is executed on the Java side, all exception classes thrown by the Cangjie side are mapped to the Java class CangjieException, which can be captured on the Java side. The following table lists their mapping relationships.

Cangjie TypeJava Type
Exception or Error and their subclassesCangjieException
JavaExceptionThrowable and its subclasses

The try/catch statement can be used to capture mapped exceptions on both the Cangjie and Java sides. When an exception thrown by Java is captured on the Cangjie side, the getCause method can be used to obtain the exception class thrown by the exception and then determine and process the exception. See the following example:

// Cangjie Code
from java8 import java.lang.Integer
from java8 import java.lang.Throwable
from java8 import java.lang.NumberFormatException
from java8 import java.lang.ArrayIndexOutOfBoundsException

main() {
    try {
    	var s = J"abc"
    	var n = Integer.parseInt(s) // It will throw exception.
        let array = JArray<Int64>([0, 1, 2, 3, 4, 5])
        var el = array[n]
    } catch (e: JavaException) {
        var cause: Throwable = e.getCause()
        match (cause) {
            case _: NumberFormatException => print("Wrong parseInt input!")
            case _: ArrayIndexOutOfBoundsException => print("Out of Bounds!")
            case _ => ()
        }
    }
}

Generics

In Cangjie, you can directly import Java generic types to create instances, call methods, inherit generic classes, and implement generic interfaces. The imported Java generic types and generic types modified by @Java are erased during compilation (consistent with Java).

The sample code for calling a generic type in Cangjie is as follows:

// Cangjie code
from java8 import java.util.ArrayList as JArrayList
from java8 import java.lang.Integer

main(){
    var arrayList = JArrayList<Integer>()
    arrayList.add(Integer(1))
    arrayList.add(Integer(3))
    arrayList.add(Integer(5))
    var iter = arrayList.iterator()
    while (iter.hasNext()) {
        iter.next().toString()
    }
}

Most Java generics can be directly used in Cangjie, but must comply with certain mapping rules and usage restrictions. For details, see the following sections.

Constraints on Java Parameters

In Java, the upper bound of a generic parameter can be specified only by extends (the lower bound cannot be specified). There can be multiple upper bounds. The first upper bound must be a class, and the subsequent upper bounds must be interfaces. The syntax of the type parameter constraint is as follows, where T indicates the type parameter, C1 indicates the class name, and I1, I2, ..., and In indicate the interface name. The upper bounds are connected by the & symbol.

T extends C1 & I1 & I2 & ... &In
T extends C1
T extends I1

The preceding syntax can be directly mapped to the following syntax:

where T <: C1 & I1 & I2 & ... &In
where T <: C1
where T <: I1

Note that the type parameters of the generic classes and interfaces modified by @Java implicitly contain the constraint where T <: java.lang.Object (except T in ffi.java.JArray<T>, which may also be the basic type that meets the JType constraint).

Wildcards in Java

In Java, wildcards are used to represent a series of types and can be used as type parameters (not used in generic methods). There are three types of wildcards: unbounded, upper bound, and lower bound.

In Java, wildcards can be used as type parameters when non-wildcard type constraints are too strict. A compilation error occurs when the sort method is called in the following code:

// Java code
class Person implements Comparable<Person> {}
class Student extends Person {}
class Utilities {
  public static <T extends Comparable<T>> void sort(List<T> list) {}
}
List<Student> list = new ArrayList<Student>();
Utilities.sort(list);       // error

If wildcards are used as type parameters, sort can be called properly as follows:

// Java code
class Utilities {
  public static <T extends Comparable <? super T > > void sort(List<T> list) {
  }
}

In Cangjie, the wildcard type is not supported, but the mapping can be performed based on certain rules. All wildcard types that need to be mapped are as follows:

  • Imported member variables, method input parameters, and method return values of the Java type.
  • The Cangjie type modified by @Java inherits or overrides the input parameters and return values of the method of the Java type.

The mapping rules are as follows:

JavaCangjie
List<? super T>List<java.lang.Object>
List<? extends T>List<T>
List<?>List<java.lang.Object>

Generic Instance Member Functions

In Cangjie, when inheriting or implementing the imported Java type, you can use the generic member function to override superclass member function. Note that:

  • The syntax requirements of generic member functions are the same as those of global generic member functions.

  • The class or interface where the generic member function is located must be modified by @Java.

  • Generic member functions must be modified by override.

    Note that the return type must be the same as the return type of the overridden method or the subtype of the return type.

The sample code is as follows:

// Cangjie code
package example
import java.GenericClassExample // Importing Java Generic Types.

@Java
class B <: GenericClassExample {
    override public foo<T>(t:T) {  // Overrides super class member functions only.
       // do something
    }
}
main(){
    var p = B()
    p.foo(p)
}

In Cangjie, a subclass member cannot shadow a superclass member, but Java allows. When the two Java classes that constitute shadowing are used in Cangjie, the access to the shadowed variables at run-time are the same as that on the Java side. If an imported or @Java-modified type is used as the upper bound of a generic constraint in a pure Cangjie generic function or generic class, do not use the Java subtype that constitute shadowing with this type as the type argument of the generic function or generic class. This restriction does not apply to other Java subtypes that do not constitute shadowing with this type.

Assume that the following Java class exists:

// Java code
package com.example;

public class Parent {
    public int data = 1;
}

public class Child extends Parent {
    public String data = "child"; // shadowing with super class
}

public class House extends Parent {
    public int price = 100; // no shadowing
}

The import and usage in Cangjie is as follows:

// Cangjie code
import com.example.*
import std.ffi.java.*

func f<T>(t: T) where T <: Parent {
    t.data // The t.data type is Int32 during compilation.
}

class A<T> where T <: Parent {
    public func method(t: T) {
        t.data // The t.data type is Int32 during compilation.
    }
}

main() {
    let p = Parent()
    let c = Child()
    let h = House()

    f<Parent>(p)  // ok, return type is Int32
    f<Parent>(c)  // ok, return type is Int32
    f<Child>(c) // compile error
    f<House>(h) // ok, return type is Int32

    let obj1 = A<Parent>() // ok
    obj1.method(p) // ok, return type is Int32
    obj1.method(c) // ok, return type is Int32
    obj1.method(h) // ok, return type is Int32

    let obj2 = A<Child>() // compile error

    let obj3 = A<House>() // ok
    obj3.method(h) // ok, return type is Int32
}

Java Raw Type

The Raw type on the Java side is mapped to the generic type of Cangjie according to the following table. Note that if the type of a member variable in a Java type, the parameter type of a member method, or the return value type uses the Raw type that contains generic constraints with multiple upper bounds, Cangjie does not support access to its member variables or member methods.

Java Generic TypeJava Raw TypeCangjie
C0<T>C0C0<java.lang.Object>
C1<T extends Bound1>C1C1<Bound1>
C2<T extends Bound1 & Bound2 ...>C2Not support

The sample code is as follows:

// Java Code
package com.example;

// no upper bound.
public class C0<T> { ... }

// has one upper bound.
public class C1<T extends Number> { ... }

// has more then one upper bounds.
public class C2<T extends Number & Clonable> { ... }

public class RawTypeExample {
    public C0 a;  // C0 is a raw type
    public C1 b;  // C1 is a raw type
    public C2 c;  // C2 is a raw type

    public C2 createC2() {...}
}

The sample code of its usage in Cangjie is as follows:

// Cangjie code
import com.example.*
from java8 import java.lang.Object as JObject
from java8 import java.lang.Number as JNumber

main() {
    var obj = RawTypeExample()
    let a: ?C0<JObject> = obj.a // ok
    let b: ?C1<JNumber> = obj.b // ok
    let c = obj.c               // compile error

    let d = obj.createC2()      // compile error
}

Java Annotations

Java annotation is a special interface type. Some user-defined annotation types on the Java side are mapped to the Cangjie annotation type. The annotation types can be imported and used on the Cangjie side, but can be used only in the types modified by @Java.

The sample code in which an annotation type is defined in Java is as follows:

// Java code
package com.example;
import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface JavaAnno {
    String value();
}

Import the preceding annotations to Cangjie and use them. The sample code is as follows:

// Cangjie code
import com.example.*

// pure cangjie class
class CangjieClass {
    @JavaAnno[J"value"] // compile error, annotation types from java can only be used in @Java-modified types.
    func a(): Unit{}
}

@Java
class JavaClass {
    @JavaAnno[J"value"] // OK
    func a():Unit {}
}

Java built-in annotation types cannot be imported. Java 8 built-in annotation types include @Override, @Deprecated, @SuppressWarnings, @Retention, @Documented, @Target, @Inherited, @SafeVarargs, @FunctionalInterface, @Repeatable, and @Native.

The annotation type whose @Retention attribute value is RetentionPolicy.SOURCE in Java cannot be imported.

When the imported Java annotation type is used on the Cangjie side, the usage position restrictions of the Cangjie annotation must be met. For details, see the description of the target parameter in [Annotations]. Java uses the @Target annotation (the received parameter is a ElementType[]) to specify the positions where the modified annotation types can be used. The function of the @Target annotation is the same as that of the target parameter in the Cangjie annotation (the received parameter is an Array<AnnotationKind>). For an imported Java annotation type:

  • If the @Target annotation is used to restrict its usage location, the @Target attribute value is mapped to the target parameter value of the Cangjie annotation according to the following table.
  • If the @Target annotation is not used, the target parameter value of the Cangjie annotation is mapped to [Type, Parameter, Init, MemberFunction, MemberVariable].
Java ElementTypeCangjie AnnotationKind
TYPEType
FIELDMemberVariable
METHODMemberFunction
PARAMETERParameter
CONSTRUCTORInit
LOCAL_VARIABLE-
ANNOTATION_TYPE-
PACKAGE-
TYPE_PARAMETER-
TYPE_USE-

Unsupported Scenarios

  • Java identifiers support the Unicode character set, but Cangjie identifiers do not. Cangjie does not support the import of Java packages, types, member methods, or member variables that do not comply with the Cangjie identifier naming rules.
ComparisonJava identifierCangjie identifier
Starting CharacterAny Unicode character that is a "Java letter", includes uppercase and lowercase ASCII Latin letters A-Z (\u0041-\u005a), and a-z (\u0061-\u007a), the ASCII underscore (_,or \u005f) and dollar sign ($, or \u0024), and other Unicode characters(such as Chinese, Japanese, and Korean, etc.)Uppercase and lowercase ASCII Latin letters A-Z and a-z, or ASCII underscore (_). Digits 0-9 (\u0030-\u0039) are not allowed.
Following CharacterAny Unicode character that is a "Java letter-or-digit".Uppercase and lowercase ASCII Latin letters A-Z and a-z, ASCII underscore (_), ASCII digits 0-9. NOTE: If the identifier begins with one or more underscores, the first subsequent character cannot be a digit.

Examples of identifiers are as follows:

IdentifierValid in Java?Valid in Cangjie?
中文YesNo
0中文NoNo
0aNoNo
a中文YesNo
_中文YesNo
_aYesYes
_0YesNo
$中文YesNo
$aYesNo
$0YesNo
\u0061Yes ('\u0061' == 'a')No
\u0030No ('\u0030' == '0')No
  • Java supports nested types (including inner classes, nested interfaces, and nested enumerations), but Cangjie does not. Currently, Cangjie does not support the import of nested types on the Java side. If a Java member variable, or the input parameter or return value type of a member method uses the Java nested type, Cangjie cannot access the corresponding Java member variable or member method.

In the following Java code, the OuterClass class can be imported to Cangjie, but the nested types defined in the OuterClass class cannot be imported, and the value member variables and foo member methods cannot be accessed.

// Java code
public class OuterClass {
    public class InnerClass {
        // ...
    }
    public static class StaticInnerClass {
        // ...
    }
    public interface InnerInterface {
        // ...
    }
    public enum InnerEnum {
        // ...
    }

    public StaticInnerClass value;

    public InnerEnum foo(InnerInterface arg) {
        // ...
    }
}
  • Java allows member variables to have the same name as member methods, but Cangjie does not. Cangjie can access only the member methods with the same name, but cannot access the corresponding member variables.

The following Java code is used as an example:

// Java code
package com.company;

public class Example {
    public int foo = 10;
    public int foo() {  // method has the same name as field
    }
}

After it is imported to Cangjie, only the foo member method can be accessed. The foo member variable cannot be accessed. The following is an example:

// Cangjie code
import com.company.*

main() {
    let obj = Example()
    let a: Int32 = obj.foo // compile error, type mismatch
    obj.foo() // ok
    let f: () -> Int32 = obj.foo // ok
}
  • Java supports generic constructors, but Cangjie does not. Cangjie does not support calling Java generic constructors.

In the following Java code, Cangjie cannot call the following Java generic constructors:

// Java code
package com.company;

public class Example {
    public <T> Example(T a) {
        // ...
    }
}

public class Example2<T> {
    public <E> Example2(E a) {
        // ...
    }
}