Skip to content

Полный гайд по созданию плагина

CORRUPTOR2037 edited this page May 27, 2018 · 10 revisions

Да, ещё один гайд. Ещё один. Ничего, ничего.

Значит, есть распространённое заблуждение, что чтобы влиться в разработку DeskChan, обязательно надо перелопатить весь код, который был до этого. Особенно ядро, в него почему-то лезут в первую очередь. Ребят, вот не надо. Вам там что, мёдом намазано?

DeskChan - это модульная система серверного типа. Именно что серверного, ядро просто облуживает ваши интересы. Не надо для этого знать, как оно работает. Вы же когда пытаетесь работать с сервером, вы не пытаетесь залезть в его код? Связаться с разработчиком, чтобы узнать, на каких конкретно технологиях сервер работает, сколько костылей и где в него затыкано? Нет, вы изучаете его API и устройство других клиентов сервера. Вот и здесь вам надо сделать то же самое. Сейчас мы здесь это и обсудим.

Значит, плагин - это такой клиент. Он вообще волен внутри себя делать что угодно, просто дополнительно он может писать запросы и ему тоже могут писать запросы. Формат плагина разрабатывается достаточно свободным, чтобы вы ни о чём не задумывались.

Оглавление

Создаём плагин

Где пишем код

Использование API

Хранение данных

Таймеры

Свойства

Локализация и ресурсы

Конфиг

Логирование

Команды

Меню

Обработка речи

Общение

Распространитель ресурсов

Отладка

Некоторые особенности Java

На данный момент у вас есть четыре варианта поведения:

  • Создаём внутренний плагин
    • Плюсы: весь функционал доступен сразу, удобно тестировать
    • Минусы: много возни по оформлению объектно-ориентированного кода, проект придётся постоянно пересобирать, и вообще нам не особо нравится идея, с запихиванием всех плагинов внутрь исполняемого файла
  • Создаём внешний плагин
    • Плюсы: можно юзать сторонние библиотеки и легко распространять свой модуль отдельно от DeskChan
    • Минусы: очень неудобно тестировать
  • Создаём скрипт
    • Плюсы: можно быстро и легко написать всё что вздумается и так же быстро поменять
    • Минусы: нельзя подключать библиотеки
  • Создаём сценарий
    • Плюсы: проще всего писать
    • Минусы: сейчас нет полной поддержки сценариев, функционал крайне ограничен

Далее подразумевается, что вы выбрали первый вариант. Хотя это и не важно: для всех трёх вариантов плагину предоставляется полностью эквивалентое API. Для сценариев есть отдельная статья.

Создаём плагин

Внутреннний модуль

Создайте новую папку или совокупность вложенных папок в директории src/main/java/. Если хотите писать на другом языке - замените java на название вашего языка (только если это не java и не kotlin, потребуется дополнительная настройка скрипта сборки).

Внутри этой директории создайте файл c классом Main и соответствующим вашему языку расширением файла.

package %ваше.название.пакета%;
public class Main implements Plugin {
    @Override
    public boolean initialize(PluginProxyInterface pluginProxy){

    }
    @Override
    public boolean unload(){

    }
}

Чтобы он работал, надо зайти в файл src/main/kotlin/info/deskchan/core/Main.kt и добавить там сразу перед блоком try-catch строчку

pluginManager.tryLoadPluginByPackageName("%ваше.название.пакета%");

Внешний модуль

В принципе, всё то же самое, только вы создаёте не папку, а отдельный проект. В Main.kt ничего запихивать не надо, вместо этого вам надо скомпилировать свой плагин и положить в папку plugins. Подробнее здесь.

Скрипт

Создайте папку внутри директории plugins с интересующим вас названием плагина. Создайте внутри папки файл plugin.groovy.

Где пишем код

Как уже говорилось, вы можете делать внутри своих файлов всё, что угодно. Для обычных модулей вся инициализация вашего плагина происходит внутри метода initialize. Освобождение всех ресурсов происходит в методе unload.

В случае и скриптов, и сценариев, тело вашего скрипта и является телом метода initialize.

В первую очередь, прочитайте эту статью.

Прочитали? Пошли дальше.

Использование API

