Матрёшка: работа с UML моделями на примере генератора SQL DDL

В современной программной индустрии широко используются различные языки моделирования, среди которых наверное наиболее обсуждаем унифицированный язык моделирования UML. Одним из интересных применений оного явлыется генерация различного кода по модели. Далее в этой статье будет рассмотрено использование модуля AMF (Ada Modeling Framework) Матрёшки для разработки собственных генераторов кода по UML моделям на простейшем примере — генераторе операторов создания таблиц базы данных.

Ada Modeling Framework

Модуль AMF является реализацией спецификации Meta Object Facility консорциума OMG на уровне CMOF. Ядро модуля обеспечивает возможность выполнения операций над произвольными моделями, метамодели которых определённы с использованием языка CMOF, и, в частности, моделями UML. В нашем случае будет интересен только маленький набор функций модуля, а именно возможность загрузки UML модели и получения элементов модели.

Общие принципы отображения метамоделей на Ada

Прежде всего кратко опишем особенности отображения метамодели UML на средства языка программирования Ada. Каждый класс метамодели UML отображается на интерфейсный тип Ada, имя которого формируется из префикса UML_ и имени класса, приведённого к типичным для Ada соглашениям именования (слова разделяются подчёркиванием, каждое слово начинается с заглавной буквы). Этот тип объявлен в пакете, дочернем к пакету AMF.UML, имя которого формируется из имени класса во множественном числе, приведённом к соглашениям именования Ada. Для удобства вместе с интерфейсным типом объявляется и надклассовый ссылочный тип.

В дочернем пакете Collections объявляется по одному типу для каждого вида коллекций: набор, упорядоченный набор, портфель и последовательность.

Для каждого атирибута класса формируется подпрограмма получения значения, а в некоторых случаях и подпрограмма установки значения. Имя подпрограммы получения значения формируется из префикса Get_ и имени атрибута, приведённого к соглашению именования языка Ada; для подпрограммы установки значения используется префик Set_. Подпрограммы установки значения не генерируются для атрибутов доступных только для чтения, а так же для всех атрибутов, использующих в коллекции в качестве значения. Последнее связано с тем, что полученная как значение атрибута коллекция может использоваться для изменения значения этого атрибута.

Инициализация, загрузка модели и получение набора элементов модели

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


          

          
with League.Application;

with AMF.Elements.Collections;
with AMF.Facility;
with AMF.URI_Stores;
with XMI.Reader;

with AMF.Internals.Factories.UML_Factory;
pragma Unreferenced (AMF.Internals.Factories.UML_Factory);
pragma Elaborate_All (AMF.Internals.Factories.UML_Factory);
--  This package should be included into partition closure to be able
--  to process UML models.

procedure Example is
   Store    : AMF.URI_Stores.URI_Store_Access;
   Elements : AMF.Elements.Collections.Set_Of_Element;

begin
   AMF.Facility.Initialize;
   Store := XMI.Reader (League.Application.Arguments.Element (1));
   Elements := Store.Elements;

Базовым пакетом для доступа к функциям модуля является пакет AMF.Facility, из которого нам будет интересна только функция инициализации модуля — AMF.Facility.Initialize. Кроме этого, необходимо установить зависимость от фабрики UML для обеспечения возможности чтения файлов UML моделей, что сделано с помощью соответствующего оператора контекста.

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

Выбор классов среди элементов модели

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


          

          
   for J in 1 .. Elements.Length loop
      Element := Elements.Element (J);

      if Element.all in AMF.UML.Classes.UML_Class'Class then
         Generate (AMF.UML.Classes.UML_Class_Access (Element));
      end if;
   end loop;

Генерация оператора создания таблицы для класса

Теперь осталось самое малое: сгенерировать оператор создания таблицы для класса. В качестве имени таблицы мы используем имя класса; для каждого атрибута с верхней границей множественности значений равной 1 создадим поле таблицы с именем атрибута класса и именем типа атрибута; а для каждого атрибута с нижней границей множественности значений не равной 0 зададим ограничение NOT NULL:


          

          
procedure Generate
 (Class : not null AMF.UML.Classes.UML_Class_Access)
is
   use type AMF.Optional_Integer;

   Owned_Attribute : constant
     AMF.UML.Properties.Collections.Ordered_Set_Of_UML_Property
       := Class.Get_Owned_Attribute;
   Attribute       : AMF.UML.Properties.UML_Property_Access;
   First           : Boolean := True;
   Attribute_Type  : AMF.UML.Types.UML_Type_Access;

