public void TestDefault()
        {
            // Automatic default
            var goodProperty = new HierarchicalProperty(GetType(), "Foo", typeof(bool), false, null, null, null);
            Assert.DoesNotThrow(() => goodProperty.Default.GetType());

            // Provided factory
            goodProperty = new HierarchicalProperty(GetType(), "Foo", typeof(bool), false, null, () => true, null);
            Assert.IsTrue((bool)goodProperty.Default);

            // Faulty factory (null return for value type)
            var mustFail = new HierarchicalProperty(GetType(), "Foo", typeof(bool), false, null, () => null, null);
            Assert.Throws<PropertyException>(() => mustFail.Default.GetType());

            // Faulty factory (the factory throws exception)
            mustFail = new HierarchicalProperty(GetType(), "Foo", typeof(bool), false, null, () =>
            {
                throw null;
            }, null);
            Assert.Throws<PropertyException>(() => mustFail.Default.GetType());

            // Faulty factory (returns the wront type)
            mustFail = new HierarchicalProperty(GetType(), "Foo", typeof(double), false, null, () => 1, null);
            Assert.Throws<PropertyException>(() => mustFail.Default.GetType());
        }
        public void TestCloner()
        {
            // The clone is the object itself (very efficient if the type is immutable)
            var goodProperty = new HierarchicalProperty(GetType(), "Foo", typeof(object), false, null, null, o => o);
            var obj = new object();
            Assert.IsTrue(goodProperty.IsCloneable, "Cloner method provided");
            Assert.AreSame(obj, goodProperty.Clone(obj));

            // Must clone automatically
            goodProperty = new HierarchicalProperty(GetType(), "Foo", typeof(double), false, null, null, null);
            Assert.IsTrue(goodProperty.IsCloneable, "Is primitive");
            Assert.AreEqual(14.0, (double)goodProperty.Clone(14.0));

            // Must return null
            goodProperty = new HierarchicalProperty(GetType(), "Foo", typeof(object), false, null, null, null);
            Assert.IsFalse(goodProperty.IsCloneable, "Is object and no cloner provided");
            Assert.IsNull(goodProperty.Clone(null));

            // Must fail from type mismatch
            var mustFail = new HierarchicalProperty(GetType(), "Foo", typeof(double), false, null, null, o => (int)(double)o);
            Assert.IsTrue(mustFail.IsCloneable, "Cloner method provided");
            Assert.Throws<ArgumentException>(() => mustFail.Clone(1)); // Wrong argument type
            Assert.Throws<PropertyException>(() => mustFail.Clone(1.0)); // Wrong type returned by the cloner
        }
        public void TestParser()
        {
            // string returns itself
            var goodProperty = new HierarchicalProperty(GetType(), "Foo", typeof(string), false, null, null, null);
            var str = 1.ToString();
            Assert.IsTrue(goodProperty.IsParsable, "Is string");
            Assert.AreSame(str, goodProperty.Parse(str));

            // Must parse automatically
            goodProperty = new HierarchicalProperty(GetType(), "Foo", typeof(double), false, null, null, null);
            Assert.IsTrue(goodProperty.IsParsable, "Is primitive");
            Assert.AreEqual(14.0, (double)goodProperty.Parse("14.0"));

            // Must parse nullable types automatically too.
            goodProperty = new HierarchicalProperty(GetType(), "Foo", typeof(DateTime?), false, null, null, null);
            var now = DateTime.Now;
            Assert.IsTrue(goodProperty.IsParsable, "Is Nullable with an underlying type understood by the Convert");
            Assert.AreEqual(Convert.ToDateTime(now.ToString(CultureInfo.InvariantCulture)),
                (DateTime)goodProperty.Parse(now.ToString(CultureInfo.InvariantCulture)));

            // Must throw on null
            var mustFail = new HierarchicalProperty(GetType(), "Foo", typeof(object), false, null, null, null);
            Assert.IsFalse(mustFail.IsParsable, "Is object and no parser provided");
            Assert.Throws<ArgumentNullException>(() => mustFail.Parse(null));

            // Must fail from type mismatch
            mustFail = new HierarchicalProperty(GetType(), "Foo", typeof(double), false, s => int.Parse(s), null, null);
            Assert.IsTrue(mustFail.IsParsable, "Parser method provided");
            Assert.Throws<PropertyException>(() => mustFail.Parse("1")); // Wrong type returned by the cloner
        }