Всё API предоставляется через объект pluginProxy, который является аргументом функции initialize. Не пытайтесь вызывать методы других плагинов другими средствами; помните, что ваш плагин может быть отделён от теля DeskChan в любой момент и вообще может быть запущен на другой машине.

Две основные функции, которые вам надо запомнить - это addMessageListener и sendMessage. Первая из них получает сообщения, вторая отправляет. Чтобы не быть голословным, давайте мы всё это продемонстрируем на примерах.

Ввод и вывод

Любая программа подразумевает ввод и вывод данных, это основа. Обычно пользуются консолью, но у нас же не консольное приложение, да? Для этого у нас есть много способов, о которых вы узнаете ниже, но есть четыре самых основных, которые вам надо знать как свои пять пальцев. Итак, первое произносит из уст персонажа вами написанный текст - DeskChan:say.

pluginProxy.sendMessage("DeskChan:say", "Привет!");

Кстати, в случае скриптов и сценариев код будет выглядеть так:

sendMessage("DeskChan:say", "Привет!")

Да, для скриптов прокси указывать не нужно, больше разницы никакой нет.

Второй вариант отобразить информацию - это DeskChan:show-technical. Этот тип сообщения отображается на экране в техническом окне. Вам наверняка понадобится показать на экране что-то непроизносимое, как например лог ошибки, текст регулярного выражения или результаты поиска. Вот для этого вы можете пользоваться этим сообщением так:

pluginProxy.sendMessage("DeskChan:show-technical", "Тут много нечитаемого текста");
pluginProxy.sendMessage("DeskChan:show-technical", new HashMap(){{
    put("name", "WARNING");
    put("text", "А вот этот текст будет выведен с плашкой 'WARNING'.");
}});

Теперь попробуем получить текст пользователя:

pluginProxy.addMessageListener("DeskChan:user-said", (sender, tag, data) -> {
    // вам будут присланы Map<String, Object> в качестве data
    // запросите data.get("value"), чтобы получить текст пользователя
});

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

DeskChan:user-said - это поток с альтернативами, вы можете в него встроиться, чтобы перехватывать речь юзера:

pluginProxy.sendMessage("core:register-alternative, new HashMap(){{
    put("srcTag", "DeskChan:user-said");
    put("dstTag", "yourPlugin:user-said");
    put("priority", 9001);
}});
pluginProxy.addMessageListener("yourPlugin:user-said", (sender, tag, data) -> {
    // вам будут присланы Map<String, Object> в качестве data
    // запросите data.get("value"), чтобы получить текст пользователя
    // далее вы можете передать управление следующей в потоке функции. или не передавать
    // но если бы вы хотели это сделать, то надо делать это так:
    pluginProxy.sendMessage("DeskChan:user-said#yourPlugin:user-said", data);
});

Но не так уж и удобно вешать глобальный прослушиватель на любую речь пользователя. Давайте запрашивать её только тогда, когда она нам нужна:

pluginProxy.sendMessage("DeskChan:request-user-speech", null, (sender, data) -> {
    // вам будут присланы Map<String, Object> в качестве data
    // запросите data.get("value"), чтобы получить текст пользователя
});

Эта функция будет вызвана всего лишь один раз, когда пользователь что-нибудь напишет.

Если что, по всем этим событиям выложена относительно подробная документация, так что мы на будем останавливаться и пойдём дальше.

Хранение данных

Ваш плагин скорее всего будет хранить какие-либо временные данные, например настройки или докачиваемые ресурсы. Где же их хранить, спросите вы? Для этого есть два места: data и assets.

В assets принято хранить все данные кастомизации программы. Вы сами можете туда зайти и проверить, контент какого плана там хранится.

В data нужно хранить временные данные, включая все настройки. Будьте готовы, что пользователь вдруг захочет снести эту папку, чтобы начать работу с DeskChan с чистого листа.

pluginProxy.getDataDirPath();   // это папка, в которой вам следует хранить временные данные
pluginProxy.getAssetsDirPath(); // это папка, в которой хранятся ресурсы программы

Таймеры

Скорее всего, вам понадобится использование таймеров в вашей программе. Почему бы не использовать стандартные?

Мы подумали, что для синхронизации между плагинами, а также оптимизации производительности необходимо использовать единый центр уведомлений о таймерах. На нижнем уровне это работает через core-utils:notify-after-delay, но в интерфейсе плагина предоставляется специальный метод setTimer, который упрощает задание таймеров.

