1 //---------------------------------------------------------------------
2 // <copyright file="ClientApiGenerator.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
8 //---------------------------------------------------------------------
12 using System.CodeDom.Compiler;
13 using System.Collections;
14 using System.Collections.Generic;
16 using System.Data.EntityModel.Emitters;
17 using SOM = System.Data.EntityModel.SchemaObjectModel;
18 using System.Diagnostics;
19 using System.Data.Metadata.Edm;
20 using System.Data.Entity.Design;
22 using System.Data.EntityModel.SchemaObjectModel;
23 using System.Data.Entity.Design.SsdlGenerator;
25 using System.Data.Entity.Design.Common;
26 using System.Runtime.Versioning;
28 namespace System.Data.EntityModel
31 /// Summary description for ClientApiGenerator.
33 internal sealed class ClientApiGenerator
35 #region Instance Fields
36 private string _codeNamespace = null;
37 private CodeCompileUnit _compileUnit = null;
38 private bool _isLanguageCaseSensitive = true;
40 private EdmItemCollection _edmItemCollection = null;
41 private Schema _sourceSchema = null;
42 private FixUpCollection _fixUps = null;
43 private AttributeEmitter _attributeEmitter = null;
44 EntityClassGenerator _generator;
45 List<EdmSchemaError> _errors;
46 TypeReference _typeReference = new TypeReference();
50 #region Public Methods
51 public ClientApiGenerator(Schema sourceSchema, EdmItemCollection edmItemCollection, EntityClassGenerator generator, List<EdmSchemaError> errors)
53 Debug.Assert(sourceSchema != null, "sourceSchema is null");
54 Debug.Assert(edmItemCollection != null, "edmItemCollection is null");
55 Debug.Assert(generator != null, "generator is null");
56 Debug.Assert(errors != null, "errors is null");
58 _edmItemCollection = edmItemCollection;
59 _sourceSchema = sourceSchema;
60 _generator = generator;
62 _attributeEmitter = new AttributeEmitter(_typeReference);
66 /// Parses a source Schema and outputs client-side generated code to
67 /// the output TextWriter.
69 /// <param name="schema">The source Schema</param>
70 /// <param name="output">The TextWriter in which to write the output</param>
71 /// <param name="outputUri">The Uri for the output. Can be null.</param>
72 /// <returns>A list of GeneratorErrors.</returns>
73 [ResourceExposure(ResourceScope.None)] //No resource is exposed.
74 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] //For Path.GetTempPath method.
75 //We use tha path to create a temp file stream which is consistent with the resource consumption of machine.
77 internal void GenerateCode(LazyTextWriterCreator target, string targetLocation)
79 Debug.Assert(target != null, "target parameter is null");
81 IndentedTextWriter indentedTextWriter = null;
82 System.IO.Stream tempFileStream = null;
83 System.IO.StreamReader reader = null;
84 System.IO.StreamWriter writer = null;
85 TempFileCollection tempFiles = null;
88 CodeDomProvider provider = null;
91 case LanguageOption.GenerateCSharpCode:
92 provider = new Microsoft.CSharp.CSharpCodeProvider();
95 case LanguageOption.GenerateVBCode:
96 provider = new Microsoft.VisualBasic.VBCodeProvider();
100 _isLanguageCaseSensitive = (provider.LanguageOptions & LanguageOptions.CaseInsensitive) == 0;
102 new NamespaceEmitter(this, _codeNamespace, target.TargetFilePath).Emit();
104 // if there were errors we don't need the output file
110 if (FixUps.Count == 0 || !FixUpCollection.IsLanguageSupported(Language))
112 indentedTextWriter = new IndentedTextWriter(target.GetOrCreateTextWriter(), "\t");
116 // need to write to a temporary file so we can do fixups...
117 tempFiles = new TempFileCollection(Path.GetTempPath());
118 string filename = Path.Combine(tempFiles.TempDir, "EdmCodeGenFixup-" + Guid.NewGuid().ToString() + ".tmp");
119 tempFiles.AddFile(filename, false);
120 tempFileStream = new System.IO.FileStream(filename, System.IO.FileMode.CreateNew, System.IO.FileAccess.ReadWrite,
121 System.IO.FileShare.None);
122 indentedTextWriter = new IndentedTextWriter(new System.IO.StreamWriter(tempFileStream), "\t");
125 CodeGeneratorOptions styleOptions = new CodeGeneratorOptions();
126 styleOptions.BracingStyle = "C";
127 styleOptions.BlankLinesBetweenMembers = false;
128 styleOptions.VerbatimOrder = true;
129 provider.GenerateCodeFromCompileUnit(CompileUnit, indentedTextWriter, styleOptions);
131 // if we wrote to a temp file need to post process the file...
132 if (tempFileStream != null)
134 indentedTextWriter.Flush();
135 tempFileStream.Seek(0, System.IO.SeekOrigin.Begin);
136 reader = new System.IO.StreamReader(tempFileStream);
137 FixUps.Do(reader, target.GetOrCreateTextWriter(), Language, SourceObjectNamespaceName != string.Empty);
140 catch (System.UnauthorizedAccessException ex)
142 AddError(ModelBuilderErrorCode.SecurityError, EdmSchemaErrorSeverity.Error, ex);
144 catch (System.IO.FileNotFoundException ex)
146 AddError(ModelBuilderErrorCode.FileNotFound, EdmSchemaErrorSeverity.Error, ex);
148 catch (System.Security.SecurityException ex)
150 AddError(ModelBuilderErrorCode.SecurityError, EdmSchemaErrorSeverity.Error, ex);
152 catch (System.IO.DirectoryNotFoundException ex)
154 AddError(ModelBuilderErrorCode.DirectoryNotFound, EdmSchemaErrorSeverity.Error, ex);
156 catch (System.IO.IOException ex)
158 AddError(ModelBuilderErrorCode.IOException, EdmSchemaErrorSeverity.Error, ex);
162 if (indentedTextWriter != null)
164 indentedTextWriter.Close();
166 if (tempFileStream != null)
168 tempFileStream.Close();
170 if (tempFiles != null)
173 ((IDisposable)tempFiles).Dispose();
187 /// Verification code invoked for types
189 /// <param name="item">The type being generated</param>
190 internal void VerifyLanguageCaseSensitiveCompatibilityForType(GlobalItem item)
192 if (_isLanguageCaseSensitive)
194 return; // no validation necessary
199 _edmItemCollection.GetItem<GlobalItem>(
204 catch (InvalidOperationException)
206 AddError(Strings.ItemExistsWithDifferentCase(item.BuiltInTypeKind.ToString(), item.Identity), ModelBuilderErrorCode.IncompatibleSettingForCaseSensitiveOption,
207 EdmSchemaErrorSeverity.Error, item.Identity);
213 /// Verification code invoked for properties
215 /// <param name="item">The property or navigation property being generated</param>
216 internal void VerifyLanguageCaseSensitiveCompatibilityForProperty(EdmMember item)
218 if (_isLanguageCaseSensitive)
220 return; // no validation necessary
223 Debug.Assert(item != null);
225 ReadOnlyMetadataCollection<EdmMember> members = item.DeclaringType.Members;
227 HashSet<string> set = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
229 foreach (EdmMember member in members)
231 if (set.Contains(member.Identity) &&
232 item.Identity.Equals(member.Identity, StringComparison.OrdinalIgnoreCase))
234 AddError(Strings.PropertyExistsWithDifferentCase(item.Identity), ModelBuilderErrorCode.IncompatibleSettingForCaseSensitiveOption,
235 EdmSchemaErrorSeverity.Error, item.DeclaringType.FullName, item.Identity);
239 set.Add(member.Identity);
245 /// Verification code invoked for entity sets
247 /// <param name="item">The entity container being generated</param>
248 internal void VerifyLanguageCaseSensitiveCompatibilityForEntitySet(System.Data.Metadata.Edm.EntityContainer item)
250 if (_isLanguageCaseSensitive)
252 return; // no validation necessary
255 Debug.Assert(item != null);
257 HashSet<string> set = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
259 foreach (EntitySetBase entitySetBase in item.BaseEntitySets)
261 if (MetadataUtil.IsEntitySet(entitySetBase))
263 EntitySet entitySet = (EntitySet)entitySetBase;
264 if (set.Contains(entitySet.Identity))
266 AddError(ModelBuilderErrorCode.IncompatibleSettingForCaseSensitiveOption,
267 EdmSchemaErrorSeverity.Error, new InvalidOperationException(Strings.EntitySetExistsWithDifferentCase(entitySet.Identity)),
272 set.Add(entitySet.Identity);
278 internal IEnumerable<EdmType> GetDirectSubTypes(EdmType edmType)
280 return _edmItemCollection.GetItems<EdmType>().Where(b => b.BaseType == edmType);
283 private static System.Data.EntityModel.SchemaObjectModel.SchemaElement GetSchemaElement(
284 System.Data.EntityModel.SchemaObjectModel.Schema schema, string itemIdentity)
286 List<System.Data.EntityModel.SchemaObjectModel.SchemaType> schemaTypes =
287 schema.SchemaTypes.Where(p => p.Identity == itemIdentity).ToList();
288 if (null != schemaTypes && schemaTypes.Count > 0)
290 return (System.Data.EntityModel.SchemaObjectModel.SchemaElement)schemaTypes.First();
299 internal static void GetElementLocationInfo(System.Data.EntityModel.SchemaObjectModel.Schema schema, string itemIdentity, out int lineNumber, out int linePosition)
301 System.Data.EntityModel.SchemaObjectModel.SchemaElement element = GetSchemaElement(schema, itemIdentity);
305 lineNumber = element.LineNumber;
306 linePosition = element.LinePosition;
310 lineNumber = linePosition = -1;
314 internal static void GetElementLocationInfo(System.Data.EntityModel.SchemaObjectModel.Schema schema, string parentIdentity, string itemIdentity, out int lineNumber, out int linePosition)
316 lineNumber = linePosition = -1;
318 System.Data.EntityModel.SchemaObjectModel.SchemaElement element = GetSchemaElement(schema, parentIdentity);
319 System.Data.EntityModel.SchemaObjectModel.StructuredType elementWithProperty =
320 element as System.Data.EntityModel.SchemaObjectModel.StructuredType;
322 if (null != elementWithProperty && elementWithProperty.Properties.ContainsKey(itemIdentity))
324 lineNumber = elementWithProperty.Properties[itemIdentity].LineNumber;
325 linePosition = elementWithProperty.Properties[itemIdentity].LinePosition;
327 else if( null != element)
329 lineNumber = element.LineNumber;
330 linePosition = element.LinePosition;
336 #region Internal Properties
338 internal LanguageOption Language
342 return _generator.LanguageOption;
346 internal TypeReference TypeReference
348 get { return _typeReference; }
351 internal CodeCompileUnit CompileUnit
355 if (_compileUnit == null)
356 _compileUnit = new CodeCompileUnit();
362 public void AddError(string message, ModelBuilderErrorCode errorCode, EdmSchemaErrorSeverity severity)
364 _errors.Add(new EdmSchemaError(message, (int)errorCode, severity));
367 public void AddError(ModelBuilderErrorCode errorCode, EdmSchemaErrorSeverity severity, Exception ex)
369 _errors.Add(new EdmSchemaError(ex.Message, (int)errorCode, severity, ex));
372 internal void AddError(string message, ModelBuilderErrorCode errorCode, EdmSchemaErrorSeverity severity, Exception ex)
374 _errors.Add(new EdmSchemaError(message, (int)errorCode, severity, ex));
377 internal void AddError(ModelBuilderErrorCode errorCode, EdmSchemaErrorSeverity severity, Exception ex, string itemIdentity)
379 int lineNumber, linePosition;
380 ClientApiGenerator.GetElementLocationInfo(this._sourceSchema, itemIdentity, out lineNumber, out linePosition);
382 _errors.Add(new EdmSchemaError(ex.Message, (int)errorCode, severity, this._sourceSchema.Location, lineNumber, linePosition, ex));
385 internal void AddError(string message, ModelBuilderErrorCode errorCode, EdmSchemaErrorSeverity severity, string itemIdentity)
387 int lineNumber, linePosition;
388 ClientApiGenerator.GetElementLocationInfo(this._sourceSchema, itemIdentity, out lineNumber, out linePosition);
390 _errors.Add(new EdmSchemaError(message, (int)errorCode, severity, this._sourceSchema.Location, lineNumber, linePosition));
393 internal void AddError(string message, ModelBuilderErrorCode errorCode, EdmSchemaErrorSeverity severity, string parentIdentity, string itemIdentity)
395 int lineNumber, linePosition;
396 ClientApiGenerator.GetElementLocationInfo(this._sourceSchema, parentIdentity, itemIdentity, out lineNumber, out linePosition);
398 _errors.Add(new EdmSchemaError(message, (int)errorCode, severity, this._sourceSchema.Location, lineNumber, linePosition));
402 /// Check collection for any real errors (Severity != Warning)
404 public bool RealErrorsExist
408 foreach (EdmSchemaError error in _errors)
410 if (error.Severity != EdmSchemaErrorSeverity.Warning)
417 public IEnumerable<GlobalItem> GetSourceTypes()
419 foreach (SOM.SchemaType type in _sourceSchema.SchemaTypes)
421 if (type is SOM.ModelFunction)
426 yield return _edmItemCollection.GetItem<GlobalItem>(type.Identity);
430 public CodeTypeReference GetFullyQualifiedTypeReference(EdmType type)
432 string fullObjectName = CreateFullName(GetObjectNamespace(type.NamespaceName), type.Name);
433 return TypeReference.FromString(fullObjectName);
436 public CodeTypeReference GetFullyQualifiedTypeReference(EdmType type, bool addGlobalQualifier)
438 string fullObjectName = CreateFullName(GetObjectNamespace(type.NamespaceName), type.Name);
439 return TypeReference.FromString(fullObjectName, addGlobalQualifier);
442 private string CreateFullName(string namespaceName, string name)
444 if (string.IsNullOrEmpty(namespaceName))
449 return namespaceName + "." + name;
452 public CodeTypeReference GetLeastPossibleQualifiedTypeReference(EdmType type)
456 if (type.BuiltInTypeKind == BuiltInTypeKind.PrimitiveType)
458 return type.ClrType.IsValueType ? TypeReference.NullableForType(type.ClrType) : TypeReference.ForType(type.ClrType);
462 if (type.NamespaceName == SourceEdmNamespaceName)
464 // we are already generating in this namespace, no need to qualify it
469 typeRef = CreateFullName(GetObjectNamespace(type.NamespaceName), type.Name);
473 return TypeReference.FromString(typeRef);
476 public string SourceEdmNamespaceName
480 return _sourceSchema.Namespace;
484 public string SourceObjectNamespaceName
488 return GetObjectNamespace(SourceEdmNamespaceName);
492 private string GetObjectNamespace(string csdlNamespaceName)
494 Debug.Assert(csdlNamespaceName != null, "csdlNamespaceName is null");
496 string objectNamespace;
497 if (_generator.EdmToObjectNamespaceMap.TryGetObjectNamespace(csdlNamespaceName, out objectNamespace))
499 return objectNamespace;
502 return csdlNamespaceName;
510 internal FixUpCollection FixUps
515 _fixUps = new FixUpCollection();
521 internal AttributeEmitter AttributeEmitter
523 get { return _attributeEmitter; }
526 internal bool IsLanguageCaseSensitive
528 get { return _isLanguageCaseSensitive; }
531 internal StringComparison LanguageAppropriateStringComparer
535 if (IsLanguageCaseSensitive)
537 return StringComparison.Ordinal;
541 return StringComparison.OrdinalIgnoreCase;
547 /// Helper method that raises the TypeGenerated event
549 /// <param name="eventArgs">The event arguments passed to the subscriber</param>
550 internal void RaiseTypeGeneratedEvent(TypeGeneratedEventArgs eventArgs)
552 _generator.RaiseTypeGeneratedEvent(eventArgs);
556 /// Helper method that raises the PropertyGenerated event
558 /// <param name="eventArgs">The event arguments passed to the subscriber</param>
559 internal void RaisePropertyGeneratedEvent(PropertyGeneratedEventArgs eventArgs)
561 _generator.RaisePropertyGeneratedEvent(eventArgs);