Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Data.Entity.Design / System / Data / EntityModel / EntityClassGenerator.cs
1 //---------------------------------------------------------------------
2 // <copyright file="EntityClassGenerator.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner       Microsoft
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
9
10 using System;
11 using System.Collections;
12 using System.Collections.Generic;
13 using System.Diagnostics;
14 using System.Globalization;
15 using System.Data;
16 using System.Data.EntityModel.SchemaObjectModel;
17 using System.Data.Metadata.Edm;
18 using System.Data.EntityModel;
19 using System.Data.Entity.Design.Common;
20 using System.IO;
21 using System.Xml;
22 using System.Data.Entity.Design.SsdlGenerator;
23 using Microsoft.Build.Utilities;
24 using System.Runtime.Versioning;
25
26 namespace System.Data.Entity.Design
27 {
28     /// <summary>
29     /// Event handler for the OnTypeGenerated event
30     /// </summary>
31     /// <param name="sender">The source of the event</param>
32     /// <param name="e">The event args</param>
33     public delegate void TypeGeneratedEventHandler(object sender, TypeGeneratedEventArgs e);
34
35     /// <summary>
36     /// Event handler for the OnPropertyGenerated event
37     /// </summary>
38     /// <param name="sender">The source of the event</param>
39     /// <param name="e">The event args</param>
40     public delegate void PropertyGeneratedEventHandler(object sender, PropertyGeneratedEventArgs e);
41
42     /// <summary>
43     /// Summary description for CodeGenerator.
44     /// </summary>
45     public sealed class EntityClassGenerator
46     {
47         #region Instance Fields
48         LanguageOption _languageOption = LanguageOption.GenerateCSharpCode;
49         EdmToObjectNamespaceMap _edmToObjectNamespaceMap = new EdmToObjectNamespaceMap();
50         #endregion
51
52         #region Events
53
54         /// <summary>
55         /// The event that is raised when a type is generated
56         /// </summary>
57         public event TypeGeneratedEventHandler OnTypeGenerated;
58
59         /// <summary>
60         /// The event that is raised when a property is generated
61         /// </summary>
62         public event PropertyGeneratedEventHandler OnPropertyGenerated;
63
64         #endregion
65
66         #region Public Methods
67         /// <summary>
68         /// 
69         /// </summary>
70         public EntityClassGenerator()
71         {
72         }
73
74         /// <summary>
75         /// 
76         /// </summary>
77         public EntityClassGenerator(LanguageOption languageOption)
78         {
79             _languageOption = EDesignUtil.CheckLanguageOptionArgument(languageOption, "languageOption");
80         }
81
82         /// <summary>
83         /// Gets and Sets the Language to use for code generation.
84         /// </summary>
85         public LanguageOption LanguageOption
86         {
87             get { return _languageOption; }
88             set { _languageOption = EDesignUtil.CheckLanguageOptionArgument(value, "value"); }
89         }
90
91         /// <summary>
92         /// Gets the map entries use to customize the namespace of .net types that are generated
93         /// and referenced by the generated code
94         /// </summary>
95         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Edm")]
96         public EdmToObjectNamespaceMap EdmToObjectNamespaceMap
97         {
98             get { return _edmToObjectNamespaceMap; }
99         }
100
101
102         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Edm")]
103         public IList<EdmSchemaError> GenerateCode(XmlReader sourceEdmSchema, TextWriter target)
104         {
105             EDesignUtil.CheckArgumentNull(sourceEdmSchema, "sourceEdmSchema");
106             EDesignUtil.CheckArgumentNull(target, "target");
107             return GenerateCode(sourceEdmSchema, target, new XmlReader[] { });
108         }
109
110         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Edm")]
111         [ResourceExposure(ResourceScope.None)] //No resource is exposed since we pass in null as the target paath.
112         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] //For GenerateCodeCommon method call. Since we use null as the path, we have not changed the scope of the resource.
113         public IList<EdmSchemaError> GenerateCode(XmlReader sourceEdmSchema, TextWriter target, IEnumerable<XmlReader> additionalEdmSchemas)
114         {
115             EDesignUtil.CheckArgumentNull(sourceEdmSchema, "sourceEdmSchema");
116             EDesignUtil.CheckArgumentNull(additionalEdmSchemas, "additionalEdmSchemas");
117             EDesignUtil.CheckArgumentNull(target, "target");
118             
119             List<EdmSchemaError> errors = new List<EdmSchemaError>();
120             try
121             {
122                 MetadataArtifactLoader sourceLoader = new MetadataArtifactLoaderXmlReaderWrapper(sourceEdmSchema);
123                 List<MetadataArtifactLoader> loaders = new List<MetadataArtifactLoader>();
124                 loaders.Add(sourceLoader);
125
126                 int index = 0;
127                 foreach (XmlReader additionalEdmSchema in additionalEdmSchemas)
128                 {
129                     if (additionalEdmSchema == null)
130                     {
131                         throw EDesignUtil.Argument(Strings.NullAdditionalSchema("additionalEdmSchema", index));
132                     }
133
134                     try
135                     {
136                         MetadataArtifactLoader loader = new MetadataArtifactLoaderXmlReaderWrapper(additionalEdmSchema);
137                         Debug.Assert(loader != null, "when is the loader ever null?");
138                         loaders.Add(loader);
139                     }
140                     catch (Exception e)
141                     {
142                         if (MetadataUtil.IsCatchableExceptionType(e))
143                         {
144                             errors.Add(new EdmSchemaError(e.Message,
145                                 (int)ModelBuilderErrorCode.CodeGenAdditionalEdmSchemaIsInvalid,
146                                 EdmSchemaErrorSeverity.Error));
147                         }
148                         else
149                         {
150                             throw;
151                         }
152                     }
153                     index++;
154                 }
155                 ThrowOnAnyNonWarningErrors(errors);
156
157                 GenerateCodeCommon(sourceLoader, 
158                     loaders, 
159                     new LazyTextWriterCreator(target),
160                     null,  // source path
161                     null,  // target file path
162                     false, // dispose readers?
163                     errors);
164             }
165             catch (TerminalErrorException)
166             {
167                 // do nothing
168                 // just a place to jump when errors are detected
169             }
170
171             return errors;
172         }
173
174         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Edm")]
175         [ResourceExposure(ResourceScope.Machine)] //Exposes the sourceEdmSchemaFilePath which is a Machine resource
176         [ResourceConsumption(ResourceScope.Machine)] //For GenerateCode method call. But the path is not created in this method.
177         public IList<EdmSchemaError> GenerateCode(string sourceEdmSchemaFilePath, string targetFilePath)
178         {
179             return GenerateCode(sourceEdmSchemaFilePath, targetFilePath, new string[] { });        
180         }
181
182         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Edm")]
183         [ResourceExposure(ResourceScope.Machine)] //Exposes the sourceEdmSchemaFilePath which is a Machine resource
184         [ResourceConsumption(ResourceScope.Machine)] //For MetadataArtifactLoader.Create method call. But the path is not created in this method.
185         public IList<EdmSchemaError> GenerateCode(string sourceEdmSchemaFilePath, string targetPath, IEnumerable<string> additionalEdmSchemaFilePaths)
186         {
187             EDesignUtil.CheckStringArgument(sourceEdmSchemaFilePath, "sourceEdmSchemaFilePath");
188             EDesignUtil.CheckArgumentNull(additionalEdmSchemaFilePaths, "additionalEdmSchemaFilePaths");
189             EDesignUtil.CheckStringArgument(targetPath, "targetPath");
190
191             List<EdmSchemaError> errors = new List<EdmSchemaError>();
192             try
193             {
194                 // create a loader for the source
195                 HashSet<string> uriRegistry = new HashSet<string>();
196                 MetadataArtifactLoader sourceLoader;
197                 try
198                 {
199                     sourceLoader = MetadataArtifactLoader.Create(sourceEdmSchemaFilePath, MetadataArtifactLoader.ExtensionCheck.Specific,
200                         XmlConstants.CSpaceSchemaExtension, uriRegistry);
201                 }
202                 catch (MetadataException e)
203                 {
204                     errors.Add(CreateErrorForException(ModelBuilderErrorCode.CodeGenSourceFilePathIsInvalid, e, sourceEdmSchemaFilePath));
205                     return errors;
206                 }
207
208                 if (sourceLoader.IsComposite)
209                 {
210                     throw new ArgumentException(Strings.CodeGenSourceFilePathIsNotAFile, "sourceEdmSchemaPath");
211                 }
212
213                 // create loaders for all the additional schemas
214                 List<MetadataArtifactLoader> loaders = new List<MetadataArtifactLoader>();
215                 loaders.Add(sourceLoader);
216                 int index = 0;
217                 foreach (string additionalSchemaFilePath in additionalEdmSchemaFilePaths)
218                 {
219                     if (additionalSchemaFilePath == null)
220                     {
221                         throw EDesignUtil.Argument(Strings.NullAdditionalSchema("additionalEdmSchemaFilePaths", index));
222                     }
223
224                     try
225                     {
226                         MetadataArtifactLoader loader = MetadataArtifactLoader.Create(additionalSchemaFilePath,
227                             MetadataArtifactLoader.ExtensionCheck.Specific,
228                             XmlConstants.CSpaceSchemaExtension, uriRegistry);
229                         Debug.Assert(loader != null, "when is the loader ever null?");
230                         loaders.Add(loader);
231                     }
232                     catch (Exception e)
233                     {
234                         if(MetadataUtil.IsCatchableExceptionType(e))
235                         {
236                             errors.Add(CreateErrorForException(ModelBuilderErrorCode.CodeGenAdditionalEdmSchemaIsInvalid, e, additionalSchemaFilePath));
237                         }
238                         else
239                         {
240                             throw;
241                         }
242                     }
243                     index++;
244                 }
245
246                 ThrowOnAnyNonWarningErrors(errors);
247                 try
248                 {
249                     using (LazyTextWriterCreator target = new LazyTextWriterCreator(targetPath))
250                     {
251                         GenerateCodeCommon(sourceLoader, loaders, target, sourceEdmSchemaFilePath, targetPath,
252                             true, // dispose readers
253                             errors);
254                     }
255                 }
256                 catch (System.IO.IOException ex)
257                 {
258                     errors.Add(CreateErrorForException(System.Data.EntityModel.SchemaObjectModel.ErrorCode.IOException, ex, targetPath));
259                     return errors;
260                 }
261             }
262             catch (TerminalErrorException)
263             {
264                 // do nothing
265                 // just a place to jump when errors are detected
266             }
267             return errors;
268         }
269
270         private void GenerateCodeCommon(MetadataArtifactLoader sourceLoader, 
271             List<MetadataArtifactLoader> loaders,
272             LazyTextWriterCreator target,
273             string sourceEdmSchemaFilePath,
274             string targetFilePath,
275             bool closeReaders,
276             List<EdmSchemaError> errors)
277         {
278             MetadataArtifactLoaderComposite composite = new MetadataArtifactLoaderComposite(loaders);
279             
280             // create the schema manager from the xml readers
281             Dictionary<MetadataArtifactLoader, XmlReader> readerSourceMap = new Dictionary<MetadataArtifactLoader, XmlReader>();
282             IList<Schema> schemas;
283             List<XmlReader> readers = composite.GetReaders(readerSourceMap);
284
285             try
286             {
287                 IList<EdmSchemaError> schemaManagerErrors =
288                     SchemaManager.ParseAndValidate(readers,
289                         composite.GetPaths(),
290                         SchemaDataModelOption.EntityDataModel,
291                         EdmProviderManifest.Instance,
292                         out schemas);
293                 errors.AddRange(schemaManagerErrors);
294             }
295             finally
296             {
297                 if (closeReaders)
298                 {
299                     MetadataUtil.DisposeXmlReaders(readers);
300                 }
301             }
302             ThrowOnAnyNonWarningErrors(errors);
303             Debug.Assert(readerSourceMap.ContainsKey(sourceLoader), "the source loader didn't produce any of the xml readers...");
304             XmlReader sourceReader = readerSourceMap[sourceLoader];
305
306
307             // use the index of the "source" xml reader as the index of the "source" schema
308             Debug.Assert(readers.Contains(sourceReader), "the source reader is not in the list of readers");
309             int index = readers.IndexOf(sourceReader);
310             Debug.Assert(index >= 0, "couldn't find the source reader in the list of readers");
311
312             Debug.Assert(readers.Count == schemas.Count, "We have a different number of readers than schemas");
313             Schema sourceSchema = schemas[index];
314             Debug.Assert(sourceSchema != null, "sourceSchema is null");
315
316             // create the EdmItemCollection from the schemas
317             EdmItemCollection itemCollection = new EdmItemCollection(schemas);
318             if (EntityFrameworkVersionsUtil.ConvertToVersion(itemCollection.EdmVersion) >= EntityFrameworkVersions.Version2)
319             {
320                 throw EDesignUtil.InvalidOperation(Strings.TargetEntityFrameworkVersionToNewForEntityClassGenerator);  
321             }
322
323             // generate code
324             ClientApiGenerator generator = new ClientApiGenerator(sourceSchema, itemCollection, this, errors);
325             generator.GenerateCode(target, targetFilePath);
326         }
327
328
329         #endregion
330
331         #region Private Methods
332         private static EdmSchemaError CreateErrorForException(System.Data.EntityModel.SchemaObjectModel.ErrorCode errorCode, System.Exception exception, string sourceLocation)
333         {
334             Debug.Assert(exception != null);
335             Debug.Assert(sourceLocation != null);
336
337             return new EdmSchemaError(exception.Message, (int)errorCode, EdmSchemaErrorSeverity.Error, sourceLocation, 0, 0, exception);
338         }
339
340         internal static EdmSchemaError CreateErrorForException(ModelBuilderErrorCode errorCode, System.Exception exception, string sourceLocation)
341         {
342             Debug.Assert(exception != null);
343             Debug.Assert(sourceLocation != null);
344
345             return new EdmSchemaError(exception.Message, (int)errorCode, EdmSchemaErrorSeverity.Error, sourceLocation, 0, 0, exception);
346         }
347
348         internal static EdmSchemaError CreateErrorForException(ModelBuilderErrorCode errorCode, System.Exception exception)
349         {
350             Debug.Assert(exception != null);
351
352             return new EdmSchemaError(exception.Message, (int)errorCode, EdmSchemaErrorSeverity.Error, null, 0, 0, exception);
353         }
354
355         private void ThrowOnAnyNonWarningErrors(List<EdmSchemaError> errors)
356         {
357             foreach (EdmSchemaError error in errors)
358             {
359                 if (error.Severity != EdmSchemaErrorSeverity.Warning)
360                 {
361                     throw new TerminalErrorException();
362                 }
363             }
364         }
365
366         #endregion
367
368         #region Event Helpers
369
370         /// <summary>
371         /// Helper method that raises the TypeGenerated event
372         /// </summary>
373         /// <param name="eventArgs">The event arguments passed to the subscriber</param>
374         internal void RaiseTypeGeneratedEvent(TypeGeneratedEventArgs eventArgs)
375         {
376             if (this.OnTypeGenerated != null)
377             {
378                 this.OnTypeGenerated(this, eventArgs);
379             }
380         }
381
382         /// <summary>
383         /// Helper method that raises the PropertyGenerated event
384         /// </summary>
385         /// <param name="eventArgs">The event arguments passed to the subscriber</param>
386         internal void RaisePropertyGeneratedEvent(PropertyGeneratedEventArgs eventArgs)
387         {
388             if (this.OnPropertyGenerated != null)
389             {
390                 this.OnPropertyGenerated(this, eventArgs);
391             }
392         }
393
394         #endregion
395     }
396 }