2010-03-12 Jb Evain <jbevain@novell.com>
[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"] = context.Parameters.GenerateTimestamps\r
79                 ? DateTime.Now.ToString("u")\r
80                 : "[TIMESTAMP]";\r
81             context["class"] = dbSchema.Class;\r
82 \r
83             using (var codeWriter = CreateCodeWriter(textWriter))\r
84             {\r
85                 WriteBanner(codeWriter, context);\r
86                 WriteUsings(codeWriter, context);\r
87 \r
88                 string contextNamespace = context.Parameters.Namespace;\r
89                 if (string.IsNullOrEmpty(contextNamespace))\r
90                     contextNamespace = dbSchema.ContextNamespace;\r
91 \r
92                 string entityNamespace = context.Parameters.Namespace;\r
93                 if (string.IsNullOrEmpty(entityNamespace))\r
94                     entityNamespace = dbSchema.EntityNamespace;\r
95 \r
96                 bool generateDataContext = true;\r
97                 var types = context.Parameters.GenerateTypes;\r
98                 if (types.Count > 0)\r
99                     generateDataContext = types.Contains(dbSchema.Class);\r
100 \r
101                 if (contextNamespace == entityNamespace)\r
102                 {\r
103                     using (WriteNamespace(codeWriter, contextNamespace))\r
104                     {\r
105                         if (generateDataContext)\r
106                             WriteDataContext(codeWriter, dbSchema, context);\r
107                         WriteClasses(codeWriter, dbSchema, context);\r
108                     }\r
109                 }\r
110                 else\r
111                 {\r
112                     if (generateDataContext)\r
113                         using (WriteNamespace(codeWriter, contextNamespace))\r
114                             WriteDataContext(codeWriter, dbSchema, context);\r
115                     using (WriteNamespace(codeWriter, entityNamespace))\r
116                         WriteClasses(codeWriter, dbSchema, context);\r
117                 }\r
118             }\r
119         }\r
120 \r
121         private void WriteBanner(CodeWriter writer, GenerationContext context)\r
122         {\r
123             using (writer.WriteRegion(context.Evaluate("Auto-generated classes for ${database} database on ${generationTime}")))\r
124             {\r
125                 // http://www.network-science.de/ascii/\r
126                 // 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
127                 writer.WriteCommentLines(\r
128                     @"\r
129  ____  _     __  __      _        _ \r
130 |  _ \| |__ |  \/  | ___| |_ __ _| |\r
131 | | | | '_ \| |\/| |/ _ \ __/ _` | |\r
132 | |_| | |_) | |  | |  __/ || (_| | |\r
133 |____/|_.__/|_|  |_|\___|\__\__,_|_|\r
134 ");\r
135                 writer.WriteCommentLines(context.Evaluate("Auto-generated from ${database} on ${generationTime}"));\r
136                 writer.WriteCommentLines("Please visit http://linq.to/db for more information");\r
137             }\r
138         }\r
139 \r
140         private void WriteUsings(CodeWriter writer, GenerationContext context)\r
141         {\r
142             writer.WriteUsingNamespace("System");\r
143             writer.WriteUsingNamespace("System.Data");\r
144             writer.WriteUsingNamespace("System.Data.Linq.Mapping");\r
145             writer.WriteUsingNamespace("System.Diagnostics");\r
146             writer.WriteUsingNamespace("System.Reflection");\r
147 \r
148 #if MONO_STRICT\r
149             writer.WriteUsingNamespace("System.Data.Linq");\r
150 #else\r
151             writer.WriteLine("#if MONO_STRICT");\r
152             writer.WriteUsingNamespace("System.Data.Linq");\r
153             writer.WriteLine("#else   // MONO_STRICT");\r
154             writer.WriteUsingNamespace("DbLinq.Data.Linq");\r
155             writer.WriteUsingNamespace("DbLinq.Vendor");\r
156             writer.WriteLine("#endif  // MONO_STRICT");\r
157 #endif\r
158 \r
159             //            writer.WriteUsingNamespace("System");\r
160             //            writer.WriteUsingNamespace("System.Collections.Generic");\r
161             //            writer.WriteUsingNamespace("System.ComponentModel");\r
162             //            writer.WriteUsingNamespace("System.Data");\r
163             //            writer.WriteUsingNamespace("System.Data.Linq.Mapping");\r
164             //            writer.WriteUsingNamespace("System.Diagnostics");\r
165             //            writer.WriteUsingNamespace("System.Linq");\r
166             //            writer.WriteUsingNamespace("System.Reflection");\r
167             //            writer.WriteUsingNamespace("System.Text");\r
168             //#if MONO_STRICT\r
169             //            writer.WriteUsingNamespace("System.Data.Linq");\r
170             //#else\r
171             //            writer.WriteUsingNamespace("DbLinq.Data.Linq");\r
172             //            writer.WriteUsingNamespace("DbLinq.Data.Linq.Mapping");\r
173             //#endif\r
174 \r
175             // now, we write usings required by implemented interfaces\r
176             foreach (var implementation in context.Implementations())\r
177                 implementation.WriteHeader(writer, context);\r
178 \r
179             // write namespaces for members attributes\r
180             foreach (var memberExposedAttribute in context.Parameters.MemberExposedAttributes)\r
181                 WriteUsingNamespace(writer, GetNamespace(memberExposedAttribute));\r
182 \r
183             // write namespaces for clases attributes\r
184             foreach (var entityExposedAttribute in context.Parameters.EntityExposedAttributes)\r
185                 WriteUsingNamespace(writer, GetNamespace(entityExposedAttribute));\r
186 \r
187             writer.WriteLine();\r
188         }\r
189 \r
190         /// <summary>\r
191         /// Writes a using, if given namespace is not null or empty\r
192         /// </summary>\r
193         /// <param name="writer"></param>\r
194         /// <param name="nameSpace"></param>\r
195         protected virtual void WriteUsingNamespace(CodeWriter writer, string nameSpace)\r
196         {\r
197             if (!string.IsNullOrEmpty(nameSpace))\r
198                 writer.WriteUsingNamespace(nameSpace);\r
199         }\r
200 \r
201         protected virtual string GetNamespace(string fullName)\r
202         {\r
203             var namePartIndex = fullName.LastIndexOf('.');\r
204             // if we have a dot, we have a namespace\r
205             if (namePartIndex < 0)\r
206                 return null;\r
207             return fullName.Substring(0, namePartIndex);\r
208         }\r
209 \r
210         private IDisposable WriteNamespace(CodeWriter writer, string nameSpace)\r
211         {\r
212             if (!string.IsNullOrEmpty(nameSpace))\r
213                 return writer.WriteNamespace(nameSpace);\r
214             return null;\r
215         }\r
216 \r
217         private void WriteDataContext(CodeWriter writer, Database schema, GenerationContext context)\r
218         {\r
219             if (schema.Tables.Count == 0)\r
220             {\r
221                 writer.WriteCommentLine("L69 no tables found");\r
222                 return;\r
223             }\r
224 \r
225 \r
226             string contextBase = schema.BaseType;\r
227             var contextBaseType = TypeLoader.Load(contextBase);\r
228             // if we don't specify a base type, use the default\r
229             if (string.IsNullOrEmpty(contextBase))\r
230             {\r
231                 contextBaseType = typeof(DataContext);\r
232             }\r
233             // in all cases, get the literal type name from loaded type\r
234             contextBase = writer.GetLiteralType(contextBaseType);\r
235 \r
236             var specifications = SpecificationDefinition.Partial;\r
237             if (schema.AccessModifierSpecified)\r
238                 specifications |= GetSpecificationDefinition(schema.AccessModifier);\r
239             else\r
240                 specifications |= SpecificationDefinition.Public;\r
241             if (schema.ModifierSpecified)\r
242                 specifications |= GetSpecificationDefinition(schema.Modifier);\r
243             using (writer.WriteClass(specifications, schema.Class, contextBase))\r
244             {\r
245                 WriteDataContextExtensibilityDeclarations(writer, schema, context);\r
246                 WriteDataContextCtors(writer, schema, contextBaseType, context);\r
247                 WriteDataContextTables(writer, schema, context);\r
248                 WriteDataContextProcedures(writer, schema, context);\r
249             }\r
250         }\r
251 \r
252         private void WriteDataContextTables(CodeWriter writer, Database schema, GenerationContext context)\r
253         {\r
254             foreach (var table in schema.Tables)\r
255                 WriteDataContextTable(writer, table);\r
256             writer.WriteLine();\r
257         }\r
258 \r
259         protected abstract void WriteDataContextTable(CodeWriter writer, Table table);\r
260 \r
261         protected virtual Type GetType(string literalType, bool canBeNull)\r
262         {\r
263             bool isNullable = literalType.EndsWith("?");\r
264             if (isNullable)\r
265                 literalType = literalType.Substring(0, literalType.Length - 1);\r
266             bool isArray = literalType.EndsWith("[]");\r
267             if (isArray)\r
268                 literalType = literalType.Substring(0, literalType.Length - 2);\r
269             Type type = GetSimpleType(literalType);\r
270             if (type == null)\r
271                 return type;\r
272             if (isArray)\r
273                 type = type.MakeArrayType();\r
274             if (isNullable)\r
275                 type = typeof(Nullable<>).MakeGenericType(type);\r
276             else if (canBeNull)\r
277             {\r
278                 if (type.IsValueType)\r
279                     type = typeof(Nullable<>).MakeGenericType(type);\r
280             }\r
281             return type;\r
282         }\r
283 \r
284         private Type GetSimpleType(string literalType)\r
285         {\r
286             switch (literalType)\r
287             {\r
288             case "string":\r
289                 return typeof(string);\r
290             case "long":\r
291                 return typeof(long);\r
292             case "short":\r
293                 return typeof(short);\r
294             case "int":\r
295                 return typeof(int);\r
296             case "char":\r
297                 return typeof(char);\r
298             case "byte":\r
299                 return typeof(byte);\r
300             case "float":\r
301                 return typeof(float);\r
302             case "double":\r
303                 return typeof(double);\r
304             case "decimal":\r
305                 return typeof(decimal);\r
306             case "bool":\r
307                 return typeof(bool);\r
308             case "DateTime":\r
309                 return typeof(DateTime);\r
310             case "object":\r
311                 return typeof(object);\r
312             default:\r
313                 return Type.GetType(literalType);\r
314             }\r
315         }\r
316 \r
317         protected string GetAttributeShortName<T>()\r
318             where T : Attribute\r
319         {\r
320             string literalAttribute = typeof(T).Name;\r
321             string end = "Attribute";\r
322             if (literalAttribute.EndsWith(end))\r
323                 literalAttribute = literalAttribute.Substring(0, literalAttribute.Length - end.Length);\r
324             return literalAttribute;\r
325         }\r
326 \r
327         protected AttributeDefinition NewAttributeDefinition<T>()\r
328             where T : Attribute\r
329         {\r
330             return new AttributeDefinition(GetAttributeShortName<T>());\r
331         }\r
332 \r
333         protected IDisposable WriteAttributes(CodeWriter writer, params AttributeDefinition[] definitions)\r
334         {\r
335             var massDisposer = new MassDisposer();\r
336             foreach (var definition in definitions)\r
337                 massDisposer.Disposables.Add(writer.WriteAttribute(definition));\r
338             return massDisposer;\r
339         }\r
340 \r
341         protected IDisposable WriteAttributes(CodeWriter writer, params string[] definitions)\r
342         {\r
343             var attributeDefinitions = new List<AttributeDefinition>();\r
344             foreach (string definition in definitions)\r
345                 attributeDefinitions.Add(new AttributeDefinition(definition));\r
346             return WriteAttributes(writer, attributeDefinitions.ToArray());\r
347         }\r
348 \r
349         protected virtual SpecificationDefinition GetSpecificationDefinition(AccessModifier accessModifier)\r
350         {\r
351             switch (accessModifier)\r
352             {\r
353             case AccessModifier.Public:\r
354                 return SpecificationDefinition.Public;\r
355             case AccessModifier.Internal:\r
356                 return SpecificationDefinition.Internal;\r
357             case AccessModifier.Protected:\r
358                 return SpecificationDefinition.Protected;\r
359             case AccessModifier.ProtectedInternal:\r
360                 return SpecificationDefinition.Protected | SpecificationDefinition.Internal;\r
361             case AccessModifier.Private:\r
362                 return SpecificationDefinition.Private;\r
363             default:\r
364                 throw new ArgumentOutOfRangeException("accessModifier");\r
365             }\r
366         }\r
367 \r
368         protected virtual SpecificationDefinition GetSpecificationDefinition(ClassModifier classModifier)\r
369         {\r
370             switch (classModifier)\r
371             {\r
372             case ClassModifier.Sealed:\r
373                 return SpecificationDefinition.Sealed;\r
374             case ClassModifier.Abstract:\r
375                 return SpecificationDefinition.Abstract;\r
376             default:\r
377                 throw new ArgumentOutOfRangeException("classModifier");\r
378             }\r
379         }\r
380 \r
381         protected virtual SpecificationDefinition GetSpecificationDefinition(MemberModifier memberModifier)\r
382         {\r
383             switch (memberModifier)\r
384             {\r
385             case MemberModifier.Virtual:\r
386                 return SpecificationDefinition.Virtual;\r
387             case MemberModifier.Override:\r
388                 return SpecificationDefinition.Override;\r
389             case MemberModifier.New:\r
390                 return SpecificationDefinition.New;\r
391             case MemberModifier.NewVirtual:\r
392                 return SpecificationDefinition.New | SpecificationDefinition.Virtual;\r
393             default:\r
394                 throw new ArgumentOutOfRangeException("memberModifier");\r
395             }\r
396         }\r
397 \r
398         /// <summary>\r
399         /// The "custom types" are types related to a class\r
400         /// Currently, we only support enums (non-standard)\r
401         /// </summary>\r
402         /// <param name="writer"></param>\r
403         /// <param name="table"></param>\r
404         /// <param name="schema"></param>\r
405         /// <param name="context"></param>\r
406         protected virtual void WriteCustomTypes(CodeWriter writer, Table table, Database schema, GenerationContext context)\r
407         {\r
408             // detect required custom types\r
409             foreach (var column in table.Type.Columns)\r
410             {\r
411                 var extendedType = column.ExtendedType;\r
412                 var enumType = extendedType as EnumType;\r
413                 if (enumType != null)\r
414                 {\r
415                     context.ExtendedTypes[column] = new GenerationContext.ExtendedTypeAndName\r
416                     {\r
417                         Type = column.ExtendedType,\r
418                         Table = table\r
419                     };\r
420                 }\r
421             }\r
422 \r
423             var customTypesNames = new List<string>();\r
424 \r
425             // create names and avoid conflits\r
426             foreach (var extendedTypePair in context.ExtendedTypes)\r
427             {\r
428                 if (extendedTypePair.Value.Table != table)\r
429                     continue;\r
430 \r
431                 if (string.IsNullOrEmpty(extendedTypePair.Value.Type.Name))\r
432                 {\r
433                     string name = extendedTypePair.Key.Member + "Type";\r
434                     for (; ; )\r
435                     {\r
436                         if ((from t in context.ExtendedTypes.Values where t.Type.Name == name select t).FirstOrDefault() == null)\r
437                         {\r
438                             extendedTypePair.Value.Type.Name = name;\r
439                             break;\r
440                         }\r
441                         // at 3rd loop, it will look ugly, however we will never go there\r
442                         name = extendedTypePair.Value.Table.Type.Name + name;\r
443                     }\r
444                 }\r
445                 customTypesNames.Add(extendedTypePair.Value.Type.Name);\r
446             }\r
447 \r
448             // write custom types\r
449             if (customTypesNames.Count > 0)\r
450             {\r
451                 using (writer.WriteRegion(string.Format("Custom type definition for {0}", string.Join(", ", customTypesNames.ToArray()))))\r
452                 {\r
453                     // write types\r
454                     foreach (var extendedTypePair in context.ExtendedTypes)\r
455                     {\r
456                         if (extendedTypePair.Value.Table != table)\r
457                             continue;\r
458 \r
459                         var extendedType = extendedTypePair.Value.Type;\r
460                         var enumValue = extendedType as EnumType;\r
461 \r
462                         if (enumValue != null)\r
463                         {\r
464                             writer.WriteEnum(GetSpecificationDefinition(extendedTypePair.Key.AccessModifier),\r
465                                              enumValue.Name, enumValue);\r
466                         }\r
467                     }\r
468                 }\r
469             }\r
470         }\r
471     }\r
472 }\r