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:
- Modifying a code block. The type of the
unsafe
expression is the type of the code block. - 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:
foreign
function modified by@C
.- Cangjie function modified by
@C
. lambda
expression of theCFunc
type.- Different from common lambda expressions,
CFunc lambda
cannot capture variables.
- Different from common lambda expressions,
// 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 beCString
. - 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 Type | C type | Size |
---|---|---|
Unit | void | 0 |
Bool | bool | 1 |
Int8 | int8_t | 1 |
UInt8 | uint8_t | 1 |
Int16 | int16_t | 2 |
UInt16 | uint16_t | 2 |
Int32 | int32_t | 4 |
UInt32 | uint32_t | 4 |
Int64 | int64_t | 8 |
UInt64 | uint64_t | 8 |
IntNative | ssize_t | platform dependent |
UIntNative | size_t | platform dependent |
Float32 | float | 4 |
Float64 | double | 8 |
Note:
- Due to the uncertainty of the
int
andlong
types on different platforms, the corresponding Cangjie programming language type need to be specified. - In C interoperability scenarios, the
IntNative
andUIntNative
are the same as thessize_t
andsize_t
in the C language, respectively. - In C interoperability scenarios, similar to the C language, the
Unit
type can only be used as the return type inCFunc
and the generic parameter ofCPointer
.
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 theCPointer<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 theJava
package. - Use
import packageName.name
to import a specific type in theJava
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 typebyte
inJava
.For details about these
Java
types, see the mapping table. -
Types that exist in both
Java
and Cangjie, but with significant semantic differences, such asJava
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 asffi.java.JString
andffi.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
andstruct
. 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.
Java | Cangjie |
---|---|
byte | Int8 |
short | Int16 |
char | UInt16 |
int | Int32 |
long | Int64 |
float | Float32 |
double | Float64 |
boolean | Bool |
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 includeTuple
,Array
,enum
,struct
, function type (except CFunc),Range
,String
,Nothing
,Rune
,UInt8
,UInt32
,UInt64
,Float16
, andUnit
. (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
- Imported
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 toJObject.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 functiontoJArray
.let a: JArray<Int64> = [1, 2, 3, 3, 2, 1].toJArray()
-
Use
ffi.java.JArray<T>()
orffi.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 ofJArray
isInt32
, which is consistent with theint
index ofJava
.
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, whereE
is a generic type variable. For example, if the enumeration classDay
is used,E
is theDay
class. -
All enumeration members are of the
Day
type and havepublic
andstatic
member variables. (open
corresponds tofinal
on theJava
side, therefore, it is not included.) -
By default, this class implements the following member functions with
public
andstatic
: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 asMONDAY = new Day("MONDAY", 0)
inJava
.)
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 tolambda
inJava
.
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 kind | Java | Cangjie |
---|---|---|
class/interface/method/field | public | public |
method/field | protected | protected |
class | abstract | abstract (class) |
method/field | static | static |
class/method | final/(non final) | (non open)/open |
field | final/(non final) | let/var |
method | @Override | override |
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 isnull
,JavaException
is thrown during running. This exception encapsulates the underlyingNullPointerException
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 fromJava
refer to imported member variables or method parameters of theJava
type. -
Store it in a variable of the
Option
type.The compiler automatically encapsulates the value into the
Option
type. If the value isnull
during running, the value is mapped toNone
of theOption
type. Otherwise, the value is mapped toSome 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 Type | Java Type |
---|---|
Exception or Error and their subclasses | CangjieException |
JavaException | Throwable 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 theJava
type.
The mapping rules are as follows:
Java | Cangjie |
---|---|
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 Type | Java Raw Type | Cangjie |
---|---|---|
C0<T> | C0 | C0<java.lang.Object> |
C1<T extends Bound1> | C1 | C1<Bound1> |
C2<T extends Bound1 & Bound2 ...> | C2 | Not 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 thetarget
parameter value of the Cangjie annotation according to the following table. - If the
@Target
annotation is not used, thetarget
parameter value of the Cangjie annotation is mapped to[Type, Parameter, Init, MemberFunction, MemberVariable]
.
Java ElementType | Cangjie AnnotationKind |
---|---|
TYPE | Type |
FIELD | MemberVariable |
METHOD | MemberFunction |
PARAMETER | Parameter |
CONSTRUCTOR | Init |
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.
Comparison | Java identifier | Cangjie identifier |
---|---|---|
Starting Character | Any 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 Character | Any 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:
Identifier | Valid in Java? | Valid in Cangjie? |
---|---|---|
中文 | Yes | No |
0中文 | No | No |
0a | No | No |
a中文 | Yes | No |
_中文 | Yes | No |
_a | Yes | Yes |
_0 | Yes | No |
$中文 | Yes | No |
$a | Yes | No |
$0 | Yes | No |
\u0061 | Yes ('\u0061' == 'a') | No |
\u0030 | No ('\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) {
// ...
}
}