Матрёшка: использование шаблона «визитёр» для обхода элементов UML модели

В прошлой статье (Матрёшка: работа с UML моделями на примере генератора SQL DDL) рассматривался пример генератора кода создания схемы базы данных по UML модели. Для анализа элементов UML модели выполнялся простой обход всех загруженных элементов. Такой механизм обработки хорош для маленьких примеров, но в реальной жизни может потребовать написание большого количества кода для классификации элементов. Для исключения написания этого кода классически используется шаблон «визитёр», предоставляющий уже готовый механизм классификации и требующий от пользователя только необходимости определить выполняемые действия для каждого из классов элементов модели.

Обзор

Базовая реализация шаблона «визитёр» находится в пакете AMF.Visitors.UML_Containment. Обход элементов UML модели при этом осуществляется в порядке вложенности элементов, от объемлющей модели вглубь. Для каждого неабстрактного класса метамодели UML базовая реализация располагает тремя подпрограммами, например для метакласса UML::Class:


          

          
   not overriding procedure Enter_Class
    (Self    : in out UML_Containment_Visitor;
     Element : not null AMF.UML.Classes.UML_Class_Access)
       is null;

   not overriding procedure Visit_Class
    (Self    : in out UML_Containment_Visitor;
     Element : not null AMF.UML.Classes.UML_Class_Access);

   not overriding procedure Leave_Class
    (Self    : in out UML_Containment_Visitor;
     Element : not null AMF.UML.Classes.UML_Class_Access)
       is null;

При обходе элементов UML модели для кажого элемента вызываются все три подпрограммы. Первой производится вызов подпрограммы Enter_<Class>, потом производится вызов подпрограммы Visit_<Class> и после этого — вызов Leave_<Class>. Как видно из кода, подпрограммы Enter_<Class> и Leave_<Class> имеют пустую реализацию по умолчанию. Их основное назначение — переопределение пользователем для реализации логики приложения. Подпрограмма Visit_<Class> по умолчанию выполняет последовательный обход каждого дочернего элемента текущего элемента UML модели. Её переопределение пользователем предполагается только на случай необходимости модификации последовательности обхода элементов UML модели.

Переработка генератора кода

Для реализации генератора кода с использованием шаблона «визитёр» нам достаточно объявить новый тип, порождённый от типа AMF.Visitors.UML_Containment.UML_Containment_Visitor и переопределив подпрограммы Enter_Class:


          

          
private with AMF.UML.Classes;
with AMF.Visitors.UML_Containment;

package Generators is

   type Generator is
     new AMF.Visitors.UML_Containment.UML_Containment_Visitor
       with private;

private

   type Generator is
     new AMF.Visitors.UML_Containment.UML_Containment_Visitor with
   record
      In_Model : Boolean := False;
   end record;

   overriding procedure Enter_Class
    (Self    : in out Generator;
     Element : not null AMF.UML.Classes.UML_Class_Access);

end Generators;

Реализация же будет выглядеть следующим образом:


          

          
with Ada.Wide_Wide_Text_IO;

with AMF.Elements;
with AMF.UML.Elements;
with AMF.UML.Models;
with AMF.UML.Properties.Collections;
with AMF.UML.Types;
with League.Strings;

package body Generators is

   use Ada.Wide_Wide_Text_IO;
   use type AMF.Optional_String;

   function "+"
    (Item : Wide_Wide_String) return League.Strings.Universal_String
       renames League.Strings.To_Universal_String;

   -----------------
   -- Enter_Class --
   -----------------

   overriding procedure Enter_Class
    (Self    : in out Generator;
     Element : 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
          := Element.Get_Owned_Attribute;
      Attribute       : AMF.UML.Properties.UML_Property_Access;
      First           : Boolean := True;
      Attribute_Type  : AMF.UML.Types.UML_Type_Access;
      Owner           : AMF.UML.Elements.UML_Element_Access;

   begin
      Owner :=
        AMF.UML.Elements.UML_Element_Access
         (AMF.Elements.Element_Access (Element).Container);

      if Owner.all not in AMF.UML.Models.UML_Model'Class
        or else AMF.UML.Models.UML_Model_Access (Owner).Get_Name /= +"Schema"
      then
        --  Return immediately when owner of current UML::Class is not
         --  UML::Model called "Schema".

         return;
      end if;

      Put ("CREATE TABLE " & Element.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 Enter_Class;

end Generators;

Код генерации в точности такой же, как и в предыдущей статье, поэтому его обсуждение здесь опущено. Однако, в самом начале подпрограммы добавлена весьма важная проверка, выполняющая генерацию кода только если текущий класс находится в моделе с именем «Schema». С одной стороны это позволяет в одной моделе иметь описание не только схемы, но и других составляющих проекта; с другой стороны это позволяет обойти генерацию кода для огромного количества классов, импортируемых современными UML моделерами как бесплатное дополнение к модели пользователя.

Из всего кода проверки хочется обратить внимание на способ получения «родительского» элемента. Структура AMF построена таким образом, что любой элемент любой модели всегда поддерживает интерфейс AMF.Elements.Abstract_Element; однако он не наследуется открыто для исключения прямой видимости его операций в коде приложения. Среди его операций имеется операция для получения «родительского» элемента, именуемая Container. Именно она и используется для получения «родительского» элемента, который с помощью преобразования типа приводится к базовому типу элементов UML модели.

Осталось сделать последний шаг — написать основную подпрограмму:


          

          
with AMF.Facility;
with AMF.URI_Stores;
with League.Application;
with XMI.Reader;

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

with Generators;

procedure Demo is
   Store     : AMF.URI_Stores.URI_Store_Access;
   Generator : Generators.Generator;

begin
   AMF.Facility.Initialize;

   --  Loading specified model

   Store := XMI.Reader.Read_File (League.Application.Arguments.Element (1));

   --  Generate code

   Generator.Visit (Store);
end Demo;

Изменение порядка обхода элементов

Как уже было сказано, обход дочерних элементов выполняют подпрограммы Visit<Class>. Пользователь вправе переопределить эти подпрограммы при необходимости изменения очередности обхода. При написании собственных подпрограмм обхода возникает необходимость классификации элемента и вызова его обработчика. Пользователь может использовать (и, более того, настоятельно рекомендуется её использовать) уже определённую подпрограмму:


          

          
   procedure Visit
    (Self    : in out UML_Containment_Visitor'Class;
     Element : not null access AMF.UML.Elements.UML_Element'Class);

Подпрограмма Visit выполняет классификацию указанного элемента и вызывает соответствующие метаклассу элемента подпрограммы Enter_<Class>, Visit_<Class> и Leave_<Class>; тем самым избавляя пользователя от необходимости самостоятельно выполнять эти действия для сотен метаклассов UML.


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