Example #4
0
        /// <summary>
        /// Register a new managed property.
        /// </summary>
        /// <returns>The representative for the property.</returns>
        /// <param name="owner">The type that owns this property</param>
        /// <param name="name">Name of the property (preferably same as it's CLR wrapper)</param>
        /// <param name="type">The type of this property</param>
        /// <param name="isInheritable">Whether the property can be inherited from the parent</param>
        /// <param name="parser">
        /// The function used to parse the string representation of this peoperty
        /// (not needed for primitives, enums, string, <see cref="DateTime"/>, 
        /// <see cref="DateTimeOffset"/> and their nullable forms)
        /// </param>
        /// <param name="defaultFactory">
        /// The function used to produce the default value of this property (optional)
        /// </param>
        /// <param name="cloner">
        /// The function used to clone the peoperty with
        /// (not needed for <see cref="ICloneable"/>s, strings, enums, all value types and their nullable forms)
        /// </param>
        /// <exception cref="ArgumentNullException">either owner, name or type is null</exception>
        /// <exception cref="T:System.ArgumentException">name is whitespace or already registered</exception>
        protected static HierarchicalProperty RegisterProperty(
            Type owner,
            string name,
            Type type,
            bool isInheritable = false,
            Func<string, object> parser = null,
            Func<object> defaultFactory = null,
            Func<object, object> cloner = null)
        {
            // Check for nulls.
            if (owner == null)
                throw new ArgumentNullException(nameof(owner));
            if (name == null)
                throw new ArgumentNullException(nameof(name));
            if (type == null)
                throw new ArgumentNullException(nameof(type));

            // Make sure the name is valid.
            if (string.IsNullOrWhiteSpace(name))
                throw new ArgumentException("name must have value. null or whitespace is not acceptable.", nameof(name));

            // Add a mapping for the owner if it does not already exist.
            if (!RegisteredProperties.ContainsKey(owner))
                RegisteredProperties.Add(owner, new Dictionary<string, HierarchicalProperty>());

            // Make sure the name is not already registered.
            if (RegisteredProperties[owner].ContainsKey(name))
                throw new ArgumentException("A property with the same name as the name provided is already registered.", nameof(name));

            // Create a new instance of HierarchicalProperty for this property.
            var hierarchicalProperty = new HierarchicalProperty(owner, name, type, isInheritable, parser, defaultFactory, cloner);
            // Add the property to its owner's list of properties.
            RegisteredProperties[owner].Add(name, hierarchicalProperty);
            // Expose the instance to client code.
            return hierarchicalProperty;
        }
Example #5
0
        /// <summary>
        /// Bind the property to a path from the <see cref="DataContext"/>
        /// </summary>
        /// <param name="property">The property to bind</param>
        /// <param name="expression"> 
        /// The expression string.
        /// The format of the expression must be one of the following:
        /// <list type="number">
        ///     <item>
        ///         <term>"."</term> 
        ///         <description>
        ///             The value will be the DataContext itself.
        ///         </description>
        ///     </item>
        ///     <item>
        ///         <term>"Foo"</term> 
        ///         <description>
        ///             The value will be from <code>DataContext.Foo</code>. 
        ///         </description>
        ///     </item>
        ///     <item>
        ///         <term>"Foo[5].Bar"</term> 
        ///         <description>
        ///             The value will be from <code>DataContext.Foo[5].Bar</code>. 
        ///         </description>
        ///     </item>
        ///     <item>
        ///         <term>"Foo.Bar["baz"].ToBaz()"</term> 
        ///         <description>
        ///             The value will be from <code>DataContext.Foo.Bar["baz"].ToBaz()</code>. 
        ///         </description>
        ///     </item>
        /// </list>
        /// </param>
        /// <param name="ignoreOnError">
        /// Determines whether faulty expressions should
        /// throw exception or ignore.
        /// <remarks>
        /// Caution: Enabling this will also silently ignore syntactic errors.
        /// </remarks>
        /// </param>
        /// <exception cref="ArgumentNullException">property or expression is null</exception>
        /// <exception cref="T:System.InvalidOperationException">object is read only</exception>
        public void SetBinding(HierarchicalProperty property, string expression, bool ignoreOnError = false)
        {
            ThrowOnReadOnly();

            if (property == null)
                throw new ArgumentNullException(nameof(property));

            if (expression == null)
                throw new ArgumentNullException(nameof(expression));

            // In case the binding is done on the data context itself change the target to the parent
            var target = (property == DataContextProperty) ? HierarchyParent : this;
            // Set the value for the property as a binding.
            _ownValues[property] = new Binder(target, expression, ignoreOnError);
        }
