OCL
Текст может содержать неточности, устаревшие данные или технические ошибки. Пожалуйста, проверяйте критически важную информацию перед использованием.
Object Constraint Language — стандартный язык записи инвариантов. Выражение только читает модель и не изменяет её. Язык статически типизирован: выражение разбирается относительно метакласса контекстного объекта, поэтому опечатку в характеристике или несовместимое сравнение видно ещё до запуска.
Как пишется выражение
Тело на OCL — это одиночное выражение-запрос. Навигация по характеристике записывается точкой, операции над коллекциями — стрелкой:
self.name <> null
self.outgoingRelationships->size() > 0
Контекст выражения — метакласс self: характеристики этого типа доступны
напрямую и предлагаются автодополнением.
Видимые переменные
self — объект, на котором вычисляется выражение, и единственная переменная,
связываемая извне. В правиле проверки его тип — метакласс, для которого
объявлено правило. Какой это объект, зависит от того, где выражение выполняется,
— см. Консоль выражений.
self можно не указывать: имя, не связанное иным образом, ищется среди
характеристик self, поэтому name и self.name — одно и то же выражение.
let вводит дополнительные имена внутри самого выражения; тип такой переменной
указывается явно:
let n : String = self.name in n <> null and n.size() > 3
Проверки типов и условия
Типы уточняются пакетом метамодели, например archimate::ServingRelationship.
oclIsKindOf(T) проверяет тип и его подтипы, oclIsTypeOf(T) — точный тип, а
oclAsType(T) сужает значение и открывает характеристики более узкого типа.
Условие — это тоже выражение, и endif обязателен:
if self.documentation = null then false else self.documentation.size() > 0 endif
implies истинно всегда, когда левая часть ложна: так записывают проверку,
неприменимую к объекту.
Примеры
Для ArchiMate, где self — элемент:
У элемента есть имя:
self.name <> null and self.name.size() > 0
У каждого элемента есть свойство owner:
self.properties->exists(p | p.key = 'owner')
Документированный элемент должен быть ещё и именованным:
self.documentation <> null implies self.name <> null
Ничто не обслуживает этот элемент дважды:
self.incomingRelationships
->select(r | r.oclIsKindOf(archimate::ServingRelationship))
->isUnique(r | r.source)
Каждая исходящая реализация заканчивается на бизнес-сервисе:
self.outgoingRelationships
->select(r | r.oclIsKindOf(archimate::RealizationRelationship))
->forAll(r | r.target.oclIsKindOf(archimate::BusinessService))
Имена всего, от чего зависит этот элемент:
self.outgoingRelationships->collect(r | r.target.name)
Элемент либо связан, либо явно задокументирован как самостоятельный:
let connected : Boolean =
self.incomingRelationships->notEmpty()
or self.outgoingRelationships->notEmpty()
in connected or self.properties->exists(p | p.key = 'standalone')
Для ArchiMate, где self — модель:
Модель формулирует своё назначение:
self.purpose <> null and self.purpose.size() > 0
Каждая папка верхнего уровня именована:
self.folders->forAll(f | f.name <> null and f.name.size() > 0)
Дополнительные материалы
Спецификация OMG Object Constraint Language
Справочник по языку
Форма выражения
Тело — это одно выражение-запрос. В нём нет операторов, нет объявлений вне
выражения и нет присваивания. let и переменные, вводимые итератором, —
единственные способы дать значению имя.
self — единственная переменная, связываемая извне. Там, где требуется
логическое значение, выражение любого другого типа отмечается как ошибка ещё до
запуска.
Метаклассы берутся из метамоделей запрашиваемой модели. Пакет ecore доступен
всегда, поэтому ecore::EObject и прочие метаклассы Ecore можно называть в
любом выражении.
| Синтаксис | Значение |
|---|---|
-- text | комментарий до конца строки |
/* text */ | комментарий, занимающий несколько строк |
_'name' | имя, которое иначе конфликтовало бы с ключевым словом — self._'name' |
Приоритет операций
От высшего к низшему. Операции в одной строке связывают слева направо.
| Операции | Категория |
|---|---|
:: | уточнение имени пакетом или типом |
. -> | навигация по характеристике, вызов операции, вызов итератора |
not, унарный - | логическое отрицание, арифметическое отрицание |
* / | умножение, деление |
+ - | сложение, вычитание |
< > <= >= | сравнение по порядку |
= <> | равенство, неравенство |
and | конъюнкция |
or | дизъюнкция |
xor | исключающая дизъюнкция |
implies | импликация |
and связывает сильнее, чем or, а or — сильнее, чем xor, поэтому
true or true and false даёт true, а true xor true or true даёт false.
Конструкция if ... endif — самостоятельный операнд:
if true then 1 else 2 endif + 1 даёт 2.
Литералы
| Синтаксис | Тип | Значение |
|---|---|---|
42 | Integer | целочисленный литерал |
1.5 | Real | вещественный литерал |
1.5e-2, 1e3 | Real | вещественный литерал с порядком |
* | UnlimitedNatural | неограниченное значение |
'text' | String | строковый литерал |
true, false | Boolean | логические литералы |
null | OclVoid | отсутствующее значение |
invalid | OclInvalid | ошибочное значение |
Set{1, 2, 3} | Set(T) | без порядка, без повторов |
OrderedSet{1, 2} | OrderedSet(T) | с порядком, без повторов |
Bag{1, 1, 2} | Bag(T) | без порядка, повторы сохраняются |
Sequence{1, 2, 3} | Sequence(T) | с порядком, повторы сохраняются |
Sequence{1..5} | Sequence(Integer) | все целые числа из диапазона |
Set{} | Set(T) | пустая коллекция этого вида |
Tuple{a = 1, b = 'x'} | Tuple(...) | кортеж, типы частей выводятся |
Tuple{a : Integer = 1} | Tuple(...) | кортеж с объявленным типом части |
Часть кортежа читается через точку: Tuple{a = 1, b = 2}.a даёт 1.
Внутри строкового литерала допустимы экранирования \b, \t, \n, \f, \r,
\', \" и \\. Любую другую обратную косую черту разборщик отвергает,
поэтому косую черту, которая должна дойти до регулярного выражения, записывают
как \\. Два строковых литерала, записанных рядом, сливаются в один:
'ab' 'cd' даёт 'abcd'.
Синтаксис типов
| Синтаксис | Значение |
|---|---|
archimate::BusinessActor | метакласс, уточнённый пакетом своей метамодели |
String, Integer, Real, Boolean, UnlimitedNatural | примитивные типы |
OclAny | тип, которому соответствует любое значение |
OclVoid, OclInvalid | типы значений null и invalid |
Set(T), OrderedSet(T), Bag(T), Sequence(T) | типы коллекций |
Collection(T) | любой из четырёх типов коллекций |
Tuple(a : Integer, b : String) | тип кортежа |
Операции над любым значением
Доступны для любого одиночного значения независимо от его типа.
| Синтаксис | Результат | Значение |
|---|---|---|
a = b | Boolean | равенство |
a <> b | Boolean | неравенство |
v.oclIsKindOf(T) | Boolean | v является T или подтипом T |
v.oclIsTypeOf(T) | Boolean | v является в точности T |
v.oclAsType(T) | T | v, рассматриваемое как T, так что характеристики T доступны |
v.oclIsUndefined() | Boolean | v есть null или invalid |
v.oclIsInvalid() | Boolean | v есть invalid |
v.oclAsSet() | Set(T) | v как множество из одного элемента, пустое при v, равном null |
v.toString() | String | строковое представление v |
T.allInstances() | Set(T) | все экземпляры T в модели, содержащей self |
allInstances() принимает и форму с ::, например
archimate::BusinessActor::allInstances().
Коллекция несёт =, <>, oclIsUndefined(), oclIsInvalid() и oclAsSet();
на коллекции oclAsSet() оборачивает саму коллекцию, поэтому
Set{1, 2}->oclAsSet()->size() даёт 1. oclIsKindOf, oclIsTypeOf и
oclAsType применимы только к одиночным значениям. Чтобы проверить тип
элементов коллекции, примените oclIsKindOf внутри итератора либо
воспользуйтесь selectByKind.
Операции над строками
Позиции нумеруются с единицы, а границы substring включаются.
| Синтаксис | Результат | Значение |
|---|---|---|
s + t | String | конкатенация |
s.concat(t) | String | конкатенация |
s.size() | Integer | число символов |
s.at(i) | String | символ на позиции i — 'abc'.at(1) даёт 'a' |
s.substring(lower, upper) | String | 'abc'.substring(1, 2) даёт 'ab' |
s.characters() | Sequence(String) | по одной записи на символ |
s.indexOf(t) | Integer | позиция первого t, 0 при отсутствии |
s.lastIndexOf(t) | Integer | позиция последнего t, 0 при отсутствии |
s.startsWith(t) | Boolean | s начинается с t |
s.endsWith(t) | Boolean | s заканчивается на t |
s.equalsIgnoreCase(t) | Boolean | равенство без учёта регистра |
s.matches(regex) | Boolean | вся строка s соответствует регулярному выражению |
s.replaceAll(regex, r) | String | каждое совпадение regex заменено на r |
s.replaceFirst(regex, r) | String | первое совпадение regex заменено на r |
s.substituteAll(regex, r) | String | каждое совпадение regex заменено на r |
s.substituteFirst(regex, r) | String | первое совпадение regex заменено на r |
s.tokenize() | Sequence(String) | разбиение по пробельным символам |
s.tokenize(delimiters) | Sequence(String) | разбиение по любому символу из delimiters |
s.tokenize(delimiters, keep) | Sequence(String) | разбиение с сохранением разделителей, если keep равно true |
s.trim() | String | без ведущих и завершающих пробельных символов |
s.toUpperCase(), s.toUpper() | String | верхний регистр |
s.toLowerCase(), s.toLower() | String | нижний регистр |
s.toInteger() | Integer | разобрано как целое число |
s.toReal() | Real | разобрано как вещественное число |
s.toBoolean() | Boolean | разобрано как логическое значение |
s < t, s <= t, s > t, s >= t | Boolean | лексикографическое сравнение |
Аргумент-шаблон операций matches, replaceAll, replaceFirst,
substituteAll и substituteFirst — это регулярное выражение, и обратные косые
черты в нём удваиваются, чтобы разборщик строки их сохранил:
'a.b'.replaceAll('\\.', '+') даёт 'a+b'.
Операции над числами
Для Integer, Real и UnlimitedNatural. / даёт Real, поэтому 3 / 2
равно 1.5.
| Синтаксис | Результат | Значение |
|---|---|---|
a + b, a - b, a * b | Integer или Real | арифметика |
a / b | Real | деление |
-a | Integer или Real | отрицание |
a.abs() | Integer или Real | модуль |
a.max(b), a.min(b) | Integer или Real | большее, меньшее |
a.floor() | Integer | наибольшее целое, не превосходящее a |
a.round() | Integer | ближайшее целое |
a.div(b) | Integer | целочисленное деление — 7.div(2) даёт 3 |
a.mod(b) | Integer | остаток — 7.mod(2) даёт 1 |
n.toInteger() | Integer | UnlimitedNatural как Integer |
a < b, a <= b, a > b, a >= b | Boolean | сравнение по порядку |
* — неограниченное значение UnlimitedNatural, и *.toInteger() даёт
invalid.
Логические операции
| Синтаксис | Результат | Значение |
|---|---|---|
a and b | Boolean | конъюнкция |
a or b | Boolean | дизъюнкция |
a xor b | Boolean | истинно, когда истинна ровно одна сторона |
not a | Boolean | отрицание |
a implies b | Boolean | истинно всегда, когда a ложно |
Виды коллекций
| Тип | Порядок | Повторы |
|---|---|---|
Set(T) | нет | нет |
OrderedSet(T) | да | нет |
Bag(T) | нет | да |
Sequence(T) | да | да |
-> на значении, которое коллекцией не является, рассматривает его как
коллекцию из одного элемента, поэтому 'abc'->size() даёт 1, тогда как
'abc'.size() даёт 3.
Операции над любой коллекцией
Доступны для Set, OrderedSet, Bag и Sequence.
| Синтаксис | Результат | Значение |
|---|---|---|
c->size() | Integer | число элементов |
c->isEmpty() | Boolean | в c нет элементов |
c->notEmpty() | Boolean | в c есть хотя бы один элемент |
c->count(x) | Integer | число вхождений x |
c->includes(x) | Boolean | x является элементом c |
c->excludes(x) | Boolean | x не является элементом c |
c->includesAll(d) | Boolean | каждый элемент d есть в c |
c->excludesAll(d) | Boolean | ни одного элемента d нет в c |
c->sum() | Integer или Real | сумма элементов |
c->max(), c->min() | Integer или Real | наибольший, наименьший элемент |
c->product(d) | Set(Tuple(first, second)) | все пары элемента c с элементом d |
c->selectByKind(T) | коллекция T | элементы, являющиеся T или подтипом |
c->selectByType(T) | коллекция T | элементы, являющиеся в точности T |
c->including(x) | см. ниже | c с добавленным x |
c->excluding(x) | см. ниже | c без всех вхождений x |
c->flatten() | см. ниже | вложенные коллекции слиты на один уровень |
sum(), max() и min() применимы к коллекциям чисел. including(),
excluding() и flatten() дают Set из Set или OrderedSet, Bag из Bag
и Sequence из Sequence, поэтому OrderedSet{1, 2}->including(3) даёт Set.
Преобразования
Доступны для Set, OrderedSet, Bag и Sequence.
| Синтаксис | Результат | Значение |
|---|---|---|
c->asSet() | Set(T) | порядок и повторы отброшены |
c->asOrderedSet() | OrderedSet(T) | повторы отброшены, порядок сохранён |
c->asBag() | Bag(T) | порядок отброшен, повторы сохранены |
c->asSequence() | Sequence(T) | порядок и повторы сохранены |
Операции над Set и OrderedSet
| Синтаксис | Результат | Значение |
|---|---|---|
c->union(d) | Set(T) или Bag(T) | все элементы обеих сторон; Bag, когда d является Bag |
c->intersection(d) | Set(T) | элементы, входящие в обе стороны |
c - d | Set(T) | элементы c, которых нет в d |
c->symmetricDifference(d) | Set(T) | элементы ровно одной из сторон |
Операции над Bag
| Синтаксис | Результат | Значение |
|---|---|---|
c->union(d) | Bag(T) | все элементы обеих сторон, повторы сохраняются |
c->intersection(d) | Bag(T) | элементы, входящие в обе стороны |
Операции над упорядоченными коллекциями
Доступны для OrderedSet и Sequence. Позиции нумеруются с единицы.
| Синтаксис | Результат | Значение |
|---|---|---|
c->first() | тип элемента | первый элемент |
c->last() | тип элемента | последний элемент |
c->at(i) | тип элемента | элемент на позиции i |
c->indexOf(x) | Integer | позиция x |
c->append(x) | тот же вид | c с добавленным в конец x |
c->prepend(x) | тот же вид | c с добавленным в начало x |
c->insertAt(i, x) | тот же вид | c со вставленным на позицию i элементом x |
c->subOrderedSet(lower, upper) | OrderedSet(T) | элементы между двумя включёнными позициями |
c->subSequence(lower, upper) | Sequence(T) | элементы между двумя включёнными позициями |
subOrderedSet применяется к OrderedSet, subSequence — к Sequence.
Sequence объединяется только с другой Sequence, причём не сливает элементы,
а сцепляет их: Sequence{1,2}->union(Sequence{3}) даёт Sequence{1, 2, 3}.
Итераторы
Тело отделяется от переменной итератора знаком |. Тип переменной можно указать
явно — c->select(e : archimate::Element | ...) — либо оставить выводимым.
| Синтаксис | Результат | Значение |
|---|---|---|
c->select(e | body) | тот же вид, что у c | элементы, для которых body истинно |
c->reject(e | body) | тот же вид, что у c | элементы, для которых body ложно |
c->collect(e | body) | Bag(T) или Sequence(T) | body для каждого элемента, вложенные результаты слиты на один уровень |
c->collectNested(e | body) | Bag(T) или Sequence(T) | body для каждого элемента, без слияния |
c->forAll(e | body) | Boolean | body истинно для каждого элемента |
c->exists(e | body) | Boolean | body истинно хотя бы для одного элемента |
c->one(e | body) | Boolean | body истинно ровно для одного элемента |
c->any(e | body) | тип элемента | один элемент, для которого body истинно |
c->isUnique(e | body) | Boolean | body даёт разные значения для всех элементов |
c->sortedBy(e | body) | OrderedSet(T) или Sequence(T) | элементы, упорядоченные по body |
c->closure(e | body) | Set(T) или OrderedSet(T) | body применяется снова и снова, пока не перестанет находиться новое |
c->iterate(e; acc : T = init | body) | T | свёртка body по элементам с переносом acc |
Тело операций select, reject, forAll, exists, one и any должно иметь
тип Boolean, а тело sortedBy — тип, поддерживающий <.
select и reject сохраняют вид исходной коллекции. collect и
collectNested дают Bag из Set или Bag и Sequence из Sequence или
OrderedSet. sortedBy даёт OrderedSet из Set или OrderedSet и
Sequence из Bag или Sequence. Тело closure должно давать тип элемента
исходной коллекции либо коллекцию таких элементов. closure даёт OrderedSet,
когда тело является OrderedSet или Sequence, и Set во всех остальных
случаях.
forAll и exists принимают две переменные итератора и тогда перебирают все
пары:
self.folders->forAll(f1, f2 | f1 <> f2 implies f1.name <> f2.name)
iterate задаёт имя накопителя, его тип и начальное значение. Тип накопителя
указывается явно:
self.folders->iterate(f; total : Integer = 0 | total + f.elements->size())
Навигация по модели
| Синтаксис | Результат | Значение |
|---|---|---|
self.feature | тип характеристики | значение характеристики |
name | тип характеристики | то же самое, с неявным self |
c.feature | Bag или Sequence | характеристика каждого элемента — то же, что c->collect(e | e.feature) |
T.allInstances() | Set(T) | все экземпляры T в модели, содержащей self |
v.oclAsType(T) | T | сужает v, делая доступными характеристики T |
c->selectByKind(T) | коллекция T | элементы c, являющиеся T или подтипом |
c->closure(e | e.feature) | Set(T) или OrderedSet(T) | проходит по feature транзитивно |
До любого объекта вы добираетесь через характеристики, объявленные его
метаклассом. Контейнер, вложение и обратная ссылка читаются так же, как любая
другая характеристика. В ArchiMate элемент находит указывающие на него отношения
через incomingRelationships, а модель — своё содержимое через folders. Там,
где метамодель не объявляет характеристику для связи, allInstances() вместе с
select находит объекты, ссылающиеся на self:
archimate::Relationship::allInstances()->select(r | r.target = self)
Навигация по характеристике значения null даёт invalid, а не null, поэтому
self.parent.name даёт invalid, когда parent не задан. oclIsUndefined()
проверяет оба случая, а -> на незаданной характеристике даёт пустую коллекцию,
поэтому self.parent->isEmpty() истинно.
Условия и объявления переменных
| Синтаксис | Результат | Значение |
|---|---|---|
if cond then a else b endif | тип a и b | условие; обе ветви и endif обязательны |
let n : T = value in body | тип body | связывает n со значением value на всём протяжении body |
let a : T = x, b : T = y in body | тип body | связывает несколько имён сразу |
Переменная let объявляется вместе со своим типом; тип нельзя опустить.
Выражения let вкладываются друг в друга, и одно из них может находиться внутри
тела итератора:
self.folders->select(f | let n : String = f.name in n <> null and n.size() > 3)