awa_2.4.0_59135a52/dynamo/src/gen-artifacts-yaml.adb

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
-----------------------------------------------------------------------
--  gen-artifacts-yaml -- Query artifact for Code Generator
--  Copyright (C) 2018, 2019, 2021, 2022 Stephane Carrez
--  Written by Stephane Carrez (Stephane.Carrez@gmail.com)
--
--  Licensed under the Apache License, Version 2.0 (the "License");
--  you may not use this file except in compliance with the License.
--  You may obtain a copy of the License at
--
--      http://www.apache.org/licenses/LICENSE-2.0
--
--  Unless required by applicable law or agreed to in writing, software
--  distributed under the License is distributed on an "AS IS" BASIS,
--  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
--  See the License for the specific language governing permissions and
--  limitations under the License.
-----------------------------------------------------------------------
with Ada.Exceptions;
with Ada.Text_IO;

with Util.Strings;
with Util.Beans.Objects;

with Text;
with Yaml.Source.File;
with Yaml.Parser;

with Gen.Model.Enums;
with Gen.Model.Tables;
with Gen.Model.Mappings;

with Util.Log.Loggers;
with Util.Stacks;

use Yaml;

package body Gen.Artifacts.Yaml is

   use Ada.Strings.Unbounded;
   use Gen.Model;
   use Gen.Model.Tables;

   Log : constant Util.Log.Loggers.Logger := Util.Log.Loggers.Create ("Gen.Artifacts.Yaml");

   type State_Type is (IN_ROOT,
                       IN_TYPE,
                       IN_TABLE,
                       IN_COLUMNS,
                       IN_ONE_TO_MANY,
                       IN_ENUM,
                       IN_ENUM_VALUES,
                       IN_KEYS,
                       IN_COLUMN,
                       IN_ASSOCIATION,
                       IN_KEY,
                       IN_GENERATOR,
                       IN_UNKNOWN);

   type Node_Info is record
      State    : State_Type := IN_UNKNOWN;
      Name     : Text.Reference;
      Tag      : Text.Reference;
      Has_Name : Boolean := False;
      Enum     : Gen.Model.Enums.Enum_Definition_Access;
      Table    : Gen.Model.Tables.Table_Definition_Access;
      Col      : Gen.Model.Tables.Column_Definition_Access;
      Assoc    : Gen.Model.Tables.Association_Definition_Access;
   end record;

   type Node_Info_Access is access all Node_Info;

   package Node_Stack is new Util.Stacks (Element_Type        => Node_Info,
                                          Element_Type_Access => Node_Info_Access);
   --  Read the UML/XMI model file.
   procedure Read_Model (Handler       : in out Artifact;
                         File          : in String;
                         Model         : in out Gen.Model.Packages.Model_Definition;
                         Context       : in out Generator'Class) is
      pragma Unreferenced (Handler);

      procedure Process_Mapping (Model : in out Gen.Model.Packages.Model_Definition;
                                 Stack : in out Node_Stack.Stack;
                                 File  : in String;
                                 Loc   : in Mark);

      procedure Read_Scalar (Node    : in Node_Info_Access;
                             Name    : in String;
                             Value   : in String;
                             Loc     : in Mark);

      procedure Read_Scalar (Node    : in Node_Info_Access;
                             Name    : in String;
                             Value   : in String;
                             Loc     : in Mark) is

         use type Gen.Model.Enums.Enum_Definition_Access;

         function Location return String is
           (File & ":" & Util.Strings.Image (Loc.Line) & ":"
            & Util.Strings.Image (Loc.Column));

         New_Value : Gen.Model.Enums.Value_Definition_Access;
      begin
         case Node.State is
         when IN_TYPE =>
            if Name = "type" then
               if Value = "enum" then
                  Node.Enum := Gen.Model.Enums.Create_Enum (Node.Tag);
                  Node.Enum.Set_Location (Location);
                  Node.State := IN_ENUM;
                  Model.Register_Enum (Node.Enum);
               else
                  Node.Table := Gen.Model.Tables.Create_Table (Node.Tag);
                  Node.Table.Set_Location (Location);
                  Node.State := IN_TABLE;
                  Model.Register_Table (Node.Table);
               end if;
            end if;

         when IN_TABLE =>
            if Node.Table = null then
               return;
            end if;

            Log.Debug ("Set table {0} attribute {1}={2}", Node.Table.Name, Name, Value);
            if Name = "table" then
               Node.Table.Table_Name := To_UString (Value);
            elsif Name = "description" or else Name = "comment" then
               Node.Table.Set_Comment (Value);
            elsif Name = "hasList" then
               Node.Table.Has_List := Value in "true" | "yes";
            end if;

         when IN_COLUMN | IN_KEY | IN_ASSOCIATION =>
            if Node.Col = null then
               return;
            end if;

            Log.Debug ("Set table column {0} attribute {1}={2}", Node.Col.Name, Name, Value);
            if Name = "type" then
               Node.Col.Set_Type (Value);
            elsif Name = "length" then
               Node.Col.Set_Sql_Length (Value, Context);
            elsif Name = "column" then
               Node.Col.Sql_Name := To_UString (Value);
            elsif Name = "unique" then
               Node.Col.Unique := Value in "true" | "yes";
            elsif Name = "nullable" or else Name = "optional" then
               Node.Col.Not_Null := Value in "false" | "no";
            elsif Name = "not-null" or else Name = "required" then
               Node.Col.Not_Null := Value in "true" | "yes";
            elsif Name = "description" or else Name = "comment" then
               Node.Col.Set_Comment (Value);
            elsif Name = "version" then
               Node.Col.Is_Version := Value in "true" | "yes";
            elsif Name = "readonly" then
               Node.Col.Is_Updated := Value in "false" | "no";
            elsif Name = "auditable" then
               Node.Col.Is_Auditable := Value in "true" | "yes";
            elsif Name = "useForeignKey" and then Node.Assoc /= null then
               Node.Assoc.Use_Foreign_Key_Type := Value in "true" | "yes";
            end if;

         when IN_GENERATOR =>
            if Node.Col = null then
               return;
            end if;
            if Name = "strategy" then
               Node.Col.Generator := Util.Beans.Objects.To_Object (Value);
            end if;

         when IN_ENUM_VALUES =>
            if Node.Enum = null then
               return;
            end if;
            Node.Enum.Add_Value (Name, New_Value);
            New_Value.Set_Location (Location);
            New_Value.Number := Natural'Value (Value);

         when others =>
            Log.Error ("Scalar {0}: {1} not handled", Name, Value);
         end case;
      end Read_Scalar;

      procedure Process_Mapping (Model    : in out Gen.Model.Packages.Model_Definition;
                                 Stack    : in out Node_Stack.Stack;
                                 File     : in String;
                                 Loc      : in Mark) is
         pragma Unreferenced (Model);

         function Location return String is
           (File & ":" & Util.Strings.Image (Loc.Line) & ":"
            & Util.Strings.Image (Loc.Column));

         Node     : Node_Info_Access;
         New_Node : Node_Info_Access;
      begin
         Node := Node_Stack.Current (Stack);
         if Node.Has_Name then
            Node.Has_Name := False;
            case Node.State is
            when IN_ROOT =>
               Node_Stack.Push (Stack);
               New_Node := Node_Stack.Current (Stack);
               New_Node.Tag := Node.Name;
               New_Node.State := IN_TYPE;

            when IN_TABLE =>
               if Node.Name = "fields" or else Node.Name = "properties" then
                  Node_Stack.Push (Stack);
                  New_Node := Node_Stack.Current (Stack);
                  New_Node.Table := Node.Table;
                  New_Node.State := IN_COLUMNS;

               elsif Node.Name = "id" then
                  Node_Stack.Push (Stack);
                  New_Node := Node_Stack.Current (Stack);
                  New_Node.Table := Node.Table;
                  New_Node.State := IN_KEYS;

               elsif Node.Name = "oneToMany" then
                  Node_Stack.Push (Stack);
                  New_Node := Node_Stack.Current (Stack);
                  New_Node.Table := Node.Table;
                  New_Node.State := IN_ONE_TO_MANY;

               else
                  Node_Stack.Push (Stack);
                  New_Node := Node_Stack.Current (Stack);
                  New_Node.Table := Node.Table;
                  New_Node.State := IN_TABLE;
               end if;

            when IN_COLUMNS =>
               Node.Table.Add_Column (Node.Name, Node.Col);
               Node.Col.Set_Location (Location);
               Node_Stack.Push (Stack);
               New_Node := Node_Stack.Current (Stack);
               New_Node.Table := Node.Table;
               New_Node.State := IN_COLUMN;

            when IN_ONE_TO_MANY =>
               Node_Stack.Push (Stack);
               New_Node := Node_Stack.Current (Stack);
               New_Node.Table := Node.Table;
               New_Node.State := IN_ASSOCIATION;
               Node.Table.Add_Association (Node.Name, New_Node.Assoc);
               New_Node.Assoc.Set_Location (Location);
               New_Node.Col := New_Node.Assoc.all'Access;

            when IN_KEYS =>
               Node.Table.Add_Column (Node.Name, Node.Col);
               Node.Col.Set_Location (Location);
               Node.Col.Is_Key := True;
               Node_Stack.Push (Stack);
               New_Node := Node_Stack.Current (Stack);
               New_Node.Table := Node.Table;
               New_Node.State := IN_KEY;

            when IN_KEY =>
               if Node.Name = "generator" then
                  Node_Stack.Push (Stack);
                  New_Node := Node_Stack.Current (Stack);
                  New_Node.Table := Node.Table;
                  New_Node.Col := Node.Col;
                  New_Node.State := IN_GENERATOR;
               end if;

            when IN_ENUM =>
               if Node.Name = "values" then
                  Node_Stack.Push (Stack);
                  New_Node := Node_Stack.Current (Stack);
                  New_Node.Table := Node.Table;
                  New_Node.State := IN_ENUM_VALUES;
               end if;

            when others =>
               Node_Stack.Push (Stack);
               New_Node := Node_Stack.Current (Stack);
               New_Node.State := IN_UNKNOWN;

            end case;
         else
            Node_Stack.Push (Stack);

         end if;
      end Process_Mapping;

      Input : Source.Pointer;
      P     : Parser.Instance;
      Cur   : Event;
      Stack : Node_Stack.Stack;
      Node  : Node_Info_Access;
      Loc   : Mark;
   begin
      Log.Info ("Reading YAML file {0}", File);

      Input := Source.File.As_Source (File);
      P.Set_Input (Input);
      loop
         Cur := P.Next;
         exit when Cur.Kind = Stream_End;
         case Cur.Kind is
            when Stream_Start | Document_Start =>
               Node_Stack.Push (Stack);
               Node := Node_Stack.Current (Stack);
               Node.State := IN_ROOT;

            when Stream_End | Document_End =>
               Node_Stack.Pop (Stack);

            when Alias =>
               null;

            when Scalar =>
               Node := Node_Stack.Current (Stack);
               if Node.Has_Name then
                  Read_Scalar (Node, To_String (Node.Name), To_String (Cur.Content),
                               P.Current_Lexer_Token_Start);
                  Node.Has_Name := False;
               else
                  Node.Name := Cur.Content;
                  Node.Has_Name := True;
               end if;

            when Sequence_Start =>
               Node_Stack.Push (Stack);

            when Sequence_End =>
               Node_Stack.Pop (Stack);

            when Mapping_Start =>
               Process_Mapping (Model, Stack, File, P.Current_Lexer_Token_Start);

            when Mapping_End =>
               Node_Stack.Pop (Stack);

            when Annotation_Start =>
               null;

            when Annotation_End =>
               null;

         end case;
      end loop;

   exception
      when E : others =>
         Loc := P.Current_Lexer_Token_Start;
         Context.Error ("{0}: {1}", Util.Strings.Image (Loc.Line) & ":"
                        & Util.Strings.Image (Loc.Column) & ": ",
                        Ada.Exceptions.Exception_Message (E));
   end Read_Model;

   --  ------------------------------
   --  Save the model in a YAML file.
   --  ------------------------------
   procedure Save_Model (Handler : in Artifact;
                         Path    : in String;
                         Model   : in out Gen.Model.Packages.Model_Definition'Class;
                         Context : in out Generator'Class) is
      pragma Unreferenced (Handler, Context);

      procedure Write_Description (Comment : in Util.Beans.Objects.Object;
                                   Indent  : in Ada.Text_IO.Count);
      procedure Write_Field (Item  : in Gen.Model.Definition'Class;
                             Name  : in String);
      procedure Write_Column (Col : in Gen.Model.Tables.Column_Definition'Class);
      procedure Write_Association (Col : in Gen.Model.Tables.Association_Definition'Class);

      procedure Process_Table (Table : in out Gen.Model.Tables.Table_Definition);
      procedure Process_Enum (Enum : in out Gen.Model.Enums.Enum_Definition);

      File : Ada.Text_IO.File_Type;

      --  Write a description field taking into account multi-lines.
      procedure Write_Description (Comment : in Util.Beans.Objects.Object;
                                   Indent  : in Ada.Text_IO.Count) is
         use type Ada.Text_IO.Count;

         Content     : constant String := Util.Beans.Objects.To_String (Comment);
         Pos, Start  : Positive := Content'First;
      begin
         Ada.Text_IO.Set_Col (File, Indent);
         Ada.Text_IO.Put (File, "description: ");
         if Util.Strings.Index (Content, ASCII.LF) > 0
           or else Util.Strings.Index (Content, ASCII.CR) > 0
         then
            Start := Content'First;
            Pos   := Content'First;
            Ada.Text_IO.Put_Line (File, "|");
            while Pos <= Content'Last loop
               if Content (Pos) in ASCII.CR | ASCII.LF then
                  Ada.Text_IO.Set_Col (File, Indent + 2);
                  Ada.Text_IO.Put_Line (File, Content (Start .. Pos - 1));
                  Start := Pos + 1;
               end if;
               Pos := Pos + 1;
            end loop;
            if Start < Pos then
               Ada.Text_IO.Set_Col (File, Indent + 2);
               Ada.Text_IO.Put_Line (File, Content (Start .. Pos - 1));
            end if;
         else
            Ada.Text_IO.Put (File, Content);
         end if;
         Ada.Text_IO.New_Line (File);
      end Write_Description;

      procedure Write_Field (Item  : in Gen.Model.Definition'Class;
                             Name  : in String) is
         Value : constant Util.Beans.Objects.Object := Item.Get_Value (Name);
      begin
         Ada.Text_IO.Put_Line (File, Util.Beans.Objects.To_String (Value));
      end Write_Field;

      procedure Write_Column (Col : in Gen.Model.Tables.Column_Definition'Class) is
         use type Gen.Model.Mappings.Mapping_Definition_Access;

         Col_Type : Gen.Model.Mappings.Mapping_Definition_Access;
      begin
         Col_Type := Col.Get_Type_Mapping;
         Ada.Text_IO.Put (File, "    ");
         Ada.Text_IO.Put (File, Col.Get_Name);
         Ada.Text_IO.Put_Line (File, ":");
         Ada.Text_IO.Put (File, "      type: ");
         if Col_Type /= null then
            Ada.Text_IO.Put (File, Col_Type.Get_Type_Name);
         end if;
         Ada.Text_IO.New_Line (File);
         if Col.Is_Variable_Length then
            Ada.Text_IO.Put (File, "      length:");
            Ada.Text_IO.Put_Line (File, Positive'Image (Col.Sql_Length));
         end if;
         Ada.Text_IO.Put (File, "      column: ");
         Write_Field (Col, "sqlName");
         if Col_Type /= null and then Col_Type.Nullable then
            Ada.Text_IO.Put_Line (File, "      nullable: true");
         end if;
         Ada.Text_IO.Put (File, "      not-null: ");
         Ada.Text_IO.Put_Line (File, (if Col.Not_Null then "true" else "false"));
         if Col.Is_Version then
            Ada.Text_IO.Put_Line (File, "      version: true");
         end if;
         if not Col.Is_Updated then
            Ada.Text_IO.Put_Line (File, "      readonly: true");
         end if;
         if Col.Is_Auditable then
            Ada.Text_IO.Put_Line (File, "      auditable: true");
         end if;
         Ada.Text_IO.Put (File, "      unique: ");
         Ada.Text_IO.Put_Line (File, (if Col.Unique then "true" else "false"));
         Write_Description (Col.Get_Comment, 7);
      end Write_Column;

      procedure Write_Association (Col : in Gen.Model.Tables.Association_Definition'Class) is
         use type Gen.Model.Mappings.Mapping_Definition_Access;

         Col_Type : Gen.Model.Mappings.Mapping_Definition_Access;
      begin
         Col_Type := Col.Get_Type_Mapping;
         Ada.Text_IO.Put (File, "    ");
         Ada.Text_IO.Put (File, Col.Get_Name);
         Ada.Text_IO.Put_Line (File, ":");
         Ada.Text_IO.Put (File, "      type: ");
         if Col_Type /= null then
            Ada.Text_IO.Put (File, Col_Type.Get_Type_Name);
         end if;
         Ada.Text_IO.New_Line (File);
         if Col.Is_Variable_Length then
            Ada.Text_IO.Put (File, "      length:");
            Ada.Text_IO.Put_Line (File, Positive'Image (Col.Sql_Length));
         end if;
         Ada.Text_IO.Put (File, "      column: ");
         Write_Field (Col, "sqlName");
         if Col_Type /= null and then Col_Type.Nullable then
            Ada.Text_IO.Put_Line (File, "      nullable: true");
         end if;
         Ada.Text_IO.Put (File, "      not-null: ");
         Ada.Text_IO.Put_Line (File, (if Col.Not_Null then "true" else "false"));
         if not Col.Is_Updated then
            Ada.Text_IO.Put_Line (File, "      readonly: true");
         end if;
         if Col.Is_Auditable then
            Ada.Text_IO.Put_Line (File, "      auditable: true");
         end if;
         if Col.Use_Foreign_Key_Type then
            Ada.Text_IO.Put_Line (File, "      useForeignKey: true");
         end if;
         Ada.Text_IO.Put (File, "      unique: ");
         Ada.Text_IO.Put_Line (File, (if Col.Unique then "true" else "false"));
         Write_Description (Col.Get_Comment, 7);
      end Write_Association;

      procedure Process_Table (Table : in out Gen.Model.Tables.Table_Definition) is
      begin
         Ada.Text_IO.Put (File, Table.Get_Name);
         Ada.Text_IO.Put_Line (File, ":");
         Ada.Text_IO.Put_Line (File, "  type: entity");
         Ada.Text_IO.Put (File, "  table: ");
         Write_Field (Table, "sqlName");
         Write_Description (Table.Get_Comment, 3);
         Ada.Text_IO.Put (File, "  hasList: ");
         Ada.Text_IO.Put_Line (File, (if Table.Has_List then "true" else "false"));
         Ada.Text_IO.Put (File, "  indexes: ");
         Ada.Text_IO.New_Line (File);
         Ada.Text_IO.Put (File, "  id: ");
         Ada.Text_IO.New_Line (File);

         for Col of Table.Members loop
            if Col.Is_Key then
               Write_Column (Col.all);
            end if;
         end loop;

         Ada.Text_IO.Put (File, "  fields: ");
         Ada.Text_IO.New_Line (File);

         for Col of Table.Members loop
            if not Col.Is_Key and then
              not (Col.all in Gen.Model.Tables.Association_Definition'Class)
            then
               Write_Column (Col.all);
            end if;
         end loop;

         if Table.Has_Associations then
            Ada.Text_IO.Put_Line (File, "  oneToMany:");
            for Col of Table.Members loop
               if Col.all in Gen.Model.Tables.Association_Definition'Class then
                  Write_Association (Gen.Model.Tables.Association_Definition'Class (Col.all));
               end if;
            end loop;
         end if;
      end Process_Table;

      procedure Process_Enum (Enum : in out Gen.Model.Enums.Enum_Definition) is
      begin
         Ada.Text_IO.Put (File, Enum.Get_Name);
         Ada.Text_IO.Put_Line (File, ":");
         Ada.Text_IO.Put_Line (File, "  type: enum");
         Ada.Text_IO.Put (File, "  values: ");
         Ada.Text_IO.New_Line (File);

         for Value of Enum.Values loop
            Ada.Text_IO.Put (File, "    ");
            Ada.Text_IO.Put (File, Value.Get_Name);
            Ada.Text_IO.Put (File, ":");
            Ada.Text_IO.Put_Line (File, Natural'Image (Value.Number));
         end loop;
      end Process_Enum;

   begin
      Ada.Text_IO.Create (File, Ada.Text_IO.Out_File, Path);
      Model.Iterate_Enums (Process_Enum'Access);
      Model.Iterate_Tables (Process_Table'Access);
      Ada.Text_IO.Close (File);
   end Save_Model;

   --  ------------------------------
   --  Prepare the generation of the package:
   --  o identify the column types which are used
   --  o build a list of package for the with clauses.
   --  ------------------------------
   overriding
   procedure Prepare (Handler : in out Artifact;
                      Model   : in out Gen.Model.Packages.Model_Definition'Class;
                      Project : in out Gen.Model.Projects.Project_Definition'Class;
                      Context : in out Generator'Class) is
      pragma Unreferenced (Project);
   begin
      Log.Debug ("Saving the model in YAML");

      Handler.Save_Model (Path    => "model.yaml",
                          Model   => Model,
                          Context => Context);
   end Prepare;

end Gen.Artifacts.Yaml;