private static ApiPropertyConfiguration GetOrAddApiPropertyConfiguration(ApiMutableObjectType apiMutableObjectType,
                                                                                 Type clrDeclaringType,
                                                                                 string clrPropertyName,
                                                                                 Type clrPropertyType,
                                                                                 ApiPrecedenceLevel apiPrecedenceLevel,
                                                                                 ApiPrecedenceStack apiPrecedenceStack)
        {
            Contract.Requires(apiMutableObjectType != null);
            Contract.Requires(clrDeclaringType != null);
            Contract.Requires(clrPropertyName.SafeHasContent());
            Contract.Requires(clrPropertyType != null);
            Contract.Requires(apiPrecedenceStack != null);

            if (apiMutableObjectType.ApiPropertyConfigurationsIndex.TryGetValue(clrPropertyName, out var apiPropertyConfigurationIndex))
            {
                var apiPropertyConfigurationExisting = apiMutableObjectType.ApiPropertyConfigurations[apiPropertyConfigurationIndex];
                return(apiPropertyConfigurationExisting);
            }

            var apiPropertyConfigurationNew      = new ApiPropertyConfiguration(clrDeclaringType, clrPropertyName, clrPropertyType, apiPrecedenceStack);
            var apiPropertyConfigurationIndexNew = apiMutableObjectType.ApiPropertyConfigurationsIndex.Count;

            apiMutableObjectType.ApiPropertyConfigurations.Add(apiPropertyConfigurationNew);
            apiMutableObjectType.ApiPropertyConfigurationsIndex.Add(clrPropertyName, apiPropertyConfigurationIndexNew);

            ApiFrameworkLog.Trace($"Added {nameof(ApiProperty)} [{nameof(ApiProperty.ClrName)}={clrPropertyName}] at {apiPrecedenceLevel} Level".Indent(IndentConstants.ApiMutableObjectTypeModifier));

            return(apiPropertyConfigurationNew);
        }
        private static IApiIdentity CreateApiIdentity(ApiMutableObjectType apiMutableObjectType, IEnumerable <IApiProperty> apiProperties)
        {
            Contract.Requires(apiMutableObjectType != null);
            Contract.Requires(apiProperties != null);

            var clrIdentityProperty = apiMutableObjectType.ClrIdentityProperty;

            if (clrIdentityProperty == null)
            {
                return(null);
            }

            var clrIdentityPropertyName = clrIdentityProperty.ClrPropertyName;

            if (String.IsNullOrWhiteSpace(clrIdentityPropertyName))
            {
                return(null);
            }

            var apiIdentityProperty = (ApiProperty)apiProperties.SingleOrDefault(x => x.ClrName == clrIdentityPropertyName);

            if (apiIdentityProperty == null)
            {
                var clrTypeName = apiMutableObjectType.ClrObjectType.Name;
                var message     = $"Unable to create API identity for an API object type [clrType={clrTypeName}] because the configured API identity property [clrName={clrIdentityPropertyName}] does not exist.";
                throw new ApiSchemaException(message);
            }

            var apiIdentity = ApiTypeFactory.CreateApiIdentity(apiIdentityProperty);

            ApiFrameworkLog.Debug($"Created {apiIdentity}".Indent(IndentConstants.ApiMutableObjectTypeIdentity));

            return(apiIdentity);
        }
        private Func <ApiMutableSchema, ApiMutableObjectType> CreateApiMutableObjectTypeFactory(Type clrObjectType)
        {
            Contract.Requires(clrObjectType != null);

            ApiMutableObjectType ApiMutableObjectTypeFactory(ApiMutableSchema apiMutableSchema)
            {
                Contract.Requires(apiMutableSchema != null);

                // Apply conventions
                this.ApiPrecedenceStack.Push(ApiPrecedenceLevel.Convention);

                var apiConventionSet      = apiMutableSchema?.ApiConventionSet;
                var apiConventionSettings = apiMutableSchema?.ApiConventionSettings;

                this.ApplyApiObjectTypeNameConventions(clrObjectType, apiConventionSet, apiConventionSettings);
                this.ApplyApiObjectTypeConventions(apiConventionSet, apiConventionSettings);

                this.ApiPrecedenceStack.Pop();

                // Create API mutable object type
                var apiDefaultName        = clrObjectType.Name;
                var apiDefaultDescription = clrObjectType.ToString();
                var apiMutableObjectType  = new ApiMutableObjectType
                {
                    ApiMutableSchema = apiMutableSchema,
                    ApiName          = apiDefaultName,
                    ApiDescription   = apiDefaultDescription,
                    ClrObjectType    = clrObjectType
                };

                return(apiMutableObjectType);
            }

            return(ApiMutableObjectTypeFactory);
        }
        private static IReadOnlyCollection <IApiProperty> CreateApiProperties(ApiMutableObjectType apiMutableObjectType, ApiSchemaProxy apiSchemaProxy)
        {
            Contract.Requires(apiMutableObjectType != null);
            Contract.Requires(apiSchemaProxy != null);

            // Create an ApiPropertyConfiguration query (LINQ to Objects).
            var apiMutableSchema               = apiMutableObjectType.ApiMutableSchema;
            var apiPropertyConfigurations      = apiMutableObjectType.ApiPropertyConfigurations;
            var apiPropertyConfigurationsQuery = apiPropertyConfigurations.AsEnumerable();

            // Enhance query to remove explicit (by name) excluded properties for this API object type.
            var clrExplicitExcludedPropertyNames = apiMutableObjectType.ClrExcludedPropertyNames;

            if (clrExplicitExcludedPropertyNames.Any())
            {
                var clrExplicitIncludedPropertyNames = apiPropertyConfigurations.Select(x => x.ClrName)
                                                       .ToList();
                var clrExplicitIncludedPropertyNameHashSet = new HashSet <string>(clrExplicitIncludedPropertyNames);
                clrExplicitIncludedPropertyNameHashSet.ExceptWith(clrExplicitExcludedPropertyNames);

                apiPropertyConfigurationsQuery = apiPropertyConfigurationsQuery.Where(x => clrExplicitIncludedPropertyNameHashSet.Contains(x.ClrName));
            }

            // Enhance query to remove implicit (by type) excluded properties for this API object type.
            var clrExcludedTypes = apiMutableSchema.ClrExcludedTypes;

            if (clrExcludedTypes.Any())
            {
                var clrImplicitExcludedPropertyNameHashSet =
                    apiPropertyConfigurations
                    .Where(x =>
                {
                    var clrLeafType = x.ClrType.GetClrLeafType();
                    return(clrExcludedTypes.Contains(clrLeafType));
                })
                    .Select(x => x.ClrName)
                    .ToList();

                var clrImplicitIncludedPropertyNames = apiPropertyConfigurations.Select(x => x.ClrName)
                                                       .ToList();
                var clrImplicitIncludedPropertyNameHashSet = new HashSet <string>(clrImplicitIncludedPropertyNames);
                clrImplicitIncludedPropertyNameHashSet.ExceptWith(clrImplicitExcludedPropertyNameHashSet);

                apiPropertyConfigurationsQuery = apiPropertyConfigurationsQuery.Where(x => clrImplicitIncludedPropertyNameHashSet.Contains(x.ClrName));
            }

            // Execute query to create API property objects for this API object type.
            var apiProperties = apiPropertyConfigurationsQuery.Select(x => x.CreateApiProperty(apiMutableSchema,
                                                                                               apiMutableObjectType,
                                                                                               apiSchemaProxy))
                                .ToList();

            return(apiProperties);
        }
        private static IApiObjectType CreateApiObjectType(ApiMutableObjectType apiMutableObjectType, ApiSchemaProxy apiSchemaProxy)
        {
            Contract.Requires(apiMutableObjectType != null);
            Contract.Requires(apiSchemaProxy != null);

            var apiName          = apiMutableObjectType.ApiName;
            var apiDescription   = apiMutableObjectType.ApiDescription;
            var apiProperties    = CreateApiProperties(apiMutableObjectType, apiSchemaProxy);
            var apiIdentity      = CreateApiIdentity(apiMutableObjectType, apiProperties);
            var apiRelationships = CreateApiRelationships(apiMutableObjectType, apiSchemaProxy, apiProperties, apiIdentity);
            var clrObjectType    = apiMutableObjectType.ClrObjectType;
            var apiObjectType    = ApiTypeFactory.CreateApiObjectType(apiName, apiDescription, apiProperties, apiIdentity, apiRelationships, clrObjectType);

            return(apiObjectType);
        }
        // ReSharper disable once ReturnTypeCanBeEnumerable.Local
        private static IReadOnlyCollection <IApiRelationship> CreateApiRelationships(ApiMutableObjectType apiMutableObjectType,
                                                                                     ApiSchemaProxy apiSchemaProxy,
                                                                                     IEnumerable <IApiProperty> apiProperties,
                                                                                     IApiIdentity apiIdentity)
        {
            Contract.Requires(apiMutableObjectType != null);
            Contract.Requires(apiSchemaProxy != null);
            Contract.Requires(apiProperties != null);

            // If this API object type has no identity, it may not contain any relationships.
            if (apiIdentity == null)
            {
                return(null);
            }

            var apiMutableSchema = apiMutableObjectType.ApiMutableSchema;
            var clrRelationshipPropertyCollection = apiMutableObjectType.ClrRelationshipPropertyCollection;
            var apiRelationships = clrRelationshipPropertyCollection
                                   .Where(x =>
            {
                // Can not create an API relationship if the related API object type does not have identity (must be an API resource type).
                var clrPropertyType     = x.ClrPropertyType;
                var apiPropertyTypeKind = clrPropertyType.GetApiTypeKind(out var clrPropertyItemType);
                switch (apiPropertyTypeKind)
                {
                case ApiTypeKind.Object:
                    {
                        var isApiResourceType = apiMutableSchema.ClrResourceTypes.Contains(clrPropertyType);
                        if (!isApiResourceType)
                        {
                            return(false);
                        }

                        break;
                    }

                case ApiTypeKind.Collection:
                    {
                        var apiItemTypeKind = clrPropertyItemType.GetApiTypeKind();
                        switch (apiItemTypeKind)
                        {
                        case ApiTypeKind.Collection:
                            {
                                // Unable to handle collections within collections.
                                var message = $"Unable to create API relationship for an API property [{nameof(x.ClrPropertyName)}={x.ClrPropertyName}] that contains collections within collections";
                                throw new ApiSchemaException(message);
                            }
                        }

                        var isApiResourceType = apiMutableSchema.ClrResourceTypes.Contains(clrPropertyItemType);
                        if (!isApiResourceType)
                        {
                            return(false);
                        }

                        break;
                    }

                default:
                    {
                        return(false);
                    }
                }

                return(true);
            })
                                   .Select(x =>
            {
                var clrPropertyName     = x.ClrPropertyName;
                var clrPropertyType     = x.ClrPropertyType;
                var apiProperty         = apiProperties.Single(y => y.ClrName == clrPropertyName);
                var apiPropertyTypeKind = clrPropertyType.GetApiTypeKind(out var clrPropertyItemType);

                ApiRelationshipCardinality apiCardinality;
                Type clrType;
                switch (apiPropertyTypeKind)
                {
                case ApiTypeKind.Object:
                    {
                        apiCardinality = ApiRelationshipCardinality.ToOne;
                        clrType        = clrPropertyType;
                        break;
                    }

                case ApiTypeKind.Collection:
                    {
                        apiCardinality = ApiRelationshipCardinality.ToMany;
                        clrType        = clrPropertyItemType;
                        break;
                    }

                default:
                    {
                        throw new ArgumentOutOfRangeException();
                    }
                }

                var apiRelatedTypeResolver = new ApiSchemaProxyTypeResolver(apiSchemaProxy, ApiTypeKind.Object, clrType);
                var apiRelationship        = ApiTypeFactory.CreateApiRelationship(apiProperty, apiCardinality, apiRelatedTypeResolver);

                return(apiRelationship);
            })
                                   .ToList();

            foreach (var apiRelationship in apiRelationships)
            {
                ApiFrameworkLog.Debug($"Created {apiRelationship}".Indent(IndentConstants.ApiMutableObjectTypeRelationship));
            }

            return(apiRelationships);
        }