Перейти к основному содержанию
`Ctrl`+`K`

# Введение и основы JEXL

## Введение в языки выражений

Языки выражений — специализированные языки, которые вычисляют выражения во время работы программы. Они позволяют динамически выполнять математические, логические и строковые операции, обращаться к свойствам объектов и вызывать методы без компиляции кода.

Языки выражений выполняются во время работы программы и позволяют гибко менять логику — например, проверять условия `user.age > 18` или вычислять формулы `price * quantity` без перезапуска приложения.

Зачем нужны языки выражений:

- гибкость — изменение логики без перекомпиляции приложения;

- безопасность — ограниченный набор операций снижает риски выполнения вредоносного кода;

- удобство — простой синтаксис для вычислений и проверок условий;

- динамическое вычисление — выражения могут загружаться из внешних источников.

JEXL — библиотека, реализующая язык выражений для вычисления и выполнения скриптов в Java-приложениях.

## Редактор JEXL

Как открыть редактор JEXL:

Откройте главное меню системы и перейдите в `Сервисы > Инструменты > Выполнить JEXL-скрипт`

Редактор состоит из трёх областей:

Панель инструментов — набор кнопок для управления скриптами:

1. Выполнить — запуск текущего скрипта.

2. Сохранить.

3. Откатить.

4. Обновить.

5. Открыть скрипт из библиотеки JEXL — выбор скрипта из общего/личного хранилища.

6. Информация + Аудит JEXL — просмотр метаданных и истории изменений
   .

7. Проверить правильность JEXL-скрипта — проверка синтаксиса написанного кода.

8. Помощь — краткая справка по существующим методам:

   - Помощь — вкладка с методами и их кратким описанием.

   - Список объектов — список объектов API всей системы.

   - Контекст — фильтр отображения методов по контексту, API, математическим функциям.

   - К оглавлению — возврат к редактору кода.

   - Обновить.

   - Проверить правильность JEXL-скрипта.

   - Официальная документация.

9. Выход.

Рабочая область — написание и редактирование JEXL-кода.

Журнал выполнения — хронология выполнения скриптов, ошибки и предупреждения, системные сообщения.

- Очистить логи — удаление всей текущей истории сообщений из журнала.

## Основы синтаксиса выражений

### Простые выражения

JEXL поддерживает стандартные операторы, аналогичные Java.

Арифметика:

    var sum = 5 + 3;       // 8
    var total = 8 * 10;      // 80
    var remainder = 10 % 3; // 1

Логические операции:

    var isActive = true;
    var isEligible = (age >= 18) && isActive;
    var isAdmin = (role == "admin") || (role == "superuser");

Сравнения:

    price == 100
    name != 'admin'
    score > 90

### Поддерживаемые типы данных

JEXL работает с основными типами:

- числа — целые (42), дробные (3.14);

- строки — в одинарных („text“), двойных («text») или обратных кавычках (`test`);

- булевы значения — true, false;

- null — отсутствие значения;

- коллекции — списки [1, 2, 3], мапы {„key“: „value“}, множества {1, 2, 3}.

### Обертки для безопасной передачи данных

В JEXL для работы со Scala-методами, требующими строгой типизации и поддержки null, используются специальные обертки.

Scala — статически типизированный язык, где:

- null для примитивов (Long, Int) запрещен.

При вызове Scala-методов из JEXL:

- JEXL передает «сырые» значения (String, Number, null);

- Scala ожидает строгие типы (`NLong`, `NGid`).

Null-типы и их назначения:

Null-тип | Назначение
--- | ---
`NLong` | Работа с идентификаторами (ID)
`NNumber` | Работа с целыми/дробными числами
`NGid` | Работа с глобальными идентификаторами (GUID)
`NDate` | Работа с датами
`NString` | Работа со строками (с поддержкой null)
`NBigDecimal` | Точные расчеты (деньги, финансы)
`NDuration` | Арифметика временных промежутков

Создание Null-типа на примере `NLong`:

    var NLong = function(number) {
        return new ("ru.bitec.app.gtk.lang.NLong", number);
    };
    var nlValue = NLong(21);

Конкретное применение null-типов:

    // Создаем Map с параметрами для заказа
    Map1.put(
        new ("ru.bitec.app.gtk.lang.NLong", ropStageDet.id),
        new ("ru.bitec.app.gtk.lang.NNumber", ropStageDet.nQty)
    );

    // Создаем заказ
    idvOrder = Stm_OrderOutApi.createOrderByContractStage(
        ropStage.id,
        idvDepOwner,
        idvOrderType,
        asScala(Map1), // Основные параметры
        asScala(EmptyMap), // Доп. параметры 1
        asScala(EmptyMap), // Доп. параметры 2
        null
    );

## Работа с переменными

### Способы объявления переменных

JEXL предоставляет три способа объявления переменных: `let`, `const`и `var`, каждый с своей областью видимости и правилами изменяемости.

`let` — Локальная переменная

Ключевое слово `let` создаёт переменную, доступную только внутри блока { }, где она объявлена, включая вложенные блоки. Переменная не может быть переопределена в той же области.

Пример:

    if (56 < 100) {
        let sText = "Example text";
    }
    dialogs.showMessage(sText) // ошибка: переменная объявлена в другом блоке

`const`— Неизменяемая переменная

Переменная, объявленная через const, имеет блочную область видимости, но её значение нельзя изменить после инициализации.