timerId = pluginProxy.setTimer(5000, (sender, data) -> func());

Здесь мы задали таймер на 5 секунд, который вызовет func(). Таймер пройдёт всего один цикл. В timerId будет храниться локальный идентификатор таймера, по которому можно этот таймер отменить в случае необходимости:

pluginProxy.cancelTimer(timerId);

Если вы хотите, чтобы таймер сработал больше одного раза или даже бесконечно, то вызовите другую реализацию данной функции:

timerId = pluginProxy.setTimer(5000, 10, (sender, data) -> func());

Так таймер будет вызван 10 раз. Если вы укажете в качестве второго аргумента -1, то таймер будет вызываться бесконечно, пока вы его не остановите.

Свойства

Часто бывает так, что для плагина нужно хранить какие-либо опции. В случае плагина чата это цвет ваш и цвет собеседника, длина истории сообщений, что-то ещё. Для этого сделана модификация стандартного класса Properties. Работает он точно так же, как стандартный, только этот класс сразу сохраняет все ваши данные в нужную папку data, а ещё вы можете выбирать тип возвращаемого значения.

// это чтобы получить значение указанного типа
pluginProxy.getProperties().getInteger("delay")  
// это чтобы получить значение указанного типа или стандартное, если в файле настроек ничего не указано
pluginProxy.getProperties().getBoolean("flag", false)  
// это чтобы его сохранить. объект не преобразуется в строку, а хранится как есть
pluginProxy.getProperties().set("delay", 5)
// это чтобы загрузить опции из файла с перезаписью
pluginProxy.getProperties().load()
// это чтобы загрузить опции из файла без перезаписи
pluginProxy.getProperties().merge()
// это чтобы сохранить опции в файл
pluginProxy.getProperties().save()

Вообще, если понимаете английский, почитайте больше и про сам класс, и про его модификацию внутри кода. От себя только добавлю, что такой код будет работать:

List<Integer> list = new ArrayList<>();
pluginProxy.getProperties().set("list", list);
list.append(5);
pluginProxy.getProperties().save(); 

После этого в файл будет сохранено:

list = []  // это было бы сохранено стандартным properties
list = [5] // это сохранится модифицированным

Локализация и ресурсы

У нас вообще на будущее задел, что программа будет работать на любом языке. Да, мечты, мечты. Но короче лучше бы вам указывать все текстовые ресурсы внутри Resource Bundle. Можете почитать про них отдельно. В качестве примера как это выглядит изнутри у нас в проекте, можете посмотреть здесь: ссылка. Причём, указывать как минимум в двух экземплярах: русском и английском.

  • Если у вас внутренний модуль, вы должны создать бандл ресурсов внутри папки src/main/resources/info/deskchan. Бандл будет автоматически подключен к проекту.
  • Если у вас внешний модуль, вы должны создать бандл ресурсов внутри папки src/resources, а также прописать в манифесте "Resources: resources.strings".
  • Если у вас скрипт, то вы можете создать папку resources внутри вашего плагина папку resources, а потом внутри вашего скрипта написать следующий код: setResourceBundle("resources").
  • Внутри сценариев нельзя указывать бандлы ресурсов.

Чтобы получить строчку из бандла ресурсов, нужно использовать функцию getString подобным образом:

pluginProxy.getString("load")

В зависимости от указанной локализации программы вам вернётся соответствующий результат.

Помните, что функция getString возвращает строки не только из личного бандла ресурсов, но и из общего для всех плагинов, находящегося в корне src/main/resources/info/deskchan.

Конфиг

Допустим, вы захотите хранить информацию о вашем плагине где-то так, чтобы ядро и другие плагины могло до неё добраться чтобы посмотреть, что вы там такого понаписали. Ну например, кто создатель плагина, на каком языке он написан и что он вообще умеет. А ещё где он хранит свои ресурсы. Такая, техническая информация.

Всё это хранится в объекте класса PluginConfig. Пока что он не шибко используется, так что мало ситуаций, где он вам может понадобится. Давайте приведём пример его использования:

pluginProxy.setConfigField("short-description", getString("plugin.short-description"));

Сейчас в конфиге могут хранится и использоваться следующие поля:

