Продолжаем цикл статей по разработке плагина для 1С:EDT для начинающего Java+1C разработчика.
В первой части – //infostart.ru/1c/articles/1311121/ мы рассмотрели, что такое платформа Eclipse, и как создать плагин с помощью конструктора. В этой части мы рассмотрим создание плагина для валидации.
Сразу поясню, что при разработке плагинов принято разделять бизнес-логику и UI-поведение форм по модели MVC, а поскольку бизнес-функция валидации – это серверная часть, то для валидации с квикфиксом мы напишем два плагина:
- один плагин с бизнес-логикой, который расширяет валидацию;
- и второй плагин с UI-поведением формы, который расширяет квикфиксы (его мы рассмотрим в следующей части).
С результатом разработки плагина можно ознакомиться в репозитории https://github.com/marmyshev/edt-bsl-validation
Содержание
- Создание плагина. Семантическое версионирование
- Создание класса для валидации в модулях встроенного языка
- Элемент модели OperatorStyleCreator – реализует конструктор с типом Новый
- Работа с декомплайлером классов
- Принципы валидации
- Работа с типами встроенного языка
- Работа со строковыми литералами
- Параметризация проверочных значений
- Регистрация замечаний по объектам в системе вывода ошибок
- Результат работы плагина
- Видео по созданию плагина проверок/валидации
Описание проблемы
Тему, что валидировать, я выбрал такую – есть стандарт, который говорит о том, что в конструкторе структуры неправильно использовать больше 3-х ключей, потому что это сильно усложняет понимание кода:
При создании объекта типа Структура не рекомендуется передавать в конструктор более 3-х значений свойств. Вместо этого рекомендуется использовать метод Вставить или присваивать значения свойствам явным образом
Но при этом часто бывает так, что разработчик сначала написал в конструкторе структуры 3 ключа, потом кто-то добавил в него еще один ключ (вроде не ухудшилось) и потом еще парочку добавили. В итоге есть существующий код с конструктором структуры, в котором понемногу накопились ключи, и этот код уже нарушает стандарт – страдает его понятность, плюс велика вероятность ошибки при его использовании. Соответственно, все такие случаи хотелось бы находить.
Вот так выглядит проблемная ситуация – у нас есть конструктор создания новой структуры, в котором используется пять ключей с инициализацией для них некоторых значений (не всех).
Давайте напишем плагин, который находит структуры, подходящие под наше правило проблемы.
Создание плагина. Семантическое версионирование
Создаем новый проект.
Это будет проект плагина.
Назовем наш пакет org.mard.dt.bsl.validation, что означает «валидация BSL для EDT».
На втором шаге я сниму галочку UI-плагина и поменяю номер версии 1.0.0 на 0.1.0. В отличие от 1С, где у номеров версий нет явного смысла, в Java нумерация версий важна – здесь для номеров версий действуют четкие правила семантического версионирования, которые помогают сходу понимать состояние проекта.
Например, первая цифра 0 означает, что проект еще сырой. А поскольку мы делаем наш первый макет, в номере версии правильно в качестве первой цифры указывать 0.
И нажимаю кнопку Finish.
Открывается информация о проекте плагина. Здесь я ставлю некоторые галочки, которые будут нам полезны.
Поскольку следом мы будем создавать точку расширения, то система автоматически предложит нам установить галочку «This plug-in is a singleton». Поэтому я сразу ставлю эту галочку по привычке.
Создание класса для валидации в модулях встроенного языка
Далее нам нужно добавить точку расширения, для этого переходим на вкладку Extensions.
В разделе документации «Возможности по работе со встроенным языком» мы находим, что для EDT есть «Расширение валидации в модулях встроенного языка», которому соответствует идентификатор com._1c.g5.v8.dt.bsl.externalBslValidator.
Скопируем его и попробуем сюда добавить.
Поскольку у нашего бандла в зависимостях сейчас нет никаких доступных точек расширения, снимаем галку «Show only extension points from the required plug-ins».
Вставляем идентификатор в поле поиска, выделяем найденный и нажимаем кнопку Finish.
Система у нас спрашивает: «Добавить ли нам плагин BSL сразу в зависимости?», отвечаем «Да». И нужные зависимости в плагине сразу прописываются автоматически.
Назовем наш класс валидации StructureCtorTooManyKeysValidator (проверка, что ключей в конструкторе слишком много).
Нажимаем гиперссылку class и запускается мастер создания нового класса с уже заполненными значениями по умолчанию.
Конструктор мы создавать не будем (галочку Constructors from superclass снимаем), остальное оставляем без изменения. Нажимаем кнопку Finish.
В папке src автоматически появляется файл класса
Система нам предлагает добавить методы, которые реализуют определенный интерфейс. Нажимаем гиперссылку «Add unimplemented methods».
Для класса создается метод needValidation. Его параметр назовем object.
Это к вопросу о том, можно ли разрабатывать плагины для EDT в другой IDE, например, в IDEA. Eclipse заточен под то, чтобы быстро и удобно создавать плагины, и, если вы будете разрабатывать в другой IDE, то всего этого у вас не будет. Да, вы можете все прописать вручную, но это будет дольше, неудобнее, надо будет знать и помнить, куда и что прописать.
Элемент модели OperatorStyleCreator – реализует конструктор с типом Новый
Разберемся, к чему привязывать валидацию, а для этого вернемся к документации, которая описывает нашу модель. Когда вы поработаете с JDT подольше, вам не нужно будет идти таким длинным путем, но сейчас давайте распишем длинный путь.
Нам нужен элемент модели для конструктора с типом Новый. Такой элемент модели называется OperatorStyleCreator – что-то, создающее оператор стиля. В документации мы видим, что у него есть методы getType(), setType() и getParams() – здесь указана вся информация, что они делают.
Копируем этот элемент модели и добавляем его сразу в импорт.
При этом мы видим, что каких-то пакетов нам в зависимостях не хватает, и система сама подсказывает, что нужно их добавить.
Соглашаемся.
Здесь есть еще один момент, который желательно изучить. В Java есть две концепции версионирования – по бандлам и по пакетам. Это зависит от того, какую стратегию принимает проект – в зависимости от этого импортируются бандлы или пакеты. Это нужно учитывать.
- Проекты платформы Eclipse, EMF и прочего версионируются по бандлам.
- А проекты EDT версионируются по пакетам.
Поэтому добавляем в зависимости пакет, который называется com._1c.g5.v8.dt.bsl.model (из которого мы скопировали OperatorStyleCreator).
Вопросы
- Что такое бандл?
- По-простому, бандл – это один скомпилированный jar-файл. Есть некоторые нюансы, но мы в них углубляться не будем. Для простого понимания этого достаточно. При этом бандл может содержать в себе какое-то количество пакетов.
- Как понять про бандл – он UI или не UI?
- Обычно схема простая – в конце имени вашего бандла добавляйте ui.
- А как решить для себя – мой бандл будет UI или не UI?
- Если вы используете точки расширения из ui – то, скорее всего, у вас бандл тоже будет ui. Все, что расширяет ui, должно быть бандлом ui. Если вы используете из target-платформы что-то, что относится к оконной системе или к интерфейсу – это UI. Неявно Debug, Help и вся оконная система со всеми ее классами – это все UI. Если вы все это используете, то это бандл UI.
- Получается, что, если мы видим ui в названии пакета, то мы считаем, что это UI?
- Я считаю, что, если есть сомнения, лучше почитать исходный проект, посмотреть, что происходит внутри этого проекта, и какие у него зависимости.
Работа с декомплайлером классов
Мы имплементили интерфейс IExternalBslValidator и чтобы понять, как с ним работать, нам нужно посмотреть, какие у него есть методы и т.д.
В первой части у меня был слайд со списком дополнительных плагинов. И первый из них, с подписью «Это нам нужно» – это плагин «Enhanced Class Decompiler». Он как раз показывает все интерфейсы, которые поставляются без исходников. Стандартно Eclipse их тоже показывает, но стандартное представление смотреть не очень удобно. Поэтому ставим плагин «Enhanced Class Decompiler» и даже если интерфейс поставляется без исходников, нажав F3 в модуле JDT мы увидим описание сигнатуры в нормальном человекочитаемом виде. А смысловое описание того, что делает класс, можно посмотреть в документации EDT.
Теперь провалимся в определение интерфейса IExternalBslValidator – нажмем F3 или вызовем пункт в контекстном меню.
Вот так выглядит его стандартный просмотр средствами Eclipse.
Чтобы по умолчанию открывалось в декомпилированном виде, нужно в настройках поменять редактор для классов без исходников на «Class Decompiler Viewer» – выбрать и нажать кнопку Default.
Теперь текст таких файлов будет открываться средствами декомпайлера – в привычном человекочитаемом виде.
Давайте скопируем из интерфейса валидатора нужный нам метод, потому что в исходниках он указан без обработки и автоматически при инициализации класса в текст не подставляется.
Есть еще некоторые настройки редактора, которые нам помогут – включим автоматический импорт зависимостей при сохранении.
Принципы валидации
Из документации мы знаем, что любой элемент модели можно валидировать, поэтому в методе needValidation мы будем делать проверку на то, подходит ли нам этот объект.
С учетом того, что разработчики EDT анонсировали уже новую систему валидации, настоятельно вам советую в теле метода needValidation писать что-то очень простое, на уровне проверки типа класса – никакую дополнительную логику не вставлять. Здесь должна быть очень легкая быстрая проверка, нужно ли что-то запускать дальше.
Если нам нужно выполнить какие-то дополнительные расчеты с тем, чтобы как-то проверить, действительно ли нам этот объект подходит, в нужном ли он проекте и т.д. – это все лучше сразу вставлять в само тело валидации.
Здесь мы проверяем, что объект, который к нам приходит, нужного нам класса (return object instanceof OperatorStyleCreator).
Неизвестный тип CustomValidationMessageAcceptor импортируем в наш класс.
А по поводу CancelIndicator система пишет: «The type org.eclipse.xtext.util.CancelIndicator cannot be resolved», поэтому импортируем его вручную и устанавливаем бандл в проект.
Еще раз про версионирование – Xtext и eCore проекты версионируются по бандлам. Поэтому в зависимости нужно добавлять именно бандлы, а не пакеты (пакеты поставляются без версий).
Дальше – правилом хорошего тона в валидации является проверка отказа. Если пользователь начал действие, а потом нажал «Отмена», вы должны на это среагировать как можно раньше. Поэтому во всяких разных циклах обычно вставляется такой код – проверяется if (monitor.isCanceled()) return.
Работа с типами встроенного языка
Дальше – поскольку мы проверили, что наш объект это уже точно OperatorStyleCreator, давайте его обработаем.
Сначала нам нужно проверить, что наш оператор создает структуру. Для этого вызываем метод getType() класса OperatorStyleCreator – этот метод возвращает тип создаваемого объекта.
Вызов метода getType() потребовал добавить пакет com._1c.g5.v8.dt.mcore в зависимости – добавляем.
Чтобы определить, что возвращенный тип соответствует структуре, вызываем для него метод getName() и сравниваем полученное значение со значением константы STRUCTURE из класса IEObjectTypeNames (в этом классе содержится описание всех типов).
Класс IEObjectTypeNames содержится в пакете com._1c.g5.v8.dt.platform, поэтому добавим этот пакет в зависимости и импортируем его в наш класс.
Откроем класс IEObjectTypeNames по F3 – это большой класс со всеми типами.
Обратите внимание, тип является двуязычным, поэтому, наверное, хорошо, когда вы представляете, с чем вы сейчас работаете – с текстом из BSL, который может быть написан на каком-то из вариантов скрипта, или уже с объектом, который имеет сразу встроенную двуязычность.
Из класса IEObjectTypeNames нам нужно значение константы STRUCTURE, которое соответствует представлению типа структуры на английском.
Это значение мы будем сравнивать в результатом метода getName(), который возвращает имя полученного из параметра типа на английском (для русских имен есть метод getNameRu()).
Работа со строковыми литералами
Дальше нам необходимо также проверить, что у этого оператора создания есть параметры, и они не пустые. И что первый параметр – это строковый литерал (потому что мы можем при создании передать переменную, куда собрать все ключи). Поэтому здесь мы четко проверяем, что первый параметр является строковым литералом.
Тип строкового литерала тоже нужно импортировать к нам в класс.
Когда мы убедились, что оператор создания соответствует структуре, его параметры не пустые и первый параметр – это строковый литерал, мы получаем этот литерал, чтобы обработать его дальше.
Обратите внимание, мы здесь работаем не с текстом, а именно с моделью модуля.
Теперь нам нужно получить контент строкового литерала.
Здесь все может быть довольно сложно. Для понимания – строковый литерал в языке BSL – это многострочный объект, у которого каждая строка может быть отформатирована по-своему, содержать табуляции и прочее. Поэтому работать со строковыми литералами надо по-особенному.
Давайте получим контент строкового литерала, лишенный всяких вертикальных черточек, кавычек и прочего.
Для получения просто исходных строк со всеми кавычками, вертикальными черточками и прочим у строкового литерала есть метод getLines(). Но мы будем использовать специальный метод lines(), который выполняет автоматический парсинг и преобразование текста и может даже сделать тримминг (сокращение всех концевых пробелов) – нам для нашей задачи нужен только реальный контент, поэтому все концевые пробелы нам не интересны.
То есть метод lines() возвращает список строк, которые мы можем взять и сразу объединить в одну строку. Нам неинтересно знать, сколько было строк реально в строковом литерале – нам в данном случае важен контент, мы можем просто заджойнить полученные строки.
Дальше извлечем из этого контента литерала ключи. На всякий случай мы уберем дополнительные пробелы, которые здесь могут быть, и разобьем строку в массив по определенному паттерну (по запятой).
В результате мы получим массив ключей, переданный в структуру.
Параметризация проверочных значений
Дальше – давайте для безопасности проверим, что пока мы парсили, не нажали кнопку отмена и количество ключей у нас превышает допустимое.
Зададим константу MAX_STRUCTURE_KEYS – ее здесь можно создать автоматически.
По стандарту у нас максимальное количество ключей – 3. Собственно, если ключей больше 3 давайте будем выдавать предупреждение – вешать варнинг на какой-то наш объект.
Регистрация замечаний по объектам в системе вывода ошибок
Итак, что мы будем сообщать пользователям?
Хороший пример – это использовать систему форматирования сообщений. Есть удобный класс, который позволяет форматировать нужный нам текст. Давайте напишем текст: «Структура в конструкторе содержит больше, чем {0} ключей».
Вставим шаблон, куда нам нужно вставить наш параметр. И в будущем нам с помощью такой шаблонизации легко и удобно будет делать локализацию этих текстов на разные языки.
Импортируем MessageFormat в наш класс.
Дальше у нас есть специальный класс-помощник messageAcceptor, который позволяет легко и удобно зарегистрировать какой-то варнинг на найденный нами объект.
И тут у нас вопрос – на что реально вешать? На сам конструктор «Новый…» (OperatorStyleCreator) или на его первый параметр (строковый литерал)?
Я бы в данном случае пошел по пути того, что нам дальше нужно будет обрабатывать этот строковый литерал, и при его изменении нужно будет сбрасывать ошибку. Соответственно, будет логичнее повесить эту ошибку на строковый литерал.
- Будем вешать варнинг, поскольку это не влияет на работоспособность программы, это влияет на читаемость кода.
- Первым параметром будет наш message,
- в качестве источника мы выбираем literal.
- A feature – это такая штука, которая тянется из EMF-системы. Советую вам в дальнейшем хорошо поизучать, что такое feature – у каждого объекта есть его определенные возможности, которые описываются моделью EMF.
Сейчас мы просто вставим нужный код, а потом я советую вам реально поизучать, что такое EMF, и как оно работает.
Все фичи хранятся и описываются в бандле com._1c.g5.v8.dt.bsl.model.BslPackage.Literals.STRING_LITERAL__LINES с описанием bsl-модели.
Поэтому мы эту штуку импортируем статично, чтобы использовать STRING_LITERAL__LINES в messageAcceptor. А дальше вы потом можете поизучать, какие бывают фичи у объектов.
Что нам еще дополнительно нужно здесь указать? Правилом хорошего тона для валидации является указание идентификатора проверки – его обычно называют ERROR_CODE.
Давайте создадим эту константу и напишем ее значение – это StructureConstructorHasTooManyKeys.
Заключительные приготовления
У нас выводится предупреждение, что пакет org.eclipse.xtext.validation еще не добавлен.
Пытаемся найти в списке бандлов пакет org.eclipse.xtext.validation, но его здесь нет.
Дело в том, что xtext версионируется по бандлам, поэтому ищем среди бандлов org.eclipse.xtext и добавляем его в зависимые бандлы.
Результат работы плагина
Больше никаких варнингов нет. Теперь давайте запустим.
На этом этапе наша проверка закончена. Мы нашли наш проблемный объект, повесили на него варнинг, и с этим классом мы, наверное, можем больше не работать, мы добились своей первой цели – ткнуть разработчика носом в ошибку.
Итак, мы запустили нашу конфигурацию, и видим, что нам на строковый литерал выводится сообщение «Structure constructor has more than 3 keys» (структура имеет больше, чем три ключа) – у нас здесь пять ключей.
Это был самый простой вариант валидации, когда мы никуда из этой функции и этого модуля не смотрим, никакие данные дополнительно не запрашиваем. В будущем можно было бы написать валидацию, которая будет читать объекты метаданных и в зависимости от каких-нибудь настроек что-нибудь валидировать. Но пока у нас самый простой вариант, который смотрит только в эту функцию и оценивает состояние одного только строкового литерала.
Сейчас, как мы указали разработчику на ошибку, хотелось бы, чтобы мы помогли ее автоматически быстро исправить. Поэтому следующим логичным этапом будет квикфикс.
Видео по созданию плагина проверок/валидации
В следующей части рассмотрим создание UI-плагина с квикфиксом для 1С:EDT.
****************
Статья подготовлена совместно с редакцией Инфостарта на основе серии обучающих видео по созданию плагинов для 1С:EDT.