Перейти к основному содержимому

OCL

Этот документ был подготовлен при помощи ИИ

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

Object Constraint Language — стандартный язык записи инвариантов. Выражение только читает модель и не изменяет её. Язык статически типизирован: выражение разбирается относительно метакласса контекстного объекта, поэтому опечатку в характеристике или несовместимое сравнение видно ещё до запуска.

Инвариант на OCL в консоли и ответ на него — «да» или «нет»

Как пишется выражение

Тело на 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.

Литералы

СинтаксисТипЗначение
42Integerцелочисленный литерал
1.5Realвещественный литерал
1.5e-2, 1e3Realвещественный литерал с порядком
*UnlimitedNaturalнеограниченное значение
'text'Stringстроковый литерал
true, falseBooleanлогические литералы
nullOclVoidотсутствующее значение
invalidOclInvalidошибочное значение
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 = bBooleanравенство
a <> bBooleanнеравенство
v.oclIsKindOf(T)Booleanv является T или подтипом T
v.oclIsTypeOf(T)Booleanv является в точности T
v.oclAsType(T)Tv, рассматриваемое как T, так что характеристики T доступны
v.oclIsUndefined()Booleanv есть null или invalid
v.oclIsInvalid()Booleanv есть 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 + tStringконкатенация
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)Booleans начинается с t
s.endsWith(t)Booleans заканчивается на 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 >= tBooleanлексикографическое сравнение

Аргумент-шаблон операций matches, replaceAll, replaceFirst, substituteAll и substituteFirst — это регулярное выражение, и обратные косые черты в нём удваиваются, чтобы разборщик строки их сохранил: 'a.b'.replaceAll('\\.', '+') даёт 'a+b'.

Операции над числами

Для Integer, Real и UnlimitedNatural. / даёт Real, поэтому 3 / 2 равно 1.5.

СинтаксисРезультатЗначение
a + b, a - b, a * bInteger или Realарифметика
a / bRealделение
-aInteger или 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()IntegerUnlimitedNatural как Integer
a < b, a <= b, a > b, a >= bBooleanсравнение по порядку

* — неограниченное значение UnlimitedNatural, и *.toInteger() даёт invalid.

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

СинтаксисРезультатЗначение
a and bBooleanконъюнкция
a or bBooleanдизъюнкция
a xor bBooleanистинно, когда истинна ровно одна сторона
not aBooleanотрицание
a implies bBooleanистинно всегда, когда 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)Booleanx является элементом c
c->excludes(x)Booleanx не является элементом 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 - dSet(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)Booleanbody истинно для каждого элемента
c->exists(e | body)Booleanbody истинно хотя бы для одного элемента
c->one(e | body)Booleanbody истинно ровно для одного элемента
c->any(e | body)тип элементаодин элемент, для которого body истинно
c->isUnique(e | body)Booleanbody даёт разные значения для всех элементов
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.featureBag или 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)