Example #6
0
        /// <summary>
        /// Set the property to this object as its own.
        /// </summary>
        /// <exception cref="ArgumentNullException">property is null</exception>
        /// <exception cref="T:System.InvalidOperationException">object is read only</exception>
        public void SetValue(HierarchicalProperty property, object value)
        {
            ThrowOnReadOnly();

            if (property == null)
                throw new ArgumentNullException(nameof(property));

            // Make sure non-nullable value-types are not assigned null.
            if (value == null
                // Make sure the property is value type...
                && property.Type.IsValueType
                // And not nullable.
                && Nullable.GetUnderlyingType(property.Type) == null)
                throw new ArgumentException("null is not assignable to " + property.Type, nameof(value));

            // It is now okay to assign null to the property. But not value of the wrong type.
            if (value != null && !property.Type.IsInstanceOfType(value))
                throw new ArgumentException("value is not an instance of " + property.Type, nameof(value));

            // Add or update the value for the property.
            _ownValues[property] = value;
        }
Example #7
0
        /// <summary>
        /// Indicates whether this object or any ancestors has a value of the provided property.
        /// </summary>
        /// <exception cref="ArgumentNullException">property is null</exception>
        public bool HasValue(HierarchicalProperty property)
        {
            if (property == null)
                throw new ArgumentNullException(nameof(property));

            // This object itself has the value.
            return HasOwnValue(property)
            // Otherwise, crawl up the tree if the property is inheritable...
            || (property.IsInheritable
            // And has a parent.
            && HierarchyParent != null
            // Recursively check the parent to determine whether
            // a value can be found in the hierarchy.
            && HierarchyParent.HasValue(property));
        }
Example #8
0
        /// <summary>
        /// Indicates whether this object has its own value of the provided property.
        /// </summary>
        /// <exception cref="ArgumentNullException">property is null</exception>
        public bool HasOwnValue(HierarchicalProperty property)
        {
            if (property == null)
                throw new ArgumentNullException(nameof(property));

            return _ownValues.ContainsKey(property);
        }
Example #9
0
        /// <summary>
        /// Get the value for the provided property.
        /// Returns the property's default if none exist.
        /// </summary>
        /// <exception cref="ArgumentNullException">property is null</exception>
        /// <exception cref="BindingExceprtion">expression might have syntax error</exception>
        public object GetValue(HierarchicalProperty property)
        {
            if (property == null)
                throw new ArgumentNullException(nameof(property));

            // This object itself has the value.
            if (HasOwnValue(property))
            {
                var value = _ownValues[property];

                // Check for binding.
                var binder = value as Binder;

                // The property is not bound. return the value.
                if (binder == null)
                    return value;

                // Apply binding
                var result = binder.Value;

                // Since binding is done after the context is set
                // type checking must be done on evaluation.
                var type = property.Type;

                // If the result of binding is null it means either
                // the DataContext is null or the binding expression evaluated to null.
                // Either way the property must be its type's default.
                if (result == null)
                    return type.IsValueType ? Activator.CreateInstance(type) : null;

                // If the result is an instance of the property type return the result
                if (type.IsInstanceOfType(result))
                    return result;

                try
                {
                    // Otherwise try for conversion.
                    return Convert.ChangeType(result, type);
                }
                catch (Exception)
                {
                    throw new BindingTypeMismatchExceprtion(
                        "The type of the result from evaluating the binding expression does not match the expected type",
                        null, this, binder.Expression, result.GetType(), type);
                }
            }
            // Return the property's default value if inheritance
            // is not allowed or there are no parents.
            if (!property.IsInheritable || HierarchyParent == null)
                return property.Default;
            // Recursively query the parent for the value.
            return HierarchyParent.GetValue(property);
        }
 static HierarchicalObjectImp()
 {
     // register the Foo property
     FooProperty = RegisterProperty(typeof(HierarchicalObjectImp), nameof(Foo), typeof(int), true);
     // register the Bar property
     BarProperty = RegisterProperty(typeof(HierarchicalObjectImp), nameof(Bar), typeof(object), true);
 }
Example #11
0
 /// <summary>
 /// Initializes a new instance of the <see cref="PropertyException"/> class.
 /// </summary>
 internal PropertyException(string message, Exception innerException, HierarchicalProperty targetProperty)
     : base(message, innerException)
 {
     TargetProperty = targetProperty;
 }