Название поля Описание
dependencies Зависимости вашего плагина, то есть те плагины, без которых ваш не будет работать корректно
extensions Если ваш плагин - загрузчик, то здесь указано, с какими расширениями файлов он работает
type Тип вашего плагина, то есть на каком языке написан. По умолчанию - Internal - внутренний
version Версия вашего плагина
author Автор плагина
short-description Короткое описание
description Полное описание
link Ссылка на документацию плагина (её лучше размещать на форуме)

6 последних полей используются в UI для показа информации о вашем плагине. Последние 2 поля создадут специальную кнопку напротив вашего плагина во вкладке "Плагины", по которой отобразится полная информация.

Кстати, конфиг можно указывать не только через прокси-класс. Для плагинов, находящихся вне ядра, таким конфигом является манифест файл: MANIFEST.MF для внешних модулей и manifest.json для скриптов.

Логирование

Вам обязательно захочется логировать вашу работу. Обязательно. Для этого есть специальные методы:

pluginProxy.log("Loading completed"); // Это выведет в лог текст
pluginProxy.log(error); // Это выведет в лог всю информацию о переданной ошибке

Причём, все эти данные будут напечатаны от имени вашего плагина. Пользуйтесь этим почаще, чтобы потом не было проблем с отладкой вашего плагина.

Команды

Вообще, это очень-очень-очень важно. Вы же прекрасно понимаете, что такая программа, как DeskChan, не может работать вне событийного подхода? Ну вот значит идите и почитайте эту статью: События и команды. А потом эту: Обработка речи. Потом возвращайтесь сюда.

Меню

Вы наверняка захотите сделать себе настроечный экран? Или выкидывать всплывающие окна? Это можно! Только есть одна загвоздка: у нас кроссплатформенное приложение серверного типа, помните? Поэтому вам запрещено напрямую использовать классы графического интерфейса. Ещё раз: запрещено.

Формочки

Поэтому у нас тут что-то типа своего языка графической разметки. Вы можете создавать свои формочки, просто указывая информацию о них.

Все операции с формочками производятся через сообщение gui:set-panel. С помощью неё можно задать любую формочку, а так же способ её отображения на экране. Вместе с сообщением нужно передать в виде конструкции Map следующие поля:

  • id - идентификатор формочки
  • name - название формочки, отображаемое в заголовке
  • type - вид отображения формочки. Возможны следующие варианты:
    • window - Отдельное окно
    • tab - Вкладка в опциях
    • submenu - Подменю плагина, отображаемое во вкладке "Плагины"
    • panel - Панель внутри опций, которую можно открыть только через посыл сообщения или кнопки в опциях
  • action - действие, которое нужно произвести с формочкой. Возможны следующие варианты:
    • set - Создаёт панель/перезаписывает существующую панель со всем содержимым. Обязательно указать controls.
    • show - Отобразить панель.
    • hide - Скрыть панель.
    • update - Обновить содержимое полей формы, не меняя саму структуру формы. Обязательно указать controls.
    • delete - Удалить панель.
  • controls - список элементов формы
  • onSave - если указано, внизу формы появится кнопка "Сохранить", при нажатии на которую на указанный тег будет отправлено сообщение со всеми текущими полями формы
  • onClose - если указано, при закрытии формы на указанный тег будет отправлено сообщение

Пример здесь будет лучше любого описания формата. Вот вполне себе реальный пример из плагина chat_window:

pluginProxy.sendMessage("gui:set-panel", new HashMap<String, Object>() {{
        put("id", "options");
        put("name", pluginProxy.getString("options"));
        put("onSave", "chat:save-options");
        put("type", "submenu");
        put("action", "set");
        List<HashMap<String, Object>> list = new LinkedList<>();
        list.add(new HashMap<String, Object>() {{
            put("id", "fixer");
            put("type", "CheckBox");
            put("label", pluginProxy.getString("fix-layout"));
            put("value", properties.getBoolean("fixer", true));
        }});
        list.add(new HashMap<String, Object>() {{
            put("id", "length");
            put("type", "Spinner");
            put("min", 1);
            put("max", 20000);
            put("label", pluginProxy.getString("log-length"));
            put("value", properties.getInteger("length"));
        }});
        list.add(new HashMap<String, Object>() {{
            put("id", "user-color");
            put("type", "ColorPicker");
            put("msgTag", "chat:set-user-color");
            put("label", pluginProxy.getString("user-color"));
            put("value", properties.getString("user-color"));
        }});
        list.add(new HashMap<String, Object>() {{
            put("id", "user-font");
            put("type", "FontPicker");
            put("label", pluginProxy.getString("user-font"));
            put("value", properties.getString("user-font"));
        }});
        list.add(new HashMap<String, Object>() {{
            put("id", "clear");
            put("type", "Button");
            put("msgTag", "chat:clear");
            put("value", pluginProxy.getString("clear"));
        }});
        put("controls", list);
}});

