<commit_before>using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Xml;
using BusinessObjects.Validators;

namespace BusinessObjects.PCL {
    /// <summary>
    /// The class all domain objects must inherit from. 
    ///
    /// Currently supports:
    /// - XML (de)serialization;
    /// - Exensible and complex validation;
    /// - IEquatable so you can easily compare complex BusinessObjects togheter.
    /// - Binding (INotififyPropertyChanged and IDataErrorInfo).
    /// 
    /// TODO:
    /// - BeginEdit()/EndEdit() combination, and rollbacks for cancels (IEditableObject).
    /// </summary>
    public abstract class BusinessObject:  
        INotifyPropertyChanged,
         IEquatable<BusinessObject> {
        protected List<Validator> Rules;

        /// <summary>
        /// Constructor.
        /// </summary>
        protected BusinessObject() {}
        protected BusinessObject(XmlReader r) : this() { ReadXml(r); }
        //protected BusinessObject(string fileName) : this() { ReadXml(fileName); }

        /// <summary>
        /// Gets a value indicating whether or not this domain object is valid. 
        /// </summary>
        public virtual bool IsValid {
            get {
                return Error == null;
            }
        }

        /// <summary>
        /// Gets an error message indicating what is wrong with this domain object. The default is a null string.
        /// </summary>
        public virtual string Error {
            get
            {
                // Get object errors
                var result = this[string.Empty];
                // Also retrieve child objects errors
                var childrenErrors = ChildrenErrors;

                result += (result != null) ? Environment.NewLine + childrenErrors : childrenErrors;
                if (result.Trim().Length == 0) {
                    result = null;
                }
                return result;
            }
        }

        /// <summary>
        /// Gets error messages indicating what is wrong with eventual child domain objects. The default is a null string.
        /// </summary>
        private string ChildrenErrors {
            get {
                string result = null;

                foreach (var prop in GetAllDataProperties())
                {
                    var v = prop.GetValue(this, null);

                    // Only operate on BusinessObject types.
                    if (!(v is BusinessObject)) continue;

                    var childDomainObject = (BusinessObject)v;
                    if (childDomainObject.IsEmpty()) continue;

                    var childErrors = childDomainObject.Error;
                    if (childErrors == null) continue;

                    // TODO Kind of hacky. Perhaps review the Error system (array?). 
                    // Inject child object name into error messages. 
                    // IDataErrorInfo wants a string as return value however.
                    var errors = childErrors.Split(new[] {"\r\n"}, StringSplitOptions.RemoveEmptyEntries);
                    foreach (var error in errors) {
                        result += prop.Name + "." + error + Environment.NewLine;
                    }
                }
                return result;
            }
        }

        /// <summary>
        /// Gets the error message for the property with the given name.
        /// </summary>
        /// <param name="propertyName">The name of the property whose error message to get.</param>
        /// <returns>The error message for the property. The default is an empty string ("").</returns>
        public virtual string this[string propertyName] {
            get {
                var result = string.Empty;

                foreach (var validator in GetBrokenRules(propertyName)) {
                    if (propertyName == string.Empty || validator.PropertyName == propertyName) {
                        result += propertyName + validator.PropertyName + ": " + validator.Description;
                        result += Environment.NewLine;
                    }
                }
                result = result.Trim();
                if (result.Length == 0) {
                    result = null;
                }
                return result;
            }
        }

        /// <summary>
        /// Validates all rules on this domain object, returning a list of the broken rules.
        /// </summary>
        /// <returns>A read-only collection of rules that have been broken.</returns>
        public virtual ReadOnlyCollection<Validator> GetBrokenRules() {
            return GetBrokenRules(string.Empty);
        }

        /// <summary>
        /// Validates all rules on this domain object for a given property, returning a list of the broken rules.
        /// </summary>
        /// <param name="property">The name of the property to check for. If null or empty, all rules will be checked.</param>
        /// <returns>A read-only collection of rules that have been broken.</returns>
        public virtual ReadOnlyCollection<Validator> GetBrokenRules(string property) {
            property = CleanString(property);
            
            // If we haven't yet created the rules, create them now.
            if (Rules == null) {
                Rules = new List<Validator>();
                Rules.AddRange(CreateRules());
            }
            var broken = new List<Validator>();

            
            foreach (var validator in Rules) {
                // Ensure we only validate a rule 
                if (validator.PropertyName == property || property == string.Empty) {
                    var isRuleBroken = !validator.Validate(this);
                    //Debug.WriteLine(DateTime.Now.ToLongTimeString() + ": Validating the rule: '" + r.ToString() + "' on object '" + this.ToString() + "'. Result = " + ((isRuleBroken == false) ? "Valid" : "Broken"));
                    if (isRuleBroken) {
                        broken.Add(validator);
                    }
                }
            }
            return new ReadOnlyCollection<Validator>(broken);
        }

        /// <summary>
        /// Occurs when any properties are changed on this object.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Override this method to create your own rules to validate this business object. These rules must all be met before 
        /// the business object is considered valid enough to save to the data store.
        /// </summary>
        /// <returns>A collection of rules to add for this business object.</returns>
        protected virtual List<Validator> CreateRules() {
            return new List<Validator>();
        }

        /// <summary>
        /// A helper method that raises the PropertyChanged event for a property.
        /// </summary>
        ///<remarks>This is a paremeterless version which uses .NET 4.5 CallerMemberName to guess the calling function name.</remarks>
        protected virtual void NotifyChanged([CallerMemberName] string caller = "") {
            NotifyChanged(new[]{caller});
        }
        /// <summary>
        /// A helper method that raises the PropertyChanged event for a property.
        /// </summary>
        /// <param name="propertyNames">The names of the properties that changed.</param>
        /// <remarks>This is a .NET 2.0 compatible version.</remarks>
        protected virtual void NotifyChanged(params string[] propertyNames) {
            foreach (var name in propertyNames) {
                OnPropertyChanged(new PropertyChangedEventArgs(name));
            }
            OnPropertyChanged(new PropertyChangedEventArgs("IsValid"));
        }

        /// <summary>
        /// Cleans a string by ensuring it isn't null and trimming it.
        /// </summary>
        /// <param name="s">The string to clean.</param>
        protected string CleanString(string s) {
            return (s ?? string.Empty).Trim();<commit_msg>NotifyChanged implementation is 4.0+ compatible<commit_after>using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Xml;
using BusinessObjects.Validators;

namespace BusinessObjects.PCL {
    /// <summary>
    /// The class all domain objects must inherit from. 
    ///
    /// Currently supports:
    /// - XML (de)serialization;
    /// - Exensible and complex validation;
    /// - IEquatable so you can easily compare complex BusinessObjects togheter.
    /// - Binding (INotififyPropertyChanged and IDataErrorInfo).
    /// 
    /// TODO:
    /// - BeginEdit()/EndEdit() combination, and rollbacks for cancels (IEditableObject).
    /// </summary>
    public abstract class BusinessObject:  
        INotifyPropertyChanged,
         IEquatable<BusinessObject> {
        protected List<Validator> Rules;

        /// <summary>
        /// Constructor.
        /// </summary>
        protected BusinessObject() {}
        protected BusinessObject(XmlReader r) : this() { ReadXml(r); }
        //protected BusinessObject(string fileName) : this() { ReadXml(fileName); }

        /// <summary>
        /// Gets a value indicating whether or not this domain object is valid. 
        /// </summary>
        public virtual bool IsValid {
            get {
                return Error == null;
            }
        }

        /// <summary>
        /// Gets an error message indicating what is wrong with this domain object. The default is a null string.
        /// </summary>
        public virtual string Error {
            get
            {
                // Get object errors
                var result = this[string.Empty];
                // Also retrieve child objects errors
                var childrenErrors = ChildrenErrors;

                result += (result != null) ? Environment.NewLine + childrenErrors : childrenErrors;
                if (result.Trim().Length == 0) {
                    result = null;
                }
                return result;
            }
        }

        /// <summary>
        /// Gets error messages indicating what is wrong with eventual child domain objects. The default is a null string.
        /// </summary>
        private string ChildrenErrors {
            get {
                string result = null;

                foreach (var prop in GetAllDataProperties())
                {
                    var v = prop.GetValue(this, null);

                    // Only operate on BusinessObject types.
                    if (!(v is BusinessObject)) continue;

                    var childDomainObject = (BusinessObject)v;
                    if (childDomainObject.IsEmpty()) continue;

                    var childErrors = childDomainObject.Error;
                    if (childErrors == null) continue;

                    // TODO Kind of hacky. Perhaps review the Error system (array?). 
                    // Inject child object name into error messages. 
                    // IDataErrorInfo wants a string as return value however.
                    var errors = childErrors.Split(new[] {"\r\n"}, StringSplitOptions.RemoveEmptyEntries);
                    foreach (var error in errors) {
                        result += prop.Name + "." + error + Environment.NewLine;
                    }
                }
                return result;
            }
        }

        /// <summary>
        /// Gets the error message for the property with the given name.
        /// </summary>
        /// <param name="propertyName">The name of the property whose error message to get.</param>
        /// <returns>The error message for the property. The default is an empty string ("").</returns>
        public virtual string this[string propertyName] {
            get {
                var result = string.Empty;

                foreach (var validator in GetBrokenRules(propertyName)) {
                    if (propertyName == string.Empty || validator.PropertyName == propertyName) {
                        result += propertyName + validator.PropertyName + ": " + validator.Description;
                        result += Environment.NewLine;
                    }
                }
                result = result.Trim();
                if (result.Length == 0) {
                    result = null;
                }
                return result;
            }
        }

        /// <summary>
        /// Validates all rules on this domain object, returning a list of the broken rules.
        /// </summary>
        /// <returns>A read-only collection of rules that have been broken.</returns>
        public virtual ReadOnlyCollection<Validator> GetBrokenRules() {
            return GetBrokenRules(string.Empty);
        }

        /// <summary>
        /// Validates all rules on this domain object for a given property, returning a list of the broken rules.
        /// </summary>
        /// <param name="property">The name of the property to check for. If null or empty, all rules will be checked.</param>
        /// <returns>A read-only collection of rules that have been broken.</returns>
        public virtual ReadOnlyCollection<Validator> GetBrokenRules(string property) {
            property = CleanString(property);
            
            // If we haven't yet created the rules, create them now.
            if (Rules == null) {
                Rules = new List<Validator>();
                Rules.AddRange(CreateRules());
            }
            var broken = new List<Validator>();

            
            foreach (var validator in Rules) {
                // Ensure we only validate a rule 
                if (validator.PropertyName == property || property == string.Empty) {
                    var isRuleBroken = !validator.Validate(this);
                    //Debug.WriteLine(DateTime.Now.ToLongTimeString() + ": Validating the rule: '" + r.ToString() + "' on object '" + this.ToString() + "'. Result = " + ((isRuleBroken == false) ? "Valid" : "Broken"));
                    if (isRuleBroken) {
                        broken.Add(validator);
                    }
                }
            }
            return new ReadOnlyCollection<Validator>(broken);
        }

        /// <summary>
        /// Occurs when any properties are changed on this object.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Override this method to create your own rules to validate this business object. These rules must all be met before 
        /// the business object is considered valid enough to save to the data store.
        /// </summary>
        /// <returns>A collection of rules to add for this business object.</returns>
        protected virtual List<Validator> CreateRules() {
            return new List<Validator>();
        }

        /// <summary>
        /// A helper method that raises the PropertyChanged event for a property.
        /// </summary>
        ///<remarks>This is a paremeterless version which uses .NET 4.0 CallerMemberName to guess the calling function name.</remarks>
        protected virtual void NotifyChanged([CallerMemberName] string caller = "") {
            NotifyChanged(new[]{caller});
        }
        /// <summary>
        /// A helper method that raises the PropertyChanged event for a property.
        /// </summary>
        /// <param name="propertyNames">The names of the properties that changed.</param>
        /// <remarks>This is a .NET 2.0 compatible version.</remarks>
        protected virtual void NotifyChanged(params string[] propertyNames) {
            foreach (var name in propertyNames) {
                OnPropertyChanged(new PropertyChangedEventArgs(name));
            }
            OnPropertyChanged(new PropertyChangedEventArgs("IsValid"));
        }

        /// <summary>
        /// Cleans a string by ensuring it isn't null and trimming it.
        /// </summary>
        /// <param name="s">The string to clean.</param>
        protected string CleanString(string s) {
            return (s ?? string.Empty).Trim();
{'fraction_non_alphanumeric': 0.09975816203143893, 'fraction_numerical': 0.0008464328899637243, 'mean_word_length': 1.6756713037851827, 'pattern_counts': {'":': 4, '<': 113, '<?xml version=': 0, '>': 113, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3852458', 'n_tokens_mistral': 3864, 'n_tokens_neox': 3694, 'n_words': 1301}