public string[] GetIgnorePaths(Type t, BuildMode mode)
        {
            var valueBuildResults = new List <ValueBuildResult>();

            var customObjectBuildContext = new CustomObjectBuildContext();

            //this.customObjectCollectionSequence = new Dictionary<Type, int>();

            var buildContext = new BuildContext(
                string.Empty,
                t,
                new Dictionary <string, object>(StringComparer.InvariantCultureIgnoreCase),
                null,
                null,
                mode);

            BuildValue(valueBuildResults, buildContext, customObjectBuildContext);

            var typeNamePrefix = string.Format("{0}.", t.Name);

            return(valueBuildResults
                   .Where(x => x.ShouldSkip)
                   .Select(x => x.LogicalPath)
                   .Select(
                       x => x.StartsWith(typeNamePrefix)
                           ? x.Substring(typeNamePrefix.Length)
                           : x)
                   .ToArray());
        }
        public object Create(
            string logicalPropertyPath,
            Type targetType,
            IDictionary <string, object> propertyValueConstraints,
            LinkedList <object> objectGraphLineage)
        {
            // TODO: Needs unit test to ensure proper cancellation
            if (CancellationToken.IsCancellationRequested)
            {
                _logger.WarnFormat("Cancellation requested prior to creating value for '{0}' (of type '{1}').", logicalPropertyPath, targetType.Name);

                throw new OperationCanceledException(
                          string.Format("Cancellation requested prior to creating value for '{0}' (of type '{1}').", logicalPropertyPath, targetType.Name));
            }

            // Make sure type should be created
            if (!CanCreate(targetType))
            {
                _logger.DebugFormat("CanCreate delegate on TestObjectFactory indicated that '{0}' should not be created.", targetType.Name);
                return(null);
            }

            if (objectGraphLineage == null)
            {
                objectGraphLineage = new LinkedList <object>();
            }

            var customObjectBuildContext = new CustomObjectBuildContext();

            var buildContext = new BuildContext(
                logicalPropertyPath,
                targetType,
                propertyValueConstraints,
                null,
                objectGraphLineage,
                BuildMode.Create);

            return(BuildValue(new List <ValueBuildResult>(), buildContext, customObjectBuildContext)
                   .Value);
        }
        // TODO: GKM - Should this method be marked Obsolete, and should its behavior be refactored to use value builders?
        public ValueBuildResult[] RandomizeValue(object value, Type explicitValueType, string[] notSoRandomUniqueIds = null)
        {
            var buildResults = new List <ValueBuildResult>();

            NotSoRandomUniqueId = notSoRandomUniqueIds;
            var customObjectBuildContext = new CustomObjectBuildContext();

            BuildAndAssignPropertyValues(
                string.Empty,
                new LinkedList <object>(
                    new[]
            {
                value
            }),
                explicitValueType,
                new Dictionary <string, object>(StringComparer.InvariantCultureIgnoreCase),
                buildResults,
                BuildMode.Modify,
                customObjectBuildContext);

            return(buildResults.OrderBy(x => x.LogicalPath)
                   .ToArray());
        }
        private void BuildAndAssignPropertyValues(
            string logicalPropertyPath,
            LinkedList <object> objectGraphLineage,
            Type explicitType,
            IDictionary <string, object> propertyValueConstraints,
            List <ValueBuildResult> results,
            BuildMode mode,
            CustomObjectBuildContext customObjectBuildContext)
        {
            // Get its public properties
            int i = 0;

            var writableProperties = // TODO: Need unit test for this behavior
                                     (from p in explicitType.GetPublicProperties()
                                      where p.CanWrite && !p.GetCustomAttributes <IgnoreDataMemberAttribute>().Any()
                                      select p)
                                     .OrderBy(
                p =>
            {
                // Set scalar values first, then object references, then lists
                if (p.PropertyType.IsCustomClass())
                {
                    return(10000 + i++);
                }

                // Not a scalar, and not a custom object reference, then it's probably a list (sort last)
                if (!p.PropertyType.IsScalarProperty())
                {
                    return(20000 + i++);
                }

                return(i++);
            })
                                     .ToList();

            _logger.DebugFormat(
                "Processing custom object properties in the following order: {0}",
                string.Join(", ", writableProperties.Select(p => p.Name)));

            var containingInstance = objectGraphLineage.First.Value;

            while (writableProperties.Count > 0)
            {
                // Make note of the number of remaining writable properties we need to generate
                int lastWritableCount = writableProperties.Count;

                // Get a list of public, writable properties
                foreach (var property in writableProperties.ToArray())
                {
                    ValueBuildResult buildResult = null;

                    object constraintValue;

                    // Look for property name match using name and (if necessary) the "role name prefix" convention.
                    if (propertyValueConstraints.TryGetValue(property.Name, out constraintValue) ||
                        propertyValueConstraints.TryGetValue(explicitType.Name + property.Name, out constraintValue))
                    {
                        _logger.DebugFormat(
                            "Setting property '{0}' to '{1}' from context.",
                            logicalPropertyPath + "." + property.Name,
                            constraintValue);

                        buildResult = ValueBuildResult.WithValue(constraintValue, logicalPropertyPath + "." + property.Name);

                        try
                        {
                            property.SetValue(containingInstance, buildResult.Value, null);
                        }
                        catch (Exception ex)
                        {
                            // TODO: Need unit test for this behavior of falling through to normal build on exception

                            string valueTypeName = buildResult.Value == null
                                ? "N/A"
                                : buildResult.Value.GetType()
                                                   .Name;

                            // This can happen when context is incorrectly used to set a similarly named property and the types are different
                            _logger.DebugFormat(
                                "Normal value building will proceed for path '{0}' because the property '{1}' (of type '{2}') could not be set to value '{3}' (of type '{4}') on instance type '{5}' due to the following exception:\r\n{6}.",
                                logicalPropertyPath,
                                property.Name,
                                property.PropertyType.Name,
                                buildResult.Value ?? "[null]",
                                valueTypeName,
                                containingInstance.GetType()
                                .Name,
                                ex);

                            // Clear the result, allow normal processing to proceed
                            buildResult = null;
                        }
                    }

                    if (buildResult == null)
                    {
                        // -----------------------------------------------------------------
                        // TODO: Need unit test for logic to not pass ALL context to children
                        // -----------------------------------------------------------------
                        // TODO: This functionality/decision needs to be externalized from the Test Object Factory... as a "ContextFilter" (or something similar)
                        // For example, IChildContextFilter with a NoContextFilter, and an EdOrgContextAllowedFilter (removes non EdOrg context from children)
                        IDictionary <string, object> constraintsToPass;

                        // Is this some sort of child list?
                        if (property.PropertyType != typeof(string) &&
                            typeof(IEnumerable).IsAssignableFrom(property.PropertyType))
                        {
                            // Filter the context passed to children.
                            // Only pass EdOrg-related property value constraints to children
                            var edOrgKeyValuePairs =
                                from kvp in propertyValueConstraints.AsEnumerable()
                                where kvp.Key.IsEducationOrganizationIdentifier()
                                select kvp;

                            constraintsToPass = new Dictionary <string, object>(StringComparer.InvariantCultureIgnoreCase);

                            foreach (var kvp in edOrgKeyValuePairs)
                            {
                                constraintsToPass.Add(kvp);
                            }
                        }
                        else
                        {
                            constraintsToPass = propertyValueConstraints;
                        }

                        // -----------------------------------------------------------------

                        var buildContext = new BuildContext(
                            logicalPropertyPath + "." + property.Name,
                            property.PropertyType,
                            constraintsToPass, //propertyValueConstraints,
                            explicitType,
                            objectGraphLineage,
                            mode);

                        buildResult = BuildValue(results, buildContext, customObjectBuildContext);

                        if (buildResult.ShouldDefer)
                        {
                            continue;
                        }
                    }

                    if (buildResult.ShouldSetValue)
                    {
                        try
                        {
                            property.SetValue(containingInstance, buildResult.Value, null);
                        }
                        catch (Exception ex)
                        {
                            throw new Exception(
                                      string.Format(
                                          "Unable to set property '{0}' to value '{1}' on instance type '{2}'.",
                                          property.Name,
                                          buildResult.Value,
                                          containingInstance.GetType()
                                          .Name),
                                      ex);
                        }
                    }

                    writableProperties.Remove(property);
                    results.Add(buildResult);
                }

                // If we made no progress on the writeable properties, throw an exception now
                if (writableProperties.Count == lastWritableCount)
                {
                    throw new Exception(
                              string.Format(
                                  "The attempts to build the following properties have failed to make progress due to 'Defer' responses from the value builders:\r\n{0}.",
                                  string.Join(", ", writableProperties.Select(p => p.Name))));
                }
            }
        }
        private ValueBuildResult TryBuildCustomObject(
            List <ValueBuildResult> buildResults,
            BuildContext buildContext,
            CustomObjectBuildContext customObjectBuildContext)
        {
            if (customObjectBuildContext == null)
            {
                customObjectBuildContext = new CustomObjectBuildContext();
            }

            Type targetType = buildContext.TargetType;

            if (targetType.IsValueType ||
                targetType == typeof(string) ||
                targetType.FullName.StartsWith("System.") ||
                targetType.IsAbstract)
            {
                return(ValueBuildResult.NotHandled);
            }

            string logicalPropertyPath      = buildContext.LogicalPropertyPath;
            var    objectGraphLineage       = buildContext.ObjectGraphLineage;
            var    propertyValueConstraints = buildContext.PropertyValueConstraints;
            var    buildMode = buildContext.BuildMode;

            // Start or augment logical property path
            if (string.IsNullOrEmpty(logicalPropertyPath))
            {
                logicalPropertyPath = targetType.Name;
            }
            else
            {
                logicalPropertyPath += "." + targetType.Name;
            }

            // Initialize depth for type if it doesn't yet exist
            if (!customObjectBuildContext.CustomObjectDepthByType.ContainsKey(targetType))
            {
                customObjectBuildContext.CustomObjectDepthByType[targetType] = 0;
            }

            // Initialize sequence for type if it doesn't yet exist
            if (!customObjectBuildContext.CustomObjectCollectionSequence.ContainsKey(targetType))
            {
                customObjectBuildContext.CustomObjectCollectionSequence[targetType] = 0;
            }

            // Are we already building an instance of this type of object?
            int depth = customObjectBuildContext.CustomObjectDepthByType[targetType];

            // Are we already building an instance in a collection of this type of object?
            int sequence = customObjectBuildContext.CustomObjectCollectionSequence[targetType];

            // Don't go any deeper than 2
            if (depth == MaximumHierarchyDepth)
            {
                return(ValueBuildResult.Skip(logicalPropertyPath));
            }

            customObjectBuildContext.CustomObjectDepthByType[targetType] = depth + 1;

            try
            {
                // Attempt to create the object using a default constructor
                try
                {
                    object instance = activator.CreateInstance(targetType);

                    objectGraphLineage.AddFirst(instance);

                    try
                    {
                        BuildAndAssignPropertyValues(
                            logicalPropertyPath,
                            objectGraphLineage,
                            targetType,
                            propertyValueConstraints,
                            buildResults,
                            buildMode,
                            customObjectBuildContext);
                    }
                    finally
                    {
                        objectGraphLineage.RemoveFirst();
                    }

                    return(ValueBuildResult.WithValue(instance, logicalPropertyPath));
                }
                catch (Exception ex)
                {
                    throw new Exception(string.Format("Unable to create type '{0}' at {1}.", targetType.FullName, logicalPropertyPath), ex);
                }
            }
            finally
            {
                customObjectBuildContext.CustomObjectDepthByType[targetType]        = customObjectBuildContext.CustomObjectDepthByType[targetType] - 1;
                customObjectBuildContext.CustomObjectCollectionSequence[targetType] = sequence + 1;
            }
        }
        private ValueBuildResult BuildValue(
            List <ValueBuildResult> buildResults,
            BuildContext buildContext,
            CustomObjectBuildContext customObjectBuildContext)
        {
            string logicalPropertyPath = buildContext.LogicalPropertyPath;
            Type   targetType          = buildContext.TargetType;

            _logger.DebugFormat(
                "Initiating build of value for property path '{0}' of type '{1}' (with build context {2})...",
                string.IsNullOrEmpty(logicalPropertyPath)
                    ? "[empty]"
                    : logicalPropertyPath,
                targetType.Name,
                buildContext);

            foreach (var builder in builders)
            {
                var buildResult = builder.TryBuild(
                    buildContext);

                if (buildResult.Handled)
                {
                    if (buildResult.ShouldSetValue)
                    {
                        _logger.DebugFormat(
                            "Value built for property path '{0}' was '{1}'...",
                            string.IsNullOrEmpty(logicalPropertyPath)
                                ? "[empty]"
                                : logicalPropertyPath,
                            buildResult.Value);
                    }

                    else if (buildResult.ShouldSkip)
                    {
                        _logger.DebugFormat(
                            "Value was skipped for property path '{0}' by value builder '{1}'...",
                            string.IsNullOrEmpty(logicalPropertyPath)
                                ? "[empty]"
                                : logicalPropertyPath,
                            builder.GetType()
                            .Name);
                    }

                    return(buildResult);
                }
            }

            // No builder handled it, so try to build it using the built in
            var customBuildResult = TryBuildCustomObject(buildResults, buildContext, customObjectBuildContext);

            if (customBuildResult.Handled)
            {
                return(customBuildResult);
            }

            throw new Exception(
                      string.Format(
                          "Unable to create object of type '{0}'{1}.  Consider adding an IValueBuilder implementation to handle creating this type for serialization testing.",
                          targetType.FullName,
                          string.IsNullOrEmpty(logicalPropertyPath)
                        ? string.Empty
                        : "at logical path " + logicalPropertyPath));
        }