* src/DbMetal/Generator/Implementation/CodeTextGenerator/CodeGenerator.cs:
[mono.git] / mcs / class / System.Data.Linq / src / DbMetal / Generator / Implementation / CodeTextGenerator / CodeGenerator.cs
1 #region MIT license\r
2 // \r
3 // MIT license\r
4 //\r
5 // Copyright (c) 2007-2008 Jiri Moudry, Pascal Craponne\r
6 // \r
7 // Permission is hereby granted, free of charge, to any person obtaining a copy\r
8 // of this software and associated documentation files (the "Software"), to deal\r
9 // in the Software without restriction, including without limitation the rights\r
10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r
11 // copies of the Software, and to permit persons to whom the Software is\r
12 // furnished to do so, subject to the following conditions:\r
13 // \r
14 // The above copyright notice and this permission notice shall be included in\r
15 // all copies or substantial portions of the Software.\r
16 // \r
17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r
23 // THE SOFTWARE.\r
24 // \r
25 #endregion\r
26 using System;\r
27 using System.Collections.Generic;\r
28 using System.IO;\r
29 using System.Linq;\r
30 using DbLinq.Data.Linq;\r
31 using DbLinq.Schema;\r
32 using DbLinq.Schema.Dbml;\r
33 using DbLinq.Schema.Dbml.Adapter;\r
34 using DbLinq.Util;\r
35 using Type = System.Type;\r
36 \r
37 #if MONO_STRICT\r
38 using System.Data.Linq;\r
39 #endif\r
40 \r
41 namespace DbMetal.Generator.Implementation.CodeTextGenerator\r
42 {\r
43 #if !MONO_STRICT\r
44     public\r
45 #endif\r
46     abstract partial class CodeGenerator : ICodeGenerator\r
47     {\r
48         public abstract string LanguageCode { get; }\r
49         public abstract string Extension { get; }\r
50 \r
51         protected class MassDisposer : IDisposable\r
52         {\r
53             public IList<IDisposable> Disposables = new List<IDisposable>();\r
54 \r
55             public void Dispose()\r
56             {\r
57                 for (int index = Disposables.Count - 1; index > 0; index--)\r
58                 {\r
59                     Disposables[index].Dispose();\r
60                 }\r
61             }\r
62         }\r
63 \r
64         protected abstract CodeWriter CreateCodeWriter(TextWriter textWriter);\r
65 \r
66         public void Write(TextWriter textWriter, Database dbSchema, GenerationContext context)\r
67         {\r
68             if (dbSchema == null || dbSchema.Tables == null)\r
69             {\r
70                 //Logger.Write(Level.Error, "CodeGenAll ERROR: incomplete dbSchema, cannot start generating code");\r
71                 return;\r
72             }\r
73 \r
74             context["namespace"] = string.IsNullOrEmpty(context.Parameters.Namespace)\r
75                                        ? dbSchema.ContextNamespace\r
76                                        : context.Parameters.Namespace;\r
77             context["database"] = dbSchema.Name;\r
78             context["generationTime"] = DateTime.Now.ToString("u");\r
79             context["class"] = dbSchema.Class;\r
80 \r
81             using (var codeWriter = CreateCodeWriter(textWriter))\r
82             {\r
83                 WriteBanner(codeWriter, context);\r
84                 WriteUsings(codeWriter, context);\r
85 \r
86                 string contextNamespace = context.Parameters.Namespace;\r
87                 if (string.IsNullOrEmpty(contextNamespace))\r
88                     contextNamespace = dbSchema.ContextNamespace;\r
89 \r
90                 string entityNamespace = context.Parameters.Namespace;\r
91                 if (string.IsNullOrEmpty(entityNamespace))\r
92                     entityNamespace = dbSchema.EntityNamespace;\r
93 \r
94                 if (contextNamespace == entityNamespace)\r
95                 {\r
96                     using (WriteNamespace(codeWriter, contextNamespace))\r
97                     {\r
98                         WriteDataContext(codeWriter, dbSchema, context);\r
99                         WriteClasses(codeWriter, dbSchema, context);\r
100                     }\r
101                 }\r
102                 else\r
103                 {\r
104                     using (WriteNamespace(codeWriter, contextNamespace))\r
105                         WriteDataContext(codeWriter, dbSchema, context);\r
106                     using (WriteNamespace(codeWriter, entityNamespace))\r
107                         WriteClasses(codeWriter, dbSchema, context);\r
108                 }\r
109             }\r
110         }\r
111 \r
112         private void WriteBanner(CodeWriter writer, GenerationContext context)\r
113         {\r
114             using (writer.WriteRegion(context.Evaluate("Auto-generated classes for ${database} database on ${generationTime}")))\r
115             {\r
116                 // http://www.network-science.de/ascii/\r
117                 // http://www.network-science.de/ascii/ascii.php?TEXT=MetalSequel&x=14&y=14&FONT=_all+fonts+with+your+text_&RICH=no&FORM=left&STRE=no&WIDT=80 \r
118                 writer.WriteCommentLines(\r
119                     @"\r
120  ____  _     __  __      _        _ \r
121 |  _ \| |__ |  \/  | ___| |_ __ _| |\r
122 | | | | '_ \| |\/| |/ _ \ __/ _` | |\r
123 | |_| | |_) | |  | |  __/ || (_| | |\r
124 |____/|_.__/|_|  |_|\___|\__\__,_|_|\r
125 ");\r
126                 writer.WriteCommentLines(context.Evaluate("Auto-generated from ${database} on ${generationTime}"));\r
127                 writer.WriteCommentLines("Please visit http://linq.to/db for more information");\r
128             }\r
129         }\r
130 \r
131         private void WriteUsings(CodeWriter writer, GenerationContext context)\r
132         {\r
133             writer.WriteUsingNamespace("System");\r
134             writer.WriteUsingNamespace("System.Data");\r
135             writer.WriteUsingNamespace("System.Data.Linq.Mapping");\r
136             writer.WriteUsingNamespace("System.Diagnostics");\r
137             writer.WriteUsingNamespace("System.Reflection");\r
138 \r
139 #if MONO_STRICT\r
140             writer.WriteUsingNamespace("System.Data.Linq");\r
141 #else\r
142             writer.WriteUsingNamespace("DbLinq.Data.Linq");\r
143             writer.WriteUsingNamespace("DbLinq.Vendor");\r
144 #endif\r
145 \r
146             //            writer.WriteUsingNamespace("System");\r
147             //            writer.WriteUsingNamespace("System.Collections.Generic");\r
148             //            writer.WriteUsingNamespace("System.ComponentModel");\r
149             //            writer.WriteUsingNamespace("System.Data");\r
150             //            writer.WriteUsingNamespace("System.Data.Linq.Mapping");\r
151             //            writer.WriteUsingNamespace("System.Diagnostics");\r
152             //            writer.WriteUsingNamespace("System.Linq");\r
153             //            writer.WriteUsingNamespace("System.Reflection");\r
154             //            writer.WriteUsingNamespace("System.Text");\r
155             //#if MONO_STRICT\r
156             //            writer.WriteUsingNamespace("System.Data.Linq");\r
157             //#else\r
158             //            writer.WriteUsingNamespace("DbLinq.Data.Linq");\r
159             //            writer.WriteUsingNamespace("DbLinq.Data.Linq.Mapping");\r
160             //#endif\r
161 \r
162             // now, we write usings required by implemented interfaces\r
163             foreach (var implementation in context.Implementations())\r
164                 implementation.WriteHeader(writer, context);\r
165 \r
166             // write namespaces for members attributes\r
167             foreach (var memberExposedAttribute in context.Parameters.MemberExposedAttributes)\r
168                 WriteUsingNamespace(writer, GetNamespace(memberExposedAttribute));\r
169 \r
170             // write namespaces for clases attributes\r
171             foreach (var entityExposedAttribute in context.Parameters.EntityExposedAttributes)\r
172                 WriteUsingNamespace(writer, GetNamespace(entityExposedAttribute));\r
173 \r
174             writer.WriteLine();\r
175         }\r
176 \r
177         /// <summary>\r
178         /// Writes a using, if given namespace is not null or empty\r
179         /// </summary>\r
180         /// <param name="writer"></param>\r
181         /// <param name="nameSpace"></param>\r
182         protected virtual void WriteUsingNamespace(CodeWriter writer, string nameSpace)\r
183         {\r
184             if (!string.IsNullOrEmpty(nameSpace))\r
185                 writer.WriteUsingNamespace(nameSpace);\r
186         }\r
187 \r
188         protected virtual string GetNamespace(string fullName)\r
189         {\r
190             var namePartIndex = fullName.LastIndexOf('.');\r
191             // if we have a dot, we have a namespace\r
192             if (namePartIndex < 0)\r
193                 return null;\r
194             return fullName.Substring(0, namePartIndex);\r
195         }\r
196 \r
197         private IDisposable WriteNamespace(CodeWriter writer, string nameSpace)\r
198         {\r
199             if (!string.IsNullOrEmpty(nameSpace))\r
200                 return writer.WriteNamespace(nameSpace);\r
201             return null;\r
202         }\r
203 \r
204         private void WriteDataContext(CodeWriter writer, Database schema, GenerationContext context)\r
205         {\r
206             if (schema.Tables.Count == 0)\r
207             {\r
208                 writer.WriteCommentLine("L69 no tables found");\r
209                 return;\r
210             }\r
211 \r
212 \r
213             string contextBase = schema.BaseType;\r
214             var contextBaseType = TypeLoader.Load(contextBase);\r
215             // if we don't specify a base type, use the default\r
216             if (string.IsNullOrEmpty(contextBase))\r
217             {\r
218                 contextBaseType = typeof(DataContext);\r
219             }\r
220             // in all cases, get the literal type name from loaded type\r
221             contextBase = writer.GetLiteralType(contextBaseType);\r
222 \r
223             var specifications = SpecificationDefinition.Partial;\r
224             if (schema.AccessModifierSpecified)\r
225                 specifications |= GetSpecificationDefinition(schema.AccessModifier);\r
226             else\r
227                 specifications |= SpecificationDefinition.Public;\r
228             if (schema.ModifierSpecified)\r
229                 specifications |= GetSpecificationDefinition(schema.Modifier);\r
230             using (writer.WriteClass(specifications, schema.Class, contextBase))\r
231             {\r
232                 WriteDataContextCtors(writer, schema, contextBaseType, context);\r
233                 WriteDataContextTables(writer, schema, context);\r
234                 WriteDataContextProcedures(writer, schema, context);\r
235             }\r
236         }\r
237 \r
238         private void WriteDataContextTables(CodeWriter writer, Database schema, GenerationContext context)\r
239         {\r
240             foreach (var table in schema.Tables)\r
241                 WriteDataContextTable(writer, table);\r
242             writer.WriteLine();\r
243         }\r
244 \r
245         protected abstract void WriteDataContextTable(CodeWriter writer, Table table);\r
246 \r
247         protected virtual Type GetType(string literalType, bool canBeNull)\r
248         {\r
249             bool isNullable = literalType.EndsWith("?");\r
250             if (isNullable)\r
251                 literalType = literalType.Substring(0, literalType.Length - 1);\r
252             bool isArray = literalType.EndsWith("[]");\r
253             if (isArray)\r
254                 literalType = literalType.Substring(0, literalType.Length - 2);\r
255             Type type = GetSimpleType(literalType);\r
256             if (type == null)\r
257                 return type;\r
258             if (isArray)\r
259                 type = type.MakeArrayType();\r
260             if (isNullable)\r
261                 type = typeof(Nullable<>).MakeGenericType(type);\r
262             else if (canBeNull)\r
263             {\r
264                 if (type.IsValueType)\r
265                     type = typeof(Nullable<>).MakeGenericType(type);\r
266             }\r
267             return type;\r
268         }\r
269 \r
270         private Type GetSimpleType(string literalType)\r
271         {\r
272             switch (literalType)\r
273             {\r
274             case "string":\r
275                 return typeof(string);\r
276             case "long":\r
277                 return typeof(long);\r
278             case "short":\r
279                 return typeof(short);\r
280             case "int":\r
281                 return typeof(int);\r
282             case "char":\r
283                 return typeof(char);\r
284             case "byte":\r
285                 return typeof(byte);\r
286             case "float":\r
287                 return typeof(float);\r
288             case "double":\r
289                 return typeof(double);\r
290             case "decimal":\r
291                 return typeof(decimal);\r
292             case "bool":\r
293                 return typeof(bool);\r
294             case "DateTime":\r
295                 return typeof(DateTime);\r
296             case "object":\r
297                 return typeof(object);\r
298             default:\r
299                 return Type.GetType(literalType);\r
300             }\r
301         }\r
302 \r
303         protected string GetAttributeShortName<T>()\r
304             where T : Attribute\r
305         {\r
306             string literalAttribute = typeof(T).Name;\r
307             string end = "Attribute";\r
308             if (literalAttribute.EndsWith(end))\r
309                 literalAttribute = literalAttribute.Substring(0, literalAttribute.Length - end.Length);\r
310             return literalAttribute;\r
311         }\r
312 \r
313         protected AttributeDefinition NewAttributeDefinition<T>()\r
314             where T : Attribute\r
315         {\r
316             return new AttributeDefinition(GetAttributeShortName<T>());\r
317         }\r
318 \r
319         protected IDisposable WriteAttributes(CodeWriter writer, params AttributeDefinition[] definitions)\r
320         {\r
321             var massDisposer = new MassDisposer();\r
322             foreach (var definition in definitions)\r
323                 massDisposer.Disposables.Add(writer.WriteAttribute(definition));\r
324             return massDisposer;\r
325         }\r
326 \r
327         protected IDisposable WriteAttributes(CodeWriter writer, params string[] definitions)\r
328         {\r
329             var attributeDefinitions = new List<AttributeDefinition>();\r
330             foreach (string definition in definitions)\r
331                 attributeDefinitions.Add(new AttributeDefinition(definition));\r
332             return WriteAttributes(writer, attributeDefinitions.ToArray());\r
333         }\r
334 \r
335         protected virtual SpecificationDefinition GetSpecificationDefinition(AccessModifier accessModifier)\r
336         {\r
337             switch (accessModifier)\r
338             {\r
339             case AccessModifier.Public:\r
340                 return SpecificationDefinition.Public;\r
341             case AccessModifier.Internal:\r
342                 return SpecificationDefinition.Internal;\r
343             case AccessModifier.Protected:\r
344                 return SpecificationDefinition.Protected;\r
345             case AccessModifier.ProtectedInternal:\r
346                 return SpecificationDefinition.Protected | SpecificationDefinition.Internal;\r
347             case AccessModifier.Private:\r
348                 return SpecificationDefinition.Private;\r
349             default:\r
350                 throw new ArgumentOutOfRangeException("accessModifier");\r
351             }\r
352         }\r
353 \r
354         protected virtual SpecificationDefinition GetSpecificationDefinition(ClassModifier classModifier)\r
355         {\r
356             switch (classModifier)\r
357             {\r
358             case ClassModifier.Sealed:\r
359                 return SpecificationDefinition.Sealed;\r
360             case ClassModifier.Abstract:\r
361                 return SpecificationDefinition.Abstract;\r
362             default:\r
363                 throw new ArgumentOutOfRangeException("classModifier");\r
364             }\r
365         }\r
366 \r
367         protected virtual SpecificationDefinition GetSpecificationDefinition(MemberModifier memberModifier)\r
368         {\r
369             switch (memberModifier)\r
370             {\r
371             case MemberModifier.Virtual:\r
372                 return SpecificationDefinition.Virtual;\r
373             case MemberModifier.Override:\r
374                 return SpecificationDefinition.Override;\r
375             case MemberModifier.New:\r
376                 return SpecificationDefinition.New;\r
377             case MemberModifier.NewVirtual:\r
378                 return SpecificationDefinition.New | SpecificationDefinition.Virtual;\r
379             default:\r
380                 throw new ArgumentOutOfRangeException("memberModifier");\r
381             }\r
382         }\r
383 \r
384         /// <summary>\r
385         /// The "custom types" are types related to a class\r
386         /// Currently, we only support enums (non-standard)\r
387         /// </summary>\r
388         /// <param name="writer"></param>\r
389         /// <param name="table"></param>\r
390         /// <param name="schema"></param>\r
391         /// <param name="context"></param>\r
392         protected virtual void WriteCustomTypes(CodeWriter writer, Table table, Database schema, GenerationContext context)\r
393         {\r
394             // detect required custom types\r
395             foreach (var column in table.Type.Columns)\r
396             {\r
397                 var extendedType = column.ExtendedType;\r
398                 var enumType = extendedType as EnumType;\r
399                 if (enumType != null)\r
400                 {\r
401                     context.ExtendedTypes[column] = new GenerationContext.ExtendedTypeAndName\r
402                     {\r
403                         Type = column.ExtendedType,\r
404                         Table = table\r
405                     };\r
406                 }\r
407             }\r
408 \r
409             var customTypesNames = new List<string>();\r
410 \r
411             // create names and avoid conflits\r
412             foreach (var extendedTypePair in context.ExtendedTypes)\r
413             {\r
414                 if (extendedTypePair.Value.Table != table)\r
415                     continue;\r
416 \r
417                 if (string.IsNullOrEmpty(extendedTypePair.Value.Type.Name))\r
418                 {\r
419                     string name = extendedTypePair.Key.Member + "Type";\r
420                     for (; ; )\r
421                     {\r
422                         if ((from t in context.ExtendedTypes.Values where t.Type.Name == name select t).FirstOrDefault() == null)\r
423                         {\r
424                             extendedTypePair.Value.Type.Name = name;\r
425                             break;\r
426                         }\r
427                         // at 3rd loop, it will look ugly, however we will never go there\r
428                         name = extendedTypePair.Value.Table.Type.Name + name;\r
429                     }\r
430                 }\r
431                 customTypesNames.Add(extendedTypePair.Value.Type.Name);\r
432             }\r
433 \r
434             // write custom types\r
435             if (customTypesNames.Count > 0)\r
436             {\r
437                 using (writer.WriteRegion(string.Format("Custom type definition for {0}", string.Join(", ", customTypesNames.ToArray()))))\r
438                 {\r
439                     // write types\r
440                     foreach (var extendedTypePair in context.ExtendedTypes)\r
441                     {\r
442                         if (extendedTypePair.Value.Table != table)\r
443                             continue;\r
444 \r
445                         var extendedType = extendedTypePair.Value.Type;\r
446                         var enumValue = extendedType as EnumType;\r
447 \r
448                         if (enumValue != null)\r
449                         {\r
450                             writer.WriteEnum(GetSpecificationDefinition(extendedTypePair.Key.AccessModifier),\r
451                                              enumValue.Name, enumValue);\r
452                         }\r
453                     }\r
454                 }\r
455             }\r
456         }\r
457     }\r
458 }\r