Каждому control можно указать как минимум следующие поля:

  • id - название элемента, по которому можно потом найти элемент и его значение
  • type - тип элемента
  • msgTag - сообщение, на которое будет направлено значение текущего элемента, когда оно будет изменено (в случае кнопки - нажатие)
  • value - начальное значение элемента
  • disabled - отключено ли поле или нет (по умолчанию - не отключено)
  • label - описание слева элемента. Если его не будет, элемент будет лежать без описания у левой границы окна, а не в центре
  • hint - если указано, то справа вашего элемента появляется маленькая кнопочка, по которой будет высвечиваться подсказка с указанным текстом

В нашем примере при нажатии на кнопку "Сохранить" на тег "chat:save-options" будет отправлено сообщение такого формата (значения взяты с потолка):

Map {
    "fixer": false,
    "length": 10,
    "user-color": 0x663366ff,
    "user-font": "Arial 10"
}

Если вы хотите обновить значение своей формочки, то вы должны отправить в принципе такой же объект типа Map, только указывать надо всего лишь имя формочки, а также список элементов, значение которых хотите изменить, указав для них только id, value и disabled.

Полную информацию по возможностям языка разметки смотрите в этой статье.

Дополнительно

Помимо этого вы можете создавать свои пункты в контекстном меню правой кнопки мыши (и заодно меню трея) через сообщение DeskChan:register-simple-action. Например, открытие того же чата делается через такой код:

pluginProxy.sendMessage("DeskChan:register-simple-action", new HashMap<String, Object>() {{
    put("name", pluginProxy.getString("chat.open"));
    put("msgTag", "chat:open");
}});

А ещё вы можете вызывать уведомления через gui:show-notification

pluginProxy.sendMessage("gui:show-notification", new HashMap<String,Object>(){{
    put("name", getString("error"));
    put("text", getString("info.no-city"));
}});

Обработка речи

К сожалению, нельзя ориентироваться на то, что все ваши проблемы решит модуль, отвечающий за события и команды. Например, яркий случай - плагин, открывающий что-нибудь. Вот у него есть список из 100 сайтов, нам теперь делать 100 связок под каждый сайт и хранить их в опциях? Не юзабельно.

Сопоставлением текста и фраз занимается модуль speech_command_system. Этот плагин предоставляет API аналогичное тому сопоставлению, что работает в чате (кроме извлечения аргументов). Используйте это API, даже если вам просто нужно сравнить два слова. Подробнее как этим пользоваться в самом низу этой статьи.

Общение

Зачастую вы будете попадать в схожие ситуации, в которых вам надо донести какую-либо мысль до пользователя. Например, уточнить его запрос или сообщить об ошибке. Перед тем, как составлять сообщение об ошибке самому, узнайте, можно ли выполнить вашу просьбу через систему характера. Для этого есть специальное сообщение DeskChan:request-say.

Так, например, можно сообщить об ошибочности полученных данных:

pluginProxy.sendMessage("DeskChan:request-say", "WRONG_DATA");

Далее программа за вас найдёт соответствующую фразу.

Если программа нужными вам фразами не обладает, то напишите и подключите свой пакет с фразами, необходимыми вашему плагину, сами, а потом скормите его системе характера специальной командой:

pluginProxy.sendMessage("talk:add-plugin-phrases", getPluginDirPath().resolve('organizer.phrases'))

Вообще, на речь персонажа можно влиять и другими средствами, например через внедрение в стандартный поток DeskChan:request-say. А на поведение персонажа можно влиять через внедрение в поток DeskChan:user-said.

