Взаимодействие с JVM и Java-средой (libretto/jvm)
Libretto
Язык программирования Libretto не является языком для Java-среды. У него совсем иная система типов, которая напрямую в JVM не отображается. Поэтому Java-окружение рассматривается лишь как удобная платформа для запуска программ Libretto.
Но взаимодействие с JVM и Java-средой (библиотеками и т.п.) необходимо, поэтому была реализована библиотека libretto/jvm, которая это обеспечивает, пусть и несколько громоздким способом (но это не влияет на эффективность).
libretto/jvm
Для взаимодействия с JVM используются трейты специального вида и методы библиотеки libretto/jvm. При компиляции работа с трейтами и методами превращается в байт-код, напрямую работающими с классами/интерфейами JVM. Есть статическая проверка взаимодействия на стадии компиляции.
JVM-трейты
Для связи с JVM классами, интерфейсами и массивами используются трейты, но специального вида. JVM-трейт определяется в любом пакете, имя трейта значения не имеет, но у него не должно быть методов (должно быть пустое тело).
trait Test
Далее нужно задать JVM-тип, в который будет отображаться этот трейт. Для этого в том же пакете определяется метод jvmType с этим трейтом в контексте. Телом метода должен быть только строковый литерал, ничего другого не допускается (это проверяется во время компиляции). Строковый литерал содержит имя JVM-типа, который будет использован на уровне байт-кода при обращении к указанному JVM-трейту на уровне программы на Libretto.
В качестве разделителей пакетов в JVM-типах используется точка (не / как в Libretto).
def Test jvmType = "java.util.List"
В этом примере трейт Test будет соответствовать JVM-типу java.util.List.
Примитивные типы (byte, double и т.д.) использовать нельзя, допускаются только ссылочные типы.
Но допускается использование массивов, для этого суффиксом должны быть символы []:
def Test jvmType = "java.util.List[]"
В данном примере будет массив из java.util.List. А вот массивы могут содержать примитивные типы. Кроме того, допускаются многомерные массивы:
def Test jvmType = "int[][]"
Правильность указания JVM-типа проверяется на стадии компиляции.
Трейты с разными именами на уровне Libretto могут быть привязаны к одному и тому же JVM-типу. Но на уровне Libretto это всё равно будут разные трейты, которые статически несовместимы между собой.
trait Test
def Test jvmType = "int[]"
trait Test2
def Test2 jvmType = "int[]"
def Test conv: Test2 = this // ошибка компиляции
Этот пример не скомпилируется, поскольку Test нельзя обычными средствами Libretto статически преобразовать в Test2. В такой ситуации нужно использовать специальные методы из libretto/jvm для проверки и преобразования типов.
Поскольку JVM-трейты всегда пустые по определению, то можно было бы подать (обернуть в трейт) любой объект, что сломало бы корректность программы. Поэтому оборачивание в такой JVM-трейт запрещено на стадии компиляции:
trait Test
def Test jvmType = "int[]"
def main = {
Test(123) // ошибка компиляции
}
Этот пример не скомпилируется.
Запрещено и прямое создание экземпляра JVM-трейта:
trait Test
def Test jvmType = "int[]"
def main = {
new Test {} // ошибка компиляции
}
Этот пример тоже не скомпилируется.
Предопределенные типы
Есть Libretto структуры, для которых уже задано автоматическое преобразование в JVM-тип и обратно:
libretto/Int⇔java.math.BigIntegerlibretto/Real⇔java.math.BigDecimallibretto/jvm/RawString⇔java.lang.Stringlibretto/num/R64⇔java.lang.Doublelibretto/num/R32⇔java.lang.Floatlibretto/num/I64⇔java.lang.Longlibretto/num/I32⇔java.lang.Integerlibretto/num/I16⇔java.lang.Shortlibretto/num/I8⇔java.lang.Bytelibretto/num/Char16⇔java.lang.Characterlibretto/num/Boolean⇔java.lang.Boolean
Кроме того, поддерживается libretto/Any как эквивалент java.lang.Object.
Остальные JVM-типы (классы, интерфейсы и массивы) должны использоваться через JVM-трейт с соответствующим jvmType (см. выше).
Кардинальность
При работе с JVM-трейтами без кардинальности предполагается, что соответствующие JVM-типы не получают null-значение, иначе возможно исключение NullPointerException во время работы программы. Для корректной работы с null значением необходимо использовать кардинальность ?, тогда пустота значения Libretto (JVM-трейта) эквивалентна null на уровне JVM (и наоборот).
Другие кардинальности (+, *) при работе с JVM не поддерживаются напрямую (см. соответствующий раздел).
Строки и JVM
Для представления строк Libretto на уровне JVM не используется класс java.lang.String напрямую. Для того, чтобы работать со Libretto-строками в JVM мире, определена структура libretto/jvm/RawString, именно она и соответствует java.lang.String.
Для перехода от libretto/String в libretto/jvm/RawString и обратно в пакете libretto/jvm определены методы:
def RawString string: String
def String raw: RawString
Пример:
use libretto/jvm
def main = {
fix s = "abc"
fix a: jvm/RawString = s.jvm/raw
println: s
println: a
fix b: String = a.string
println: b
}
Выдает:
abc
abc
abc
RawString предназначен в первую очередь для взаимодействия с JVM, использование для каких-то других рабочих целей не рекомендуется.
Примитивные типы
Поддерживается автоматический boxing/unboxing для примитивных типов JVM. Но для этого соответствующие типы на уровне Libretto должны использоваться без кардинальности (пустота null не допускается):
libretto/num/R64⇔doublelibretto/num/R32⇔floatlibretto/num/I64⇔longlibretto/num/I32⇔intlibretto/num/I16⇔shortlibretto/num/I8⇔bytelibretto/num/Char16⇔charlibretto/num/Boolean⇔boolean
Автоматически boxing/unboxing не работает на уровне массивов, они должны быть определены как есть: int[], java.lang.Integer[] и т.д.
Последовательность и Many
Нельзя напрямую работать с кардинальностями */+, но можно получить последовательность как единое значение. Тип этого значения в Libretto представлен трейтом libretto/jvm/Many
При помощи методов libretto/jvm/Many и libretto/jvm/value можно делать преобразования в обе стороны:
def Many(value: Any*): Many
def Many value: Any*
Пример:
use libretto/jvm
def main = {
fix test: Int* = (1,2,3)
fix m: jvm/Many = test.*jvm/Many // последовательность как одно значение
// здесь можно работать с m на уровне JVM
fix back: Int* = m.value.*dyn[Int*] // обратное преобразование, в явный тип Int*
println: test == back
}
Выдает: unit
Many принимает и одиночные значение, но оборачивает их в последовательность, после чего возвращается полученный Many.
Параметризация методов типами
Как указано выше, одному JVM-типу может соответствовать несколько JVM-трейтов в Libretto, поэтому при вызове некоторых методов из libretto/jvm, которые возвращают результат из мира JVM, нужно знать тип - JVM-трейт, которым будет представлен результат.
Для этого вызов метода параметризуется типом через []. Общее правило: если параметров типа несколько, то тип результата всегда указывается первым:
-
jvm/newObject[Date]()-Dateкак тип результата. -
list.jvm/call[num/I32]("size")-num/I32как тип результата. -
jvm/callStatic[(), S]("gc")-()как тип результата. -
jvm/getFieldStatic[num/R64, Math]("E")-num/R64как тип результата.
Мнемоническое правило: порядок перечисления типов в [] и язык Java, в котором тип результата всегда указывается первым.
Создание объекта (newObject)
Для создания JVM-объекта используется метод libretto/jvm/newObject, который типизируется JVM-трейтом, обозначающим JVM-класс (именно класс, не интерфейс или массив). Результат вызова newObject будет указанного типа. Допускается только тип без кардинальности. При отсутствии аргументов при вызове обязательны круглые скобки .
use libretto/jvm
trait Date
def Date jvmType = "java.util.Date"
def main = {
fix d = jvm/newObject[Date]()
println: d
println: type(d)
}
Выдает (дата-время будут отличаться):
Wed Sep 02 14:36:38 SGT 2020
_default/Date!
В newObject можно дополнительно передавать аргументы конструктора.
use libretto/jvm
trait File
def File jvmType = "java.io.File"
def main = {
fix a = jvm/newObject[File]("a".jvm/raw)
fix f = jvm/newObject[File](a, "b".jvm/raw)
println: f
}
Выдает (под Windows):
a\b
Как указано, работает автоматическое преобразование аргументов в примитивные типы.
use libretto/jvm
use libretto/num
trait UUID
def UUID jvmType = "java.util.UUID"
def main = {
fix m = jvm/newObject[UUID](1.num/i64, 2.num/i64)
println: m
}
Выдает:
00000000-0000-0001-0000-000000000002
Вызовы методов (call, callStatic)
Для вызова нестатических методов интерфейсов и классов используется метод libretto/jvm/call. Вызов метода call параметризуется типом результата. Объект, на котором вызывается метод, берётся из контекста ($). Первый аргумент вызова (обязательный) - это имя метода, должен быть представлен строковым литералом. Далее могут быть опциональные аргументы для вызываемого JVM-метода.
use libretto/jvm
use libretto/num
trait List
def List jvmType = "java.util.ArrayList"
def main = {
fix list = jvm/newObject[List]()
println: list.jvm/call[num/I32]("size")
println: list.jvm/call[num/Boolean]("add", "abc".jvm/raw)
println: list.jvm/call[num/I32]("size")
}
Выдает:
0
true
1
Если метод не возвращает результат (void в JVM), то вместо типа результата указывается тип пустоты () . Его же можно указывать в том случае, если результат возвращается, но не нужен при вызове.
use libretto/jvm
use libretto/num
trait List
def List jvmType = "java.util.ArrayList"
def main = {
fix list = jvm/newObject[List]()
println: list.jvm/call[num/I32]("size")
list.jvm/call[()]("add", "def".jvm/raw)
list.jvm/call[()]("add", 0.num/i32, "abc".jvm/raw)
println: list.jvm/call[num/I32]("size")
println: list
}
Выдает:
0
2
[abc, def]
Для вызова статического метода используется libretto/jvm/callStatic, он параметризуется двумя типами: тип результата и тип класса/интерфейса, у которого будет вызван статический метод. Первый аргумент - это имя метода (должен быть строковым литералом). Остальные аргументы передаются в статический метод.
use libretto/jvm
trait S
def S jvmType = "java.lang.System"
def main = {
jvm/callStatic[(), S]("gc")
}
Вызывает GC.
Пример передачи параметра:
use libretto/jvm
use libretto/num
trait Math
def Math jvmType = "java.lang.Math"
def main = {
fix r = jvm/callStatic[num/I32, Math]("abs", (-123).num/i32)
println: r
}
Выдает: 123
Алгоритм выбора методов/конструкторов
Алгоритм выбора методов/конструкторов при конфликте типов параметров совпадает с таковым в Libretto. Но одна особенность: примитивные типы считаются более специфичными, чем обёрточный класс или Object.
Например, аргументом передаётся значение типа libretto/num/I32, а под него попали методы, принимающие:
-
java.lang.Object -
java.lang.Integer -
int
Алгоритм посчитает наиболее специфичным метод, принимающий int, хотя реально (внутри скомпилированного JVM-кода) значение представлено при помощи java.lang.Integer.
Если же требуется выбрать метод именно с java.lang.Integer, то нужно подавать аргументом значение типа libretto/num/I32?, тогда параметр int не попадёт в выбранные, а из java.lang.Object и java.lang.Integer будет использоваться java.lang.Integer как наиболее специфичный.
При равенстве типов параметров выбор идет по типу результата: выбирается более специфичный.
Если у методов одинаковые типы параметров и одинаковый тип результата, то более специфичным считается тот, что определен в более специфичном классе.
Если выбрать нужный метод нельзя, то возникает ошибка компиляции.
Ограничение статических методов интерфейсов
При использовании вызова статического метода интерфейса возникает исключение: java.lang.IncompatibleClassChangeError: Method ... must be InterfaceMethodref constant. Происходит под Java 9+, под Java 8 работает нормально.
Проблема в изменениях Java. Байт-код Libretto генерируется под Java 5, тогда как InterfaceMethodref требуется начиная с Java 9, а поддерживается начиная с Java 8. Сейчас сложно переключиться на генерацию Libretto компилятором байт-кода под Java 8 поскольку нужно задавать не только данные о глубине стека, но и так называемые стэкмэпы, которые требуются с Java 6 версии.
В будущем возможно исправление этой недоработки, сейчас же нужно воздержаться от прямых вызовов таких методов: сделать “переходник” на Java.
Статические поля (getFieldStatic, setFieldStatic)
Метод libretto/jvm/getFieldStatic служит для получения значения статического поля у класса/интерфейса. Вызов метода параметризуется двумя типами: тип результата и тип класса/интерфейса, у которого будет прочитано статическое поле. Первый аргумент - это имя поля (должен быть строковым литералом).
use libretto/jvm
use libretto/num
trait Math
def Math jvmType = "java.lang.Math"
def main = {
println: jvm/getFieldStatic[num/R64, Math]("PI")
println: jvm/getFieldStatic[num/R64, Math]("E")
}
Выдает:
3.141592653589793
2.718281828459045
Метод установки значения статического поля у класса: libretto/jvm/setFieldStatic, параметризуется типом класса/интерфейса, у которого будет установлено статическое поле. Первый аргумент - это имя поля (должно быть строковым литералом). Второй аргумент - это новое значение поля.
Пример (зависит от com/teacode/pdf):
use libretto/jvm
use libretto/num
use com/teacode/pdf
trait A
def A jvmType = "com.lowagie.text.FontFactory"
def main = {
fix saved = jvm/getFieldStatic[num/Boolean, A]("defaultEmbedding")
println: saved
jvm/setFieldStatic[A]("defaultEmbedding", num/true)
println: jvm/getFieldStatic[num/Boolean, A]("defaultEmbedding")
jvm/setFieldStatic[A]("defaultEmbedding", saved)
}
Выдает:
false
true
Работа с массивами (newArray, arraySize, arraySet, arrayGet)
Создание массива методом libretto/jvm/newArray, параметризуется типом массива (не типом элемента массива), аргументом передаётся размер (libretto/num/I32)
Получение длины массива методом libretto/jvm/arraySize, массив передается контекстом, вызов без круглых скобок.
Выставление значения массива по индексу методом libretto/jvm/arraySet, первый аргумент - это индекс (libretto/num/I32), второй аргумент - значение элемента. Сам массив передается контекстом. Значение элемента должно соответствовать типу массива.
Получение значения массива по индексу методом libretto/jvm/arrayGet, параметризуется типом элемента массива, аргумент - индекс (libretto/num/I32). Массив передаётся контекстом.
use libretto/jvm
use libretto/num
trait Arr
def Arr jvmType = "int[]"
def Arr print() = {
fix size = this.jvm/arraySize
println: s"size=#{size}"
0..(size.int - 1) as i.${
fix v = this.jvm/arrayGet[num/I32](i.num/i32)
println: s" #{i}: #{v}"
}
}
def main = {
fix arr = jvm/newArray[Arr](7.num/i32)
arr.print()
arr.jvm/arraySet(1.num/i32, 123.num/i32)
arr.print()
}
Выдает:
size=7
0: 0
1: 0
2: 0
3: 0
4: 0
5: 0
6: 0
size=7
0: 0
1: 123
2: 0
3: 0
4: 0
5: 0
6: 0
Ограничения JVM-типов на уровне Libretto
Преобразование типов при JVM-взаимодействии имеет свою специфику, поскольку система типов Libretto отличается от системы типов JVM. Например, в JVM есть интерфейс java.util.List, который реализуется, например, классом java.util.ArrayList. Это означает, что вместо List можно подставлять ArrayList.
use libretto/jvm
use libretto/num
trait List
def List jvmType = "java.util.List"
trait ArrayList
def ArrayList jvmType = "java.util.ArrayList"
def test(list: List) = {
println: list.jvm/call[num/I32]("size")
}
def main = {
fix list: List = jvm/newObject[ArrayList]()
test(list)
}
Но этот пример не компилируется. Хотя ArrayList в JVM можно безопасно преобразовать в List, на на уровне Libretto трейты ArrayList и List всегда несовместимы.
В связи с этим для работы с преобразованием типов рекомендуется использовать специальные методы, а не обычные средства Libretto.
Проверка и преобразования типов (isInstanceOf, convert, forcedCast)
Вызов метода libretto/jvm/convert параметризуется JVM-типом, в который нужно преобразовать переданное аргументом значение. Преобразование на уровне JVM безопасное (от более специфичного к менее специфичному типу). Если статически нельзя преобразовать тип, то возникнет ошибка компиляции.
Вызов метода libretto/jvm/forcedCast аналогично параметризуется JVM-типом, в который нужно преобразовать переданное аргументом значение. Но преобразование динамическое при помощи кастинга (от менее специфичного типа к более специфичному), во время компиляции не проверяется. Если во время исполнения невозможно сделать это преобразование, то будет соответствующее исключение.
use libretto/jvm
use libretto/num
trait List
def List jvmType = "java.util.List"
trait ArrayList
def ArrayList jvmType = "java.util.ArrayList"
def test(list: List) = {
fix arrayList = jvm/forcedCast[ArrayList](list)
println: arrayList.jvm/call[num/I32]("size")
}
def main = {
fix arrayList = jvm/newObject[ArrayList]()
fix list: List = jvm/convert[List](arrayList)
test(list)
}
Выдает: 0
Метод isInstanceOf параметризуется JVM-типом. При вызове метода контекст проверяется на соответствие этому типу во время исполнения. Если соответствует, то результат unit. Если не соответствует - ().
use libretto/jvm
trait List1
def List1 jvmType = "java.util.ArrayList"
trait List2
def List2 jvmType = "java.util.ArrayList"
def main = {
fix list = jvm/newObject[List1]()
println: list.*{
case ~: List1 = "List1"
else = "not List1"
}
println: list.*{
case ~: List2 = "List2"
else = "not List2"
}
println: list.jvm/isInstanceOf[List1]
println: list.jvm/isInstanceOf[List2]
}
Выдает:
List1
not List2
unit
unit
Исключения (throw, catch)
Метод libretto/jvm/throw используется для выбрасывания исключения. Переданное значение должно быть экземпляром java.lang.Throwable или его подклассов - это проверяется статически при компиляции.
Пример:
use libretto/jvm
use libretto/util
trait Exception
def Exception jvmType = "java.lang.Exception"
def main = {
fix exc = jvm/newObject[Exception]("Hello!".jvm/raw)
println("Before")
jvm/throw(exc).*util/finally: {
println("After")
}
}
Выдает:
Before
After
Error (5 ms): java.lang.Exception: Hello!
at _default/main (internal editor:8)
at <jvm>
Метод libretto/jvm/catch позволяет ловить исключения. Вызов метода параметризуется классом исключения (должен быть java.lang.Throwable или его подкласс - проверяется статически), которое будет поймано в случае его возникновения. Будет ловиться исключение как напрямую указанного класса, так и его подклассов. Аргумент - это код, который будет выполнен и в котором будет ловиться исключение.
Тип результата метода catch является типом результата переданного кода, обобщённого с типом исключения.
Пример:
use libretto/jvm
trait Exception
def Exception jvmType = "java.lang.Exception"
def main = {
fix t? = unit
fix r = jvm/catch[Exception]: {
fix exc = jvm/newObject[Exception]("Hello!".jvm/raw)
if (t)
jvm/throw(exc)
else
777
}
println: r
println: type(r)
}
Выдает:
java.lang.Exception: Hello!
(_default/Exception | libretto/Int)!
Т.е. в этом примере результатом catch является либо само исключение (Exception), либо целое число - это если не было исключения.
Более сложный пример:
use libretto/jvm
trait Exception
def Exception jvmType = "java.lang.Exception"
trait RuntimeException
def RuntimeException jvmType = "java.lang.RuntimeException"
def main = {
println: (1,2,3).{
fix r = jvm/catch[Exception]: {
if ($ == 2) {
fix exc = jvm/newObject[RuntimeException]("Hello!".jvm/raw)
jvm/throw(exc)
}
$ + 100
}
r.*{
case r: Exception = r.jvm/call[jvm/RawString?]("getMessage").string
case i: Int = -i
}
}
}
Выдает: (-101,Hello!,-103)
Обратите внимание, что ловится Exception, тогда как кидается его подкласс RuntimeException. И разбор case идёт именно по Exception, поскольку трейт RuntimeException на уровне Libretto не является подклассом трейта Exception (см. подробно соответствующее описание).
Метод catch работает на более низком уровне по сравнению с try из libretto/util. В частности, catch строго отлавливает указанный класс или подкласс исключений, никакой обработки (например, игнорирование ControlThrowable или того подобного) он не делает. Обработку ControlThrowable необходимо делать самостоятельно.
Реализация классов/интерфейсов в Libretto (implement, extend)
При помощи метода libretto/jvm/implement можно превратить экземпляр трейта (этот экземпляр передается аргументом) в экземпляр JVM-интерфейса (тип интерфейса параметризует вызов).
Каждому методу из интерфейса в трейте должен соответствовать одноименный метод с таким же числом аргументов. Типы преобразуются как обычно. Если в интерфейсе результат void, то результат трейта игнорируется (но он должен соответствовать JVM-миру).
Пример:
use libretto/jvm
trait Runnable
def Runnable jvmType = "java.lang.Runnable"
trait Example {
def run(): ()
}
def main = {
fix example = new Example {
def run() = {
println("Hello!")
()
}
}
fix runnable = jvm/implement[Runnable](example)
runnable.jvm/call[()]("run")
}
Выдает: Hello!
Здесь трейт Example служит для реализации интерфейса java.lang.Runnable (в котором только метод void run()). implement возвращает реализацию интерфейса на базе экземпляра Example. Метод run вызывается уже не на трейте, а на интерфейсе.
Чуть сложнее пример. Реализуется Lambda1 (интерфейс для внутреннего представления одноместной анонимной функции при компиляции Libretto в JVM).
use libretto/jvm
trait L1
def L1 jvmType = "org.ontobox.libretto.jvm.Lambda1"
def Example(str: String) = {
new {
def apply(ctx: Any, p0: jvm/Many): jvm/Many = {
fix m = p0.value
jvm/Many: m + str
}
}
}
def main = {
fix l1 = jvm/implement[L1](Example("_"))
fix r = l1.jvm/call[jvm/Many]("apply", unit, jvm/Many: (1, "a", unit)).value
r as v.${
println(v)
}
}
Выдает:
1_
a_
unit_
Много переходов к jvm/Many и обратно. Это необходимо, поскольку на уровне JVM нет *-кардинальности (см. соответствующее описание).
В реализации интерфейса при помощи implement разрешён overloading, но на уровне интерфейса. В трейте, соответственно, всё равно будет только один метод, который должен принимать вызовы через все overloaded методы.
Есть возможность расширения класса. Аналогично implement, но метод libretto/jvm/extend. Вызов параметризуется jvm-трейтом, обозначающий класс для расширения, аргумент вызова - это трейт, который реализует абстрактные или override уже существующие методы класса.
Ограничения: класс не должен быть final, должен содержать пустой (т.е. без параметров) public или protected конструктор. Методы для реализации должны быть абстрактные. Методы для override не должны быть final или private.
Пример:
use libretto/jvm
use libretto/num
trait Number
def Number jvmType = "java.lang.Number"
def ExampleNumber(v: Real): ExampleNumber = {
new {
def intValue: num/I32 = v.int.num/i32
def longValue: num/I64 = v.int.num/i64
def floatValue: num/R32 = v.num/r32
def doubleValue: num/R64 = v.num/r64
}
}
def main = {
fix en = jvm/extend[Number](ExampleNumber(123.45))
println: en.jvm/call[num/I32]("intValue")
println: en.jvm/call[num/I64]("longValue")
println: en.jvm/call[num/R32]("floatValue")
println: en.jvm/call[num/R64]("doubleValue")
}
Выдает:
123
123
123.45
123.45
Number - это абстрактный класс, для реализации которого нужно определить четыре метода, что и делается при помощи трейта ExampleNumber.
Вложенные классы Java
В Java есть статические вложенные классы, имя которых доступно через точку. Например, библиотечный Base64.Encoder. Но это имя превращается в Base64$Encoder на уровне JVM. Поэтому для работы с подобными классами нужно использовать именно $ как разделитель, а точка . используется только как разделитель частей имени пакета. Т.е. полное имя будет java.util.Base64$Encoder.
Соответствующий пример кодирования/декодирования Base64:
use libretto/jvm
trait B64
def B64 jvmType = "java.util.Base64"
trait Encoder
def Encoder jvmType = "java.util.Base64$Encoder"
trait Decoder
def Decoder jvmType = "java.util.Base64$Decoder"
trait Bytes
def Bytes jvmType = "byte[]"
def charSet = "UTF-8".jvm/raw
def encode(str: String): String = {
fix encoder = jvm/callStatic[Encoder, B64]("getEncoder")
fix stringBytes = str.jvm/raw.jvm/call[Bytes]("getBytes", charSet)
fix encoded = encoder.jvm/call[jvm/RawString]("encodeToString", stringBytes)
encoded.string
}
def decode(encoded: String): String = {
fix decoder = jvm/callStatic[Decoder, B64]("getDecoder")
fix decodedBytes = decoder.jvm/call[Bytes]("decode", encoded.jvm/raw)
fix decoded = jvm/newObject[jvm/RawString](decodedBytes, charSet)
decoded.string
}
def main = {
fix original = <<Hello, world!>>
println: original
fix encoded = encode(original)
println: encoded
fix decoded = decode(encoded)
println: decoded
}
Выдает:
Hello, world!
SGVsbG8sIHdvcmxkIQ==
Hello, world!
Сравнение == и equals
Сравнение JVM-объектов можно делать и при помощи вызова equals (как то делается в Java). Но и == на уровне Libretto тоже работает (вызывается тот же equals).
use libretto/jvm
use libretto/num
trait UUID
def UUID jvmType = "java.util.UUID"
def main = {
fix uuid1 = jvm/newObject[UUID](1.num/i64, 2.num/i64)
fix uuid2 = jvm/callStatic[UUID, UUID]("fromString",
"00000000-0000-0001-0000-000000000002".jvm/raw)
fix uuid3 = jvm/callStatic[UUID, UUID]("fromString",
"00000000-0000-0001-0000-000000000003".jvm/raw)
println: uuid1
println: uuid2
println: uuid3
println()
println: uuid1.jvm/call[num/Boolean]("equals", uuid2)
println: uuid1 == uuid2
println()
println: uuid1.jvm/call[num/Boolean]("equals", uuid3)
println: uuid1 == uuid3
}
Выдаёт:
00000000-0000-0001-0000-000000000002
00000000-0000-0001-0000-000000000002
00000000-0000-0001-0000-000000000003
true
unit
false
()
Enum
Enum технически представлены обычным JVM-классом. Для получения экземпляра enum можно использовать одноименное статическое поле: jvm/getFieldStatic[ExEnum, ExEnum]("Name") - возвращает экземпляр Name enum ExEnum.
Пример работы с enum Thread.State.
use libretto/jvm
trait Thread
def Thread jvmType = "java.lang.Thread"
// Это enum Thread.State
trait ThreadState
def ThreadState jvmType = "java.lang.Thread$State"
def main = {
fix thread = jvm/callStatic[Thread, Thread]("currentThread")
fix state = thread.jvm/call[ThreadState]("getState")
println: state
fix runnable = jvm/getFieldStatic[ThreadState, ThreadState]("RUNNABLE")
println: state == runnable
}
Выдаёт:
RUNNABLE
unit
Поскольку это не просто enum, но и вложенный класс, то вместо Thread.State нужно использовать Thread$STate
JVM-обобщения (generics)
На уровне байт-кода обобщения (generics) теряют свою информацию о параметризации. Поэтому нужно определять JVM-тип без обобщения, затем использовать ручной динамический кастинг к нужному типу при помощи forcedCast.
Например, если в программе на Java используется java.util.List<String>, то на уровне JVM это будет общий тип java.util.List, методы которого оперируют не со String, а с Object. Для получения экземпляров String необходимо сделать ручной кастинг к String.
Аналогично с другими типами:
use libretto/jvm
use libretto/num
trait System
def System jvmType = "java.lang.System"
trait Map
def Map jvmType = "java.util.Map"
trait Set
def Set jvmType = "java.util.Set"
trait Iterator
def Iterator jvmType = "java.util.Iterator"
def main = {
// Map<String, String>
fix map = jvm/callStatic[Map, System]("getenv")
fix keys = map.jvm/call[Set]("keySet")
fix iterator = keys.jvm/call[Iterator]("iterator")
while(iterator.jvm/call[num/Boolean]("hasNext").unitValue): {
fix key = iterator.jvm/call[Any]("next") // java.lang.Object
fix stringKey = key.*jvm/forcedCast[jvm/RawString] // java.lang.String
println(stringKey)
}
}
В этом примере Map<String, String> из Java доступен в JVM как Map, т.е. типизация ключа и значения “сбрасывается” до Object. И, при необходимости, нужно делать кастинг вручную. В данном случае это преобразование ключа из java.lang.Object в java.lang.String. Хотя для вывода на экран в этом нет необходимости, но это сделано для демонстрации методики.
Вспомогательные сущности в пакете libretto/jvm/java
Сущности, которые часто используются для взаимодействия с JVM-кодом, Java-библиотеками.
Трейты в пакете libretto/jvm/java:
Throwableсоответствуетjava.lang.ThrowableListсоответствуетjava.util.ListMapсоответствуетjava.util.MapSetсоответствуетjava.util.SetIteratorсоответствуетjava.util.Iterator
Вспомогательные сущности в пакете libretto/jvm/scala
Сущности, которые часто используются для взаимодействия с кодом, написанным на языке Scala.
Трейты в пакете libretto/jvm/scala:
Optionсоответствуетscala.OptionSeqсоответствуетscala.collection.SeqIteratorсоответствуетscala.collection.IteratorFunction1соответствуетscala.Function1
Методы для работы с Option:
def Option(any: Any?): Option- созданиеOptiondef Option value: Any?- получение значения изOption
Методы для работы с Seq:
def toSeq(value: Any*): Seq- созданиеSeq
Методы для создания FunctionX:
def Function1(func: Func[Any, Any]): Function1
Подключение библиотек (jar-файлов)
Поскольку не все необходимые Java-библиотеки доступны в стандартном JRE, то есть возможность подключения сторонних jar-файлов. Для этого используются Libretto-библиотеки, в которые и кладутся необходимые jar-файлы. Более подробно см. описание структуры Libretto-библиотек.