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

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

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

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

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

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

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

## Редактор JEXL

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

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

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

![panel](/img_jexl/panel.png)

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

1. **Выполнить** — запуск текущего скрипта.
2. **Сохранить**.
3. **Откатить**.
4. **Обновить**.
5. **Открыть скрипт из библиотеки JEXL** — выбор скрипта из общего/личного хранилища.
6. **Информация + Аудит JEXL** — просмотр метаданных и истории изменений  
![pun6](/img_jexl/pun6.png).
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)

Пример

```jexl
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 = {:}; // Пустая карта
```
### Доступ к свойствам объектов
Два варианта доступа к объекту:
```jexl
// Через точку (автоматически вызывает getter)
var sClientName = order.client.name;

// Через метод
var nTotal = order.getTotal();
```
### Импорт статических обектов
Синтаксис:
```
#pragma import <Путь до класса>
#pragma import "<Путь до класса> as алиас"
```
Импортированный объект доступен будет доступен по наименованию класса `<Имя_класса>:` или по алиасу, при его указинии `<алиас>:`

**Быстрый ввод:** введите `import` и вызовите подсказки сочетанием `Ctrl+Enter`.`
Варианты подсказок:
```jexl
import named    ->  #pragma import "class as alias"
import default  ->  #pragma import class
```

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

**Пример:**
```js
#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);
```