Давайте разберём это на примере: вдруг вам захотелось сделать ленивого персонажа. Как это сделать? Вы же не будете просто постоянно выдавать фразы о том, что он ленивый? Нет, можно придумать что интереснее. Например, чтобы персонаж не хотел выполнять наши команды. Тогда в первую очередь используем одно из двух событий, чтобы получить информацию о персонаже:

// здесь вы будете получать текущий пресет
pluginProxy.addMessageListener("talk:character-updated", ... ); 

// а это используйте, чтобы напрямую запросить пресет
pluginProxy.sendMessage("talk:get-preset", ... ); 

Теперь вы можете внедриться в поток DeskChan:user-said прямо перед функцией speech:get, чтобы отменять выполнение команды. И будете делать это на основании полученных данных о персонаже.

Распространитель ресурсов (данный функционал ещё сырой)

DeskChan всё таки про персонализацию и удобство (ну мы к этому идём, да, не смейтесь). Вдруг пользователю захочется установить персонажа? Ну понятно, что ему надо поставить пресет. А если ещё и скин? А если ещё и правильно настроить плагин, который видоизменяет речь? А может у него ещё один плагин в поставке? Ему что, пол часа ходить по настройкам и всё ставить отдельно?

Конечно, нет. В DeskChan реализован специальный механизм распространения ресурсов. Вы можете загрузить в него файл-инструкцию, который расскажет всем плагинам в системе, как им нужно перенастроиться.

Теперь, как подписаться на такой распространитель? Вы должны создать в своём плагине специальный прослушиватель сообщений структуры "%PLUGINNAME%:supply-resources". Там вы получите объект типа Map, который является инструкцией по настройке. Собственно, с этим объектом можете делать что хотите. Далее вы размещаете в своей документации, какие инструкции вы принимаете. Собственно, на этом всё, инструкций не будет.

Отладка

Система у нас большая, тяжёлая, поэтому и отлаживать её тоже не особо удобно. Так что вот вам только несколько рекомендаций:

  • Вы же помните, что плагины можно загружать и выгружать прямо во время работы программы? Вообще, если у вас какой-нибудь внешний модуль и точно стабильный билд (ээээх, стабильный...), то вы можете даже программу не перезапускать.
  • Режим "Дебаг" в IDE работает из рук вон плохо. Особенно вас может смутить заполнение экрана белым, поскольку окно DeskChan занимает всё доступное пространство и на остановке программы перестаёт реагировать на запросы. Зайдите в класс core_utils/Main, найдите строчку, активирующую TerminalGUI, раскомментируйте её. Теперь DeskChan будет работать полностью в консольном режиме. Прямо туда вы можете вводить команды и отправлять их на Enter. Либо используйте режим отображения персонажа "Разделение на мелкие окна".
  • В ядре есть класс Debug, который содержит всякую мелочёвку, которую удобно использовать во время отладки.

Некоторые особенности Java

Здесь даны скорее рекомендации и советы о том, как делать некоторые вещи с помощью языка.

Работа с файлами

Работа с внешними файлами в Java сделана не самым удобным и однозначным образом, но проще всего работать с ними с помощью классов FileWriter и BufferedReader.

Код для чтения файла:

// Второй аргумент указывает на дозапись
// true - дописать, false - перезаписать	
try(FileWriter file = new FileWriter("C://SomeDir//notes3.txt", false)){
    // Запись всей строки                                  
    String text = "Мама мыла раму, раму мыла мама";  
    file.file(text);
    // Запись отдельных строк или символов
    file.write("line");
    // Переносы строк нужно делать самому
    file.write('\n');

    file.flush();  // Очищение буфера для сохранения данных в файл
} catch(IOException ex) {
    System.out.println(ex.getMessage());
} // После завершения кода в блоке try, автоматически будет вызван метод file.close()

Код для записи в файл:

// BufferedReader позволяет читать файл построчно. FileReader - нет.
try(BufferedReader file = new BufferedReader(new FileReader("C:\\SomeDir\\notes3.txt"))){
    String text, line;
    while((line=file.readLine()) != null) { //пока мы можем получить следующую строку, иначе конец файла
    text += line; // Запись файла в одну переменную
    System.out.println(line);    // Можно работать с прочитаной информацией построчно 		
    System.out.println("-----");
}
System.out.println(text); // Или сразу со всем файлом.
} catch(IOException ex) {  
System.out.println(ex.getMessage());
}

