static CodeBlock WriteNode( Variable <JsonWriter> jsonWriter, TypedExpression <string> baseUri, ResourceModel model, Expression resource, MemberAccess <Func <object, string> > uriGenerator, MemberAccess <Func <object, string> > typeGenerator, IMetaModelRepository models, Variable <HydraJsonFormatterResolver> jsonFormatterResolver, NodeProperty[] existingNodeProperties = null, Stack <ResourceModel> recursionDefender = null) { recursionDefender = recursionDefender ?? new Stack <ResourceModel>(); var resourceType = model.ResourceType; if (recursionDefender.Contains(model)) { throw new InvalidOperationException( $"Detected recursion, already processing {resourceType?.Name}: {string.Join("->", recursionDefender.Select(m => m.ResourceType?.Name).Where(n => n != null))}"); } recursionDefender.Push(model); var resourceRegistrationHydraType = HydraTextExtensions.GetHydraTypeName(model); var resourceUri = uriGenerator.Invoke(resource); List <NodeProperty> nodeProperties = new List <NodeProperty>(existingNodeProperties ?? Enumerable.Empty <NodeProperty>()); nodeProperties.AddRange(GetNodeProperties( jsonWriter, baseUri, model, resource, uriGenerator, typeGenerator, models, recursionDefender, jsonFormatterResolver, resourceType, resourceUri, resourceRegistrationHydraType)); var collectionItemTypes = HydraTextExtensions.CollectionItemTypes(resourceType).ToList(); Type collectionItemType = null; var isHydraCollection = collectionItemTypes.Count == 1 && models.TryGetResourceModel(collectionItemType = collectionItemTypes.First(), out _); IEnumerable <AnyExpression> render() { if (isHydraCollection) { var collectionType = HydraTypes.Collection.MakeGenericType(collectionItemType); var collectionCtor = collectionType.GetConstructor(new[] { typeof(IEnumerable <>).MakeGenericType(collectionItemType) }); var collection = Expression.Variable(collectionType); yield return(collection); var instantiateCollection = Expression.Assign(collection, Expression.New(collectionCtor, resource)); yield return(instantiateCollection); resource = collection; // if we have a generic list of sort, we hydra:Collection instead if (resourceType.IsGenericType) // IEnum<T>, List<T> etc { resourceRegistrationHydraType = "hydra:Collection"; } resourceType = collectionType; // Remove existing id and type if already defined nodeProperties.RemoveAll(p => p.Name == "@id" || p.Name == "@type"); nodeProperties.AddRange(GetNodeProperties(jsonWriter, baseUri, model, resource, uriGenerator, typeGenerator, models, recursionDefender, jsonFormatterResolver, resourceType, resourceUri, resourceRegistrationHydraType)); } if (nodeProperties.Any()) { yield return(WriteNodeProperties(jsonWriter, nodeProperties)); } } recursionDefender.Pop(); return(new CodeBlock(render())); }
static NodeProperty WriteNodeProperty( Variable <JsonWriter> jsonWriter, TypedExpression <string> baseUri, Expression resource, MemberAccess <Func <object, string> > uriGenerator, MemberAccess <Func <object, string> > typeGenerator, IMetaModelRepository models, Stack <ResourceModel> recursionDefender, PropertyInfo pi, Variable <HydraJsonFormatterResolver> jsonFormatterResolver) { // var propertyValue; var propertyValue = Expression.Variable(pi.PropertyType, $"val{pi.DeclaringType.Name}{pi.Name}"); // propertyValue = resource.Property; var propertyValueAssignment = Expression.Assign(propertyValue, Expression.MakeMemberAccess(resource, pi)); var preamble = new InlineCode(new Expression[] { propertyValue, propertyValueAssignment }); if (models.TryGetResourceModel(pi.PropertyType, out var propertyResourceModel)) { var jsonPropertyName = HydraTextExtensions.GetJsonPropertyName(pi); return(new NodeProperty(jsonPropertyName) { Preamble = preamble, Code = new InlineCode(new[] { jsonWriter.WritePropertyName(jsonPropertyName), jsonWriter.WriteBeginObject(), WriteNode(jsonWriter, baseUri, propertyResourceModel, propertyValue, uriGenerator, typeGenerator, models, jsonFormatterResolver, recursionDefender: recursionDefender), jsonWriter.WriteEndObject() }), Conditional = Expression.NotEqual(propertyValue, Expression.Default(pi.PropertyType)) }); } var itemTypes = (from i in pi.PropertyType.GetInterfaces() .Concat(pi.PropertyType.IsInterface ? new[] { pi.PropertyType } : Array.Empty <Type>()) where i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable <>) let itemType = i.GetGenericArguments()[0] where itemType != typeof(object) select itemType).ToList(); // not an iri node itself, but is it a list of nodes? var itemResourceRegistrations = ( from itemType in itemTypes let resourceModels = models.ResourceRegistrations.Where(r => r.ResourceType != null && itemType.IsAssignableFrom(r.ResourceType)).ToList() where resourceModels.Any() orderby resourceModels.Count() descending select ( itemType, (from possible in resourceModels orderby possible.ResourceType.GetInheritanceDistance(itemType) select possible).ToList() )).ToList <(Type itemType, List <ResourceModel> models)>(); if (itemResourceRegistrations.Any() == false) { // not a list of iri or blank nodes var propValue = WriteNodePropertyValue(jsonWriter, pi, jsonFormatterResolver, resource); propValue.Preamble = preamble; propValue.Conditional = Expression.NotEqual(propertyValue, Expression.Default(pi.PropertyType)); return(propValue); } // it's a list of nodes return(WriteNodeList(jsonWriter, baseUri, uriGenerator, typeGenerator, models, recursionDefender, pi, jsonFormatterResolver, itemResourceRegistrations, propertyValue, preamble)); }