Пример:

    if (56 < 100) {
        `const`sText = "Example text";
        sText = "Changed text"; // Ошибка: изменение константы
    }

Исключение: если `const`ссылается на объект, его внутренние свойства могут изменяться — неизменной остаётся только ссылка.

Пример:

    `const`arrayEven = [2, 4, 6, 8];
    arrayEven[0] = 10;
    dialogs.showMessage(arrayEven[0]); // Вывод: 10

var — Глобальная переменная

По умолчанию `var` создаёт переменную, видимую во всём скрипте, и позволяет её переопределять:

    if (56 < 100) {
        var sText = "Example text";
        sText = "Changed text";
    }
    sText = "Outer text"; // Изменение разрешено
    dialogs.showMessage(sText); // Вывод: Outer text

Когда что использовать:

- `const`— для значений, которые не должны меняться;

- `let` — для обычных переменных внутри блоков;

- `var` — для глобальных переменных (с осторожностью).

### Ленивое вычисление значения

В JEXL нет отдельной конструкции `lazy val`. Если значение нужно вычислить только при первом обращении и затем переиспользовать, следует применять явную ленивую инициализацию:

- значение-кэш хранить в `var`;

- доступ к нему выполнять через функцию;

- при первом вызове функция вычисляет значение и сохраняет его в кэш;

- при последующих вызовах возвращает уже сохранённый результат.

Такой подход удобно использовать для дорогих вычислений, повторных SQL-запросов и получения данных, которые в рамках одного выполнения скрипта не меняются. `var` в текущем руководстве описан как переменная, видимая во всём скрипте, а `const`— как неизменяемая ссылка, поэтому для такого шаблона обычно используют `var` для кэша и `const`или function для accessor-функции. (Global ERP)

Пример

    var vSettingsCache = null;

    `const`getSettings = function() {
        if (isNull(vSettingsCache)) {
            vSettingsCache = sql(`
                select code, name
                from cfg_settings
                where is_active = true
            `).asList();
        }
        return vSettingsCache;
    };

    var settings1 = getSettings(); // первое обращение: выполняется запрос
    var settings2 = getSettings(); // повторное обращение: используется кэш

Рекомендации

- не выполнять тяжёлое выражение напрямую в нескольких местах;

- выносить ленивую инициализацию в отдельную функцию с понятным именем;

- использовать такой приём только в пределах одного JEXL-скрипта;

- не применять этот шаблон там, где значение должно пересчитываться при каждом обращении.

### Объявления переменных поддерживаемых типов

Числовые типы:

    var nValue = 42; // Целое число
    var fValue = 42.0f; // Число с плавающей точкой
    var lValue = 42L; // Длинное целое
    var dValue = 42.0d; // Число двойной точности
    var bigIntValue = 42H; // Большое целое
    var bigDecValue = 42.0B; // Число высокой точности
    var hexValue = 0x2A; // Шестнадцатеричное число
    var octValue = 052; // Восьмеричное число
    var sciValue = 4.2E+1D; // Число в научной нотации

Строковые типы:

    var sStr1 = "Hello"; // Строка (двойные кавычки)
    var sStr2 = 'World'; // Строка (одинарные кавычки)
    var sStr3 = `line1`; `line2`; `line3`; // Многострочный комментарий;
    var intValue = 10
    var interpolatedStr = `Value: ${intValue}`; // Строка с подстановкой
    var regex = ~/pattern\d+/; // Регулярное выражение

Специальные значения:

    var bVal = true; // Логическое значение
    var nVal = null; // Пустое значение
    var tuple2 = (v1, v2) -> { return new(`scala.Tuple2`, v1, v2); }; // Кортеж

Коллекции:

    var array = [1, 2, 3]; // Массив
    var objArray = [1, "two", 3.0]; // Массив объектов
    var list = [1, 2, 3, ...]; // Список
    var set = {"a", "b", "c"}; // Множество
    var map = {"key1": 1, "key2": 2}; // Карта
    var range = 1..10; // Диапазон чисел
    var rop = Stm_OrderOutApi.load(id); // Строка роп соответствующая объекту БД

Пустые коллекции:

    var emptyArray = []; // Пустой массив
    var emptyList = [...]; // Пустой список
    var emptySet = {}; // Пустое множество
    var emptyMap = {:}; // Пустая карта

### Доступ к свойствам объектов

Два варианта доступа к объекту:

    // Через точку (автоматически вызывает getter)
    var sClientName = order.client.name;

    // Через метод
    var nTotal = order.getTotal();

### Импорт статических обектов

Синтаксис:

    #pragma import <Путь до класса>
    #pragma import "<Путь до класса> as алиас"

Импортированный объект доступен будет доступен по наименованию класса `<Имя_класса>:` или по алиасу, при его указинии `<алиас>:`

Быстрый ввод: введите `import` и вызовите подсказки сочетанием `Ctrl+Enter`.` Варианты подсказок:

    import named    ->  #pragma import "class as alias"
    import default  ->  #pragma import class

При вводе полного имени класса (`ru.bitec.app.gtk.lang.NLong`) и просто имени класса (`NLong`) работают подсказки полного пути

Пример:

    #pragma import ru.bitec.app.gtk.lang.NLong
    #pragma import "ru.bitec.app.gtk.lang.NNumber as nr"
    // Вызов статического метода
    let dec = nr:fromAny("10.01");
    let long = NLong:fromAny("10");
    // Создание класса
    new nr(dec);
    new NLong(long);