Работа с JSON

Если вам необходимо хранить структурированные данные, то лучше храните их в JSON. Ведь мало ли, вдруг когда-нибудь вы или кто-то другой захочет использовать эти файлы в других модулях на других языках или отредактировать в блокнотике, это самый простой выбор.

JSONArray jsonArr = new JSONArray();
// Конструктору можно передать строку или объект класса Map,
//   jsonObject будет создан с полями переданного аргумента.
// При отсутствии параметров будет создан пустой jsonObject.
JSONObject jsonObj = new JSONObject();
	
// Создать поле с ключом "city" и значением "Москва".
jsonObj.put("city", "Москва");
jsonObj.put("streets", "Петровкская");
	
// Если поле с ключом "streets" существует, то новое значение в месте со  старым образует список.
// Если поле не существует, то оно будет работать аналогично .put().
jsonObj.accumulate("streets", "Заводская");

// Добавить в список элемент "Автозаводская".
jsonArr.add("Автозаводская");
jsonArr.add("Красная");

// Добавить поле с ключом "squares" и значением jsonArr. 
jsonObj.put("squares", jsonArr);
jsonObj.put("area (km2)", 2561.5);
jsonObj.put("capital", true);

// Преобразование объекта json в строку, например чтобы вывести на экран или записать в файл
// Число в скобках означает количество пробелов в каждом уровне вложенности, можно не указывать
String str = jsonObj.toString(2);
/* В результате строка str будет содержать:
*  { "city": "Москва",
*    "streets": ["Петровская", "Заводская"],
*	 "squares": ["Автозаводская", "Красная"],
*    "area (km2)": 2561.5,
*    "capitall": true,
*  }  */

// Теперь прочитаем нашу строку
JSONObject jsonObj2 = new JSONObject(str);
	
// Получить элемент по индексу 0.
String city = json2.get(0);
// Получить эдемент по ключу.
city = json2.get("city");
// Получить значение по ключу преобразованное к boolean.
boolean capital = json2.getBoolean("capital");
// Получить значение по ключу преобразованное к duble.
double area = json2.getDouble("area (km2)");
// Получить значение по ключу преобразованное к String.
city = json2.getString("Name");
// Получить значение по ключу преобразованное к JSONArray.
JSONArray squares =  json2.getJSONArray("squares");

Запрос на сервер

String url = "some.url", query = "/somequery";          
// Создание объекта связанного с указаным url.
URL ur = new URL(url+query);
// Создание потока с данными полученными в результате get запроса.
InputStream stream = ur.openStream();
char[] buffer = new char[1024];
    
// Создание объекта читающего данные из потока как символы.
// Первый аргумент конструктора - поток из которого идёт чтение,
// Второй аргумент - кадировка, в кторой будет считываться символы.
Reader in = new InputStreamReader(stream, "UTF-8");
    
// Чтение символов из потока.
// Первый аргумент - буфер, в который происходит чтение.
// Второй - номер символа, с которого необходимо начинать считывание.
// Третий - кол-во символов, которое необходимо считать.
int nc = in.read(buffer, 0, budder.length());
// Переменная хранит кол-во прочитанных символов.
// Будет равна -1, если был достигнут конец потока.
stream.close();
String result = new String(buffer);
System.out.println(result);

Запуск отдельной программы (или выполнение команды в терминале)

// Создание объекта, управляющего запуском процессом. Все аргументы (если они нужны) указываются ТОЛЬКО через запятую.
ProcessBuilder builder = new ProcessBuilder("someproc", "-arg", 1);    
Process proc = builder.start(); // Запуск процесса.
// Создание потока, для чтения вывода из запущенного процесса.
BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream()));
// Создание потока, для записи текста в запущенный процесс.
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(proc.getOutputStream()));
// Чтение строки из процесса. Текст внутри потока обязательно должен заканчиваться /n. Вернёт null, если в потоке нет строки для чтения. 
String line = reader.readLine();
System.out.println(line);

// Запись строки в процесс.
writer.write("Very very long string with text.");
writer.flush();
// Завершить процесс.
proc.destroy();

Ну, вроде всё.

Clone this wiki locally
You can’t perform that action at this time.