/// <summary> /// Prepares generation by processing the result of code parsing. /// </summary> private void Prepare() { TypeModel.MapModelTypes(TypeModels, ModelsNamespace); var isInMemoryMode = Config.ModelsMode == ModelsMode.InMemoryAuto; // for the first two of these two tests, // always throw, even in InMemory mode: cannot happen unless ppl start fidling with attributes to rename // things, and then they should pay attention to the generation error log - there's no magic here // for the last one, don't throw in InMemory mode, see comment // ensure we have no duplicates type names foreach (var xx in TypeModels.GroupBy(x => x.ClrName).Where(x => x.Count() > 1)) { throw new InvalidOperationException($"Type name \"{xx.Key}\" is used" + $" for types with alias {string.Join(", ", xx.Select(x => x.ItemType + ":\"" + x.Alias + "\""))}. Names have to be unique." + " Consider using an attribute to assign different names to conflicting types."); } // ensure we have no duplicates property names foreach (var typeModel in TypeModels) { foreach (var xx in typeModel.Properties.GroupBy(x => x.ClrName).Where(x => x.Count() > 1)) { throw new InvalidOperationException($"Property name \"{xx.Key}\" in type {typeModel.ItemType}:\"{typeModel.Alias}\"" + $" is used for properties with alias {string.Join(", ", xx.Select(x => "\"" + x.Alias + "\""))}. Names have to be unique." + " Consider using an attribute to assign different names to conflicting properties."); } } // ensure content & property type don't have identical name (csharp hates it) foreach (var typeModel in TypeModels) { foreach (var xx in typeModel.Properties.Where(x => x.ClrName == typeModel.ClrName)) { if (!isInMemoryMode) { throw new InvalidOperationException($"The model class for content type with alias \"{typeModel.Alias}\" is named \"{xx.ClrName}\"." + $" CSharp does not support using the same name for the property with alias \"{xx.Alias}\"." + " Consider using an attribute to assign a different name to the property."); } // in InMemory mode we generate commented out properties with an error message, // instead of throwing, because then it kills the sites and ppl don't understand why xx.AddError($"The class {typeModel.ClrName} cannot implement this property, because" + $" CSharp does not support naming the property with alias \"{xx.Alias}\" with the same name as content type with alias \"{typeModel.Alias}\"." + " Consider using an attribute to assign a different name to the property."); // will not be implemented on interface nor class // note: we will still create the static getter, and implement the property on other classes... } } // ensure we have no collision between base types // NO: we may want to define a base class in a partial, on a model that has a parent // we are NOT checking that the defined base type does maintain the inheritance chain //foreach (var xx in _typeModels.Where(x => !x.IsContentIgnored).Where(x => x.BaseType != null && x.HasBase)) // throw new InvalidOperationException(string.Format("Type alias \"{0}\" has more than one parent class.", // xx.Alias)); // discover interfaces that need to be declared / implemented foreach (var typeModel in TypeModels) { // collect all the (non-removed) types implemented at parent level // ie the parent content types and the mixins content types, recursively var parentImplems = new List <TypeModel>(); if (typeModel.BaseType != null) { TypeModel.CollectImplems(parentImplems, typeModel.BaseType); } // interfaces we must declare we implement (initially empty) // ie this type's mixins, except those that have been removed, // and except those that are already declared at the parent level // in other words, DeclaringInterfaces is "local mixins" var declaring = typeModel.MixinTypes .Except(parentImplems); typeModel.DeclaringInterfaces.AddRange(declaring); // interfaces we must actually implement (initially empty) // if we declare we implement a mixin interface, we must actually implement // its properties, all recursively (ie if the mixin interface implements...) // so, starting with local mixins, we collect all the (non-removed) types above them var mixinImplems = new List <TypeModel>(); foreach (var i in typeModel.DeclaringInterfaces) { TypeModel.CollectImplems(mixinImplems, i); } // and then we remove from that list anything that is already declared at the parent level typeModel.ImplementingInterfaces.AddRange(mixinImplems.Except(parentImplems)); } // ensure elements don't inherit from non-elements foreach (var typeModel in TypeModels.Where(x => x.IsElement)) { if (typeModel.BaseType != null && !typeModel.BaseType.IsElement) { throw new InvalidOperationException($"Cannot generate model for type '{typeModel.Alias}' because it is an element type, but its parent type '{typeModel.BaseType.Alias}' is not."); } var errs = typeModel.MixinTypes.Where(x => !x.IsElement).ToList(); if (errs.Count > 0) { throw new InvalidOperationException($"Cannot generate model for type '{typeModel.Alias}' because it is an element type, but it is composed of {string.Join(", ", errs.Select(x => "'" + x.Alias + "'"))} which {(errs.Count == 1 ? "is" : "are")} not."); } } }
private void WriteProperty(StringBuilder sb, TypeModel type, PropertyModel property, string?mixinClrName = null) { var mixinStatic = mixinClrName != null; sb.Append("\n"); if (property.Errors != null) { sb.Append("\t\t/*\n"); sb.Append("\t\t * THIS PROPERTY CANNOT BE IMPLEMENTED, BECAUSE:\n"); sb.Append("\t\t *\n"); var first = true; foreach (var error in property.Errors) { if (first) { first = false; } else { sb.Append("\t\t *\n"); } foreach (var s in SplitError(error)) { sb.Append("\t\t * "); sb.Append(s); sb.Append("\n"); } } sb.Append("\t\t *\n"); sb.Append("\n"); } // Adds xml summary to each property containing // property name and property description if (!string.IsNullOrWhiteSpace(property.Name) || !string.IsNullOrWhiteSpace(property.Description)) { sb.Append("\t\t///<summary>\n"); if (!string.IsNullOrWhiteSpace(property.Description)) { sb.AppendFormat("\t\t/// {0}: {1}\n", XmlCommentString(property.Name), XmlCommentString(property.Description)); } else { sb.AppendFormat("\t\t/// {0}\n", XmlCommentString(property.Name)); } sb.Append("\t\t///</summary>\n"); } WriteGeneratedCodeAttribute(sb, "\t\t"); if (!property.ModelClrType.IsValueType) { WriteMaybeNullAttribute(sb, "\t\t"); } sb.AppendFormat("\t\t[ImplementPropertyType(\"{0}\")]\n", property.Alias); if (mixinStatic) { sb.Append("\t\tpublic virtual "); WriteClrType(sb, property.ClrTypeName); sb.AppendFormat(" {0} => {1}(this, _publishedValueFallback);\n", property.ClrName, MixinStaticGetterName(property.ClrName)); } else { sb.Append("\t\tpublic virtual "); WriteClrType(sb, property.ClrTypeName); sb.AppendFormat(" {0} => this.Value", property.ClrName); if (property.ModelClrType != typeof(object)) { sb.Append("<"); WriteClrType(sb, property.ClrTypeName); sb.Append(">"); } sb.AppendFormat("(_publishedValueFallback, \"{0}\");\n", property.Alias); } if (property.Errors != null) { sb.Append("\n"); sb.Append("\t\t *\n"); sb.Append("\t\t */\n"); } if (!mixinStatic) { return; } var mixinStaticGetterName = MixinStaticGetterName(property.ClrName); //if (type.StaticMixinMethods.Contains(mixinStaticGetterName)) return; sb.Append("\n"); if (!string.IsNullOrWhiteSpace(property.Name)) { sb.AppendFormat("\t\t/// <summary>Static getter for {0}</summary>\n", XmlCommentString(property.Name)); } WriteGeneratedCodeAttribute(sb, "\t\t"); if (!property.ModelClrType.IsValueType) { WriteMaybeNullAttribute(sb, "\t\t", true); } sb.Append("\t\tpublic static "); WriteClrType(sb, property.ClrTypeName); sb.AppendFormat(" {0}(I{1} that, IPublishedValueFallback publishedValueFallback) => that.Value", mixinStaticGetterName, mixinClrName); if (property.ModelClrType != typeof(object)) { sb.Append("<"); WriteClrType(sb, property.ClrTypeName); sb.Append(">"); } sb.AppendFormat("(publishedValueFallback, \"{0}\");\n", property.Alias); }
protected string GetModelsBaseClassName(TypeModel type) { // default return(type.IsElement ? "PublishedElementModel" : "PublishedContentModel"); }
private void WriteContentType(StringBuilder sb, TypeModel type) { string sep; if (type.IsMixin) { // write the interface declaration sb.AppendFormat("\t// Mixin Content Type with alias \"{0}\"\n", type.Alias); if (!string.IsNullOrWhiteSpace(type.Name)) { sb.AppendFormat("\t/// <summary>{0}</summary>\n", XmlCommentString(type.Name)); } sb.AppendFormat("\tpublic partial interface I{0}", type.ClrName); var implements = type.BaseType == null ? (type.HasBase ? null : (type.IsElement ? "PublishedElement" : "PublishedContent")) : type.BaseType.ClrName; if (implements != null) { sb.AppendFormat(" : I{0}", implements); } // write the mixins sep = implements == null ? ":" : ","; foreach (var mixinType in type.DeclaringInterfaces.OrderBy(x => x.ClrName)) { sb.AppendFormat("{0} I{1}", sep, mixinType.ClrName); sep = ","; } sb.Append("\n\t{\n"); // write the properties - only the local (non-ignored) ones, we're an interface var more = false; foreach (var prop in type.Properties.OrderBy(x => x.ClrName)) { if (more) { sb.Append("\n"); } more = true; WriteInterfaceProperty(sb, prop); } sb.Append("\t}\n\n"); } // write the class declaration if (!string.IsNullOrWhiteSpace(type.Name)) { sb.AppendFormat("\t/// <summary>{0}</summary>\n", XmlCommentString(type.Name)); } // cannot do it now. see note in ImplementContentTypeAttribute //if (!type.HasImplement) // sb.AppendFormat("\t[ImplementContentType(\"{0}\")]\n", type.Alias); sb.AppendFormat("\t[PublishedModel(\"{0}\")]\n", type.Alias); sb.AppendFormat("\tpublic partial class {0}", type.ClrName); var inherits = type.HasBase ? null // has its own base already : (type.BaseType == null ? GetModelsBaseClassName(type) : type.BaseType.ClrName); if (inherits != null) { sb.AppendFormat(" : {0}", inherits); } sep = inherits == null ? ":" : ","; if (type.IsMixin) { // if it's a mixin it implements its own interface sb.AppendFormat("{0} I{1}", sep, type.ClrName); } else { // write the mixins, if any, as interfaces // only if not a mixin because otherwise the interface already has them already foreach (var mixinType in type.DeclaringInterfaces.OrderBy(x => x.ClrName)) { sb.AppendFormat("{0} I{1}", sep, mixinType.ClrName); sep = ","; } } // begin class body sb.Append("\n\t{\n"); // write the constants & static methods // as 'new' since parent has its own - or maybe not - disable warning sb.Append("\t\t// helpers\n"); sb.Append("#pragma warning disable 0109 // new is redundant\n"); WriteGeneratedCodeAttribute(sb, "\t\t"); sb.AppendFormat("\t\tpublic new const string ModelTypeAlias = \"{0}\";\n", type.Alias); var itemType = type.IsElement ? TypeModel.ItemTypes.Content : type.ItemType; // fixme WriteGeneratedCodeAttribute(sb, "\t\t"); sb.AppendFormat("\t\tpublic new const PublishedItemType ModelItemType = PublishedItemType.{0};\n", itemType); WriteGeneratedCodeAttribute(sb, "\t\t"); WriteMaybeNullAttribute(sb, "\t\t", true); sb.Append("\t\tpublic new static IPublishedContentType GetModelContentType(IPublishedSnapshotAccessor publishedSnapshotAccessor)\n"); sb.Append("\t\t\t=> PublishedModelUtility.GetModelContentType(publishedSnapshotAccessor, ModelItemType, ModelTypeAlias);\n"); WriteGeneratedCodeAttribute(sb, "\t\t"); WriteMaybeNullAttribute(sb, "\t\t", true); sb.AppendFormat("\t\tpublic static IPublishedPropertyType GetModelPropertyType<TValue>(IPublishedSnapshotAccessor publishedSnapshotAccessor, Expression<Func<{0}, TValue>> selector)\n", type.ClrName); sb.Append("\t\t\t=> PublishedModelUtility.GetModelPropertyType(GetModelContentType(publishedSnapshotAccessor), selector);\n"); sb.Append("#pragma warning restore 0109\n\n"); sb.Append("\t\tprivate IPublishedValueFallback _publishedValueFallback;"); // write the ctor sb.AppendFormat("\n\n\t\t// ctor\n\t\tpublic {0}(IPublished{1} content, IPublishedValueFallback publishedValueFallback)\n\t\t\t: base(content, publishedValueFallback)\n\t\t{{\n\t\t\t_publishedValueFallback = publishedValueFallback;\n\t\t}}\n\n", type.ClrName, type.IsElement ? "Element" : "Content"); // write the properties sb.Append("\t\t// properties\n"); WriteContentTypeProperties(sb, type); // close the class declaration sb.Append("\t}\n"); }