begin
   Put ("CREATE TABLE " & Class.Get_Name.Value.To_Wide_Wide_String);

   for J in 1 .. Owned_Attribute.Length loop
      Attribute      := Owned_Attribute.Element (J);
      Attribute_Type := Attribute.Get_Type;
       if not Attribute.Is_Multivalued then
         if First then
            New_Line;
            Put ("  (");
            First := False;
          else
            Put_Line (";");
            Put ("   ");
         end if;

         Put (Attribute.Get_Name.Value.To_Wide_Wide_String);
         Put (' ');
         Put
          (Attribute_Type.Get_Name.Value.To_Uppercase.
             To_Wide_Wide_String);

         if Attribute.Lower_Bound = 1 then
            Put (" NOT NULL");
         end if;
      end if;
   end loop;

   Put_Line (");");
end Generate;

Для знакомых со структурой моделей UML приведённого вполне достаточно, а для незнакомых — несколько комментариев:

  • подпрограмма Get_Owned_Attribute для класса возвращает набор атрибутов класса, не включая унаследованные;
  • подпрограмма Get_Name для класса, атрибута и типа возвращает его имя;
  • подпрограмма Get_Type для атрибута возвращает его тип;
  • подпрограмма Is_Multivalued возвращает True когда верхняя граница множественности значения более одного;
  • подпрограмма Lower_Bound возвращает значение нижней границы множественности значения в виде числа.

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

Заключение

На этом немного сумбурный расказ закончен, а далее приводится полный код примера:


          

          
with Ada.Wide_Wide_Text_IO;

with AMF.Elements.Collections;
with AMF.Facility;
with AMF.UML.Classes;
with AMF.UML.Properties.Collections;
with AMF.UML.Types;
with AMF.URI_Stores;
with League.Application;
with XMI.Reader;

with AMF.Internals.Factories.UML_Factory;
pragma Unreferenced (AMF.Internals.Factories.UML_Factory);
pragma Elaborate_All (AMF.Internals.Factories.UML_Factory);
--  This package should be included into partition closure to be able
--  to process UML models.

procedure Demo is

   use Ada.Wide_Wide_Text_IO;

   procedure Generate
    (Class : not null AMF.UML.Classes.UML_Class_Access);

   --------------
   -- Generate --
   --------------

   procedure Generate
    (Class : not null AMF.UML.Classes.UML_Class_Access)
   is
      use type AMF.Optional_Integer;

      Owned_Attribute : constant
        AMF.UML.Properties.Collections.Ordered_Set_Of_UML_Property
          := Class.Get_Owned_Attribute;
      Attribute       : AMF.UML.Properties.UML_Property_Access;
      First           : Boolean := True;
      Attribute_Type  : AMF.UML.Types.UML_Type_Access;

   begin
      Put ("CREATE TABLE " & Class.Get_Name.Value.To_Wide_Wide_String);

      for J in 1 .. Owned_Attribute.Length loop
         Attribute      := Owned_Attribute.Element (J);
         Attribute_Type := Attribute.Get_Type;

         if not Attribute.Is_Multivalued then
            if First then
               New_Line;
               Put ("  (");
               First := False;

            else
               Put_Line (";");
               Put ("   ");
            end if;

            Put (Attribute.Get_Name.Value.To_Wide_Wide_String);
            Put (' ');
            Put
             (Attribute_Type.Get_Name.Value.To_Uppercase.
                To_Wide_Wide_String);

            if Attribute.Lower_Bound = 1 then
               Put (" NOT NULL");
            end if;
         end if;
      end loop;

      Put_Line (");");
   end Generate;

   Store    : AMF.URI_Stores.URI_Store_Access;
   Elements : AMF.Elements.Collections.Set_Of_Element;
   Element  : AMF.Elements.Element_Access;

begin
   AMF.Facility.Initialize;
   Store := XMI.Reader (League.Application.Arguments.Element (1));
   Elements := Store.Elements;

   for J in 1 .. Elements.Length loop
      Element := Elements.Element (J);

      if Element.all in AMF.UML.Classes.UML_Class'Class then
         Generate (AMF.UML.Classes.UML_Class_Access (Element));
      end if;
   end loop;
end Example;

Автор: Вадим Годунко
Дата: 11.11.2011