Principal header file



//  File. . . . : M6Property.h
//  Description : Property template
//  Rev . . . . :
//
//  Classes . . :
//
//  Modification History
//
//  11-Mar-98 : Clean up interface to validator methods         Alan Griffiths
//  27-Jan-98 : Original version                                Alan Griffiths
//
//  Copyright 1998                  Experian (formerly CCN) Limited,
//                                  Talbot House, Talbot Street,
//                                  Nottingham, NG1 5HF, ENGLAND
//  telephone   int + 44 (0)115 934 4517
//
//  The copyright to the computer program(s) herein
//  is the property of Experian Limited, ENGLAND.
//  The program(s) may be used and/or copied only with
//  the written permission of Experian Limited
//  or in accordance with the terms and conditions
//  stipulated in the agreement/contract under which
//  the programs have been supplied.

#ifndef M6PROPERTY_H
#define M6PROPERTY_H

#include <utility>
#include <list>

namespace M6Properties
{
    // This stuff visible, but not exposed, to client code
    namespace M6Private
    {
        // Abstract interface for validator callback.
        template<typename MyPropertyType>
        class M6ValidatorCallback;

        // Abstract interface for listener callback.
        template<typename MyPropertyType>
        class M6ListenerCallback;
    } // namespace M6Properties::M6Private


    /** Property template.  A wrapper for a value (the property type) that
    *   allows a "validation method" and "listener methods" to be associated
    *   with it.
    <p>
    *   A <em>validator method</em> is called whenever an attempt is made to
    *   set the property's value.  If this returns 0 the value is changed.
    *   If not, the value is unchanged.  In either case the value returned
    *   by the <em>validator method</em> is returned to the client code.
    <p>
    *   Whenever a property value changes, all current <em>listener methods</em>
    *   are invoked with the updated value.
    */
    template<typename MyValueType>
    class M6Property
    {
    public:

        /// For the convenience of client code
        typedef MyValueType MyValueType;

    /** @name 1 Construction */
    //@{
        /** Unvalidated constructor.
        @param  initValue the initial value of the property
        */
        M6Property(MyValueType initValue);

        /** Validated constructor. Client code looks like
        *       "makeValidatorCallback(*this, &Class::method)".
        @param  initValue       the initial value of the property
        @param  validator       validator object
        @param  method          the validation method
        <p>
        *   the method prototype must:
        <ul>
            <li>    be compatable with int (Class::* method)(Value new)
            <li>    return 0 or an error code for use by application
        </ul>
        */
        template<typename MyValidatorObject>
        M6Property(
            MyValueType               initValue,
            MyValidatorObject&        validator,
            int (MyValidatorObject::* method)(MyValueType)) :
        value(initValue),
        validator(new M6Private::M6ValidatorCallbackImplementation<
                        MyValueType, MyValidatorObject>(&validator, method))
        {
            // implemented in class because of MSVC5 limitations. {arg 29jan98}
        }
    //@}


    /** @name 2 Accessors */
    //@{
        /// get the value
        MyValueType getValue() const;
    //@}


    /** @name 3 Mutators */
    //@{
        /** try to the value
        @param  newValue the new value to be taken
        @return 0 or application specific error code return by the validator
        */
        int setValue(MyValueType newValue);


        /**     Add a listener.  Client code looks like
        *       "makeListenerCallback(*this, &Class::method)".
        <p>
        *       It is the responsibility of the client code to ensure
        *       that the listener object continues to exist until either
        *       the property is destroyed, or the listener is removed by
        *       calling "delListener()".
        *
        @param  listener object requiring notification
        @param  method   function to call for notification
        <p>
        *   the method prototype must:
        <ul>
            <li>    be compatable with void (Class::* method)(Value new)
            <li>    must not throw an exception
        </ul>
        */
        template<typename MyListenerObject>
        void addListener(MyListenerObject& listener, void (MyListenerObject::* method)(MyValueType))
        {
            // implemented in class because of MSVC5 limitations. {arg 29jan98}
            typedef M6Private::M6ListenerCallbackImplementation<MyValueType, MyListenerObject> L;
            listeners.push_back(MyListener(new L(&listener, method)));
        }


        /** Delete a listener.
        @param  listener object no longer requiring notification
        @param  method   function called for notification
        */
        template<typename MyListenerObject>
        void delListener(MyListenerObject& listener, void (MyListenerObject::* method)(MyValueType))
        {
            // implemented in class because of MSVC5 limitations. {arg 29jan98}
            typedef M6Private::M6ListenerCallbackImplementation<MyValueType, MyListenerObject> L;
            L oldListener(&listener, method);

            for (std::list<MyListener>::iterator i(listeners.begin());
                i != listeners.end(); ++i)
            {
                if (oldListener == *(*i))
                {
                    listeners.erase(i);
                    break;
                }
            }
        }
    //@}

    private:

        typedef std::auto_ptr<M6Private::M6ListenerCallback<MyValueType> > MyListener;
        typedef std::auto_ptr<M6Private::M6ValidatorCallback<MyValueType> > MyValidator;

        // Can't define sensible copy semantics given existence of
        // values, validators, & listeners - clearly values should be
        // copied, but what about validator (pros & cons) and listeners
        // (probably not).
        M6Property(const M6Property& rhs);
        M6Property& operator=(const M6Property& rhs);

        ///
        MyValueType value;

        ///
        MyValidator validator;


        ///
        std::list<MyListener> listeners;
    };
}


// While we need to put template implementations into the compilation unit!
#ifndef M6PROPERTYIMP_H
#include "M6PropertyImp.h"
#endif

#endif


Implementation header file


//  File. . . . : M6PropertyImp.h
//  Description : Property template implementation
//  Rev . . . . :
//
//  Classes . . :
//
//  Modification History
//
//  11-Mar-98 : Clean up interface to validator methods         Alan Griffiths
//  27-Jan-98 : Original version                                Alan Griffiths
//
//  Copyright 1998                  Experian (formerly CCN) Limited,
//                                  Talbot House, Talbot Street,
//                                  Nottingham, NG1 5HF, ENGLAND
//  telephone   int + 44 (0)115 934 4517
//
//  The copyright to the computer program(s) herein
//  is the property of Experian Limited, ENGLAND.
//  The program(s) may be used and/or copied only with
//  the written permission of Experian Limited
//  or in accordance with the terms and conditions
//  stipulated in the agreement/contract under which
//  the programs have been supplied.

#ifndef M6PROPERTYIMP_H
#define M6PROPERTYIMP_H

// Not quite the standard approach, but M6PropertyImp.h shouldn't be included
// directly by client code and this serves as a deterrent.

#ifndef M6PROPERTY_H
#error must include M6Property.h before M6PropertyImp.h
#endif

/*------------------------------------------------------------------------**
** The rest of the file is the template implementation and should not     **
** concern the user.  However, with the template inclusion model it does  **
** concern the compiler.                                 {arg 27jan98}    **
**------------------------------------------------------------------------*/
namespace M6Properties
{
    // This stuff visible, but not exposed, to client code
    namespace M6Private
    {
        // Abstract interface for validator callback.
        template<typename MyPropertyType>
        class M6ValidatorCallback
        {
        public:

            // make a clone
            virtual M6ValidatorCallback* makeClone() const = 0;

            /* do the validation.
            * @return 0 or an application specific error code
            */
            virtual int validateChange(MyPropertyType newValue) const = 0;
        };


        // Abstract interface for listener callback.
        template<typename MyPropertyType>
        class M6ListenerCallback
        {
        public:

            // make a clone
            virtual M6ListenerCallback* makeClone() const = 0;

            // do the notification
            virtual void notifyChange(MyPropertyType newValue) const = 0;

            // comparison operator
            virtual bool operator==(const M6ListenerCallback<MyPropertyType>& rhs) const = 0;
        };
    } // namespace M6Properties::M6Private


    template<typename MyValueType>
    M6Property<MyValueType>::M6Property(MyValueType initValue) :
    value(initValue)
    {
    }


    template<typename MyValueType>
    MyValueType M6Property<MyValueType>::getValue() const
    {
        return value;
    }


    template<typename MyValueType>
    int M6Property<MyValueType>::setValue(MyValueType newValue)
    {
        int rc(0);

        if (newValue != value && 0 != validator.get())
        {
            rc = validator->validateChange(newValue);

            if (0 == rc)
            {
                value = newValue;

                for (std::list<MyListener>::iterator i(listeners.begin());
                    i != listeners.end(); ++i)
                {
                    (*i)->notifyChange(newValue);
                }
            }
        }

        return rc;
    }


    // This stuff visible, but not exposed, to client code
    namespace M6Private
    {
        // Hidden implementation, default ctor, assignment OK
        template<typename MyPropertyType, typename MyValidatorObject>
        class M6ValidatorCallbackImplementation :
                public M6ValidatorCallback<MyPropertyType>
        {
        public:

            typedef int (MyValidatorObject::*MyValidatorMethod)(MyPropertyType newValue);

            M6ValidatorCallbackImplementation(MyValidatorObject* validator, MyValidatorMethod  method);


            virtual M6ValidatorCallback<MyPropertyType>* makeClone() const;

            virtual int validateChange(MyPropertyType newValue) const;

        private:

            MyValidatorObject* myValidator;
            MyValidatorMethod  myMethod;
        };


        template<typename MyPropertyType, typename MyValidatorObject>
        M6ValidatorCallbackImplementation<MyPropertyType, MyValidatorObject>::
            M6ValidatorCallbackImplementation(MyValidatorObject* validator, MyValidatorMethod  method) :
            myValidator(validator),
            myMethod(method){}


        template<typename MyPropertyType, typename MyValidatorObject>
        M6ValidatorCallback<MyPropertyType>*
        M6ValidatorCallbackImplementation<MyPropertyType, MyValidatorObject>::
            makeClone() const
        {
            return new M6ValidatorCallbackImplementation(*this);
        }


        template<typename MyPropertyType, typename MyValidatorObject>
        int
        M6ValidatorCallbackImplementation<MyPropertyType, MyValidatorObject>::
            validateChange(MyPropertyType newValue) const
        {
            return (myValidator->*myMethod)(newValue);
        }


        // Hidden implementation, default ctor, assignment OK
        template<typename MyPropertyType, typename MyListenerObject>
        class M6ListenerCallbackImplementation : public M6ListenerCallback<MyPropertyType>
        {
        public:

            typedef void (MyListenerObject::*MyListenerMethod)(MyPropertyType newValue);

            M6ListenerCallbackImplementation(MyListenerObject* validator, MyListenerMethod  method);


            virtual M6ListenerCallback<MyPropertyType>* makeClone() const;

            virtual void notifyChange(MyPropertyType newValue) const;

            virtual bool operator==(const M6ListenerCallback<MyPropertyType>& rhs) const;
        private:

            MyListenerObject* myListener;
            MyListenerMethod  myMethod;
        };


        template<typename MyPropertyType, typename MyListenerObject>
        M6ListenerCallbackImplementation<MyPropertyType, MyListenerObject>::
            M6ListenerCallbackImplementation(MyListenerObject* validator, MyListenerMethod  method) :
            myListener(validator),
            myMethod(method){}


        template<typename MyPropertyType, typename MyListenerObject>
        M6ListenerCallback<MyPropertyType>*
        M6ListenerCallbackImplementation<MyPropertyType, MyListenerObject>::
            makeClone() const
        {
            return new M6ListenerCallbackImplementation(*this);
        }


        template<typename MyPropertyType, typename MyListenerObject>
        void
        M6ListenerCallbackImplementation<MyPropertyType, MyListenerObject>::
            notifyChange(MyPropertyType newValue) const
        {
            (myListener->*myMethod)(newValue);
        }


        template<typename MyPropertyType, typename MyListenerObject>
        bool
        M6ListenerCallbackImplementation<MyPropertyType, MyListenerObject>::
            operator==(const M6ListenerCallback<MyPropertyType>& rhs) const
        {
            typedef M6ListenerCallbackImplementation<MyPropertyType, MyListenerObject>
                MyType;

            bool rc(false);

            if (const MyType* pRhs=(dynamic_cast<const MyType*>(&rhs)))
            {
                rc = myListener == pRhs->myListener &&
                     myMethod   == pRhs->myMethod;
            }

            return rc;
        }


    } // namespace M6Properties::M6Private

} // namespace M6Properties
#endif


Test harness


//  File. . . . : TestProperty.cpp
//  Description : Test harness for M6Property
//  Rev:
//
//
//  Modification History
//
//  11-Mar-98 : Clean up interface to validator methods         Alan Griffiths
//  29-Jan-98 : Original version                                                    Alan Griffiths
//
//  Copyright 1997                  Experian Limited (formerly CCN)
//                                  Talbot House, Talbot Street,
//                                  Nottingham, NG1 5HF, ENGLAND
//      telephone   int + 44 (0)115 934 4517
//
//  The copyright to the computer program(s) herein
//  is the property of Experian Limited, ENGLAND.
//  The program(s) may be used and/or copied only with
//  the written permission of Experian Limited
//  or in accordance with the terms and conditions
//  stipulated in the agreement/contract under which
//  the programs have been supplied.

#include "M6Property.h"

#include "testharness.h"

#include <iostream>
#include <string>

namespace
{
    struct DummyNumeric
    {
        int newValue;
        int notifiedValue;
        int code;

        int validate(int nv)
        {
            TEST(newValue == nv);

            return code;
        }

        void listen(int nv)
        {
            notifiedValue = nv;
            TEST(newValue == nv);
        }
    };


    struct DummyString
    {
        std::string newValue;
        std::string notifiedValue;
        int code;

        int validate(std::string nv)
        {
            TEST(newValue == nv);

            return code;
        }

        void listen(std::string nv)
        {
            notifiedValue = nv;
            TEST(newValue == nv);
        }
    };


    int testValidatorCallback()
    {
        DummyNumeric dn;
        DummyString ds;

        M6Properties::M6Private::M6ValidatorCallbackImplementation<int, DummyNumeric>  vn(&dn, &DummyNumeric::validate);
        M6Properties::M6Private::M6ValidatorCallbackImplementation<std::string, DummyString> vs(&ds, &DummyString::validate);

        dn.newValue = 1;
        dn.code     = 0;

        TEST(0 == vn.validateChange(1));

        dn.newValue = 0;
        dn.code     = 1;
        TEST(1 == vn.validateChange(0));

        ds.newValue = "cba";
        ds.code     = -1;

        TEST(-1 == vs.validateChange("cba"));

        ds.newValue = "abc";
        ds.code     = 0;
        TEST(0 == vs.validateChange("abc"));

        ds.code     = 1;
        TEST(1 == vs.validateChange("abc"));

        return 0;
    }


    int testListenerCallback()
    {
        DummyNumeric dn;
        DummyString ds;

        M6Properties::M6Private::M6ListenerCallbackImplementation<int, DummyNumeric> ln(&dn, &DummyNumeric::listen);
        M6Properties::M6Private::M6ListenerCallbackImplementation<std::string, DummyString> ls(&ds, &DummyString::listen);

        dn.notifiedValue = -2;
        dn.newValue = 0;
        dn.code     = 0;

        ln.notifyChange(0);
        TEST(0 == dn.notifiedValue);

        dn.newValue = 1;
        ln.notifyChange(1);
        TEST(1 == dn.notifiedValue);

        ds.newValue = "aaa";
        ds.code     = 0;

        ls.notifyChange("aaa");
        TEST("aaa" == ds.notifiedValue);

        ds.newValue = "bbb";
        ls.notifyChange("bbb");
        TEST("bbb" == ds.notifiedValue);

        ds.newValue = "ccc";
        ls.notifyChange("ccc");
        TEST("ccc" == ds.notifiedValue);

        return 0;
    }


    int testProperty()
    {
        using M6Properties::M6Property;

        DummyNumeric dn;
        DummyString ds;

        M6Property<int> intProperty(1, dn, &DummyNumeric::validate);
        M6Property<std::string> textProperty("abc", ds, &DummyString::validate);


        // First without listener...
        dn.notifiedValue = -2;
        dn.newValue = 0;
        dn.code     = 0;
        TEST(0 == intProperty.setValue(0));     // 3 tests
        TEST(-2 == dn.notifiedValue);

        dn.newValue = 0;
        dn.code     = 1;
        TEST(0 == intProperty.setValue(0));     // 2 tests (value doesn't change)
        TEST(-2 == dn.notifiedValue);

        dn.newValue = 1;
        dn.code     = 0;
        TEST(0 == intProperty.setValue(1));     // 3 tests
        TEST(-2 == dn.notifiedValue);


        // Now with a listener
        intProperty.addListener(dn, &DummyNumeric::listen);

        dn.notifiedValue = -2;
        dn.newValue = 0;
        dn.code     = 0;
        TEST(0 == intProperty.setValue(0));     // validation OK  - 4 tests
        TEST(0 == dn.notifiedValue);

        dn.notifiedValue = -2;
        dn.newValue = 0;
        dn.code     = 1;
        TEST(0 == intProperty.setValue(0));     // 2 tests (no change)
        TEST(-2 == dn.notifiedValue);

        dn.notifiedValue = -2;
        dn.newValue = 1;
        dn.code     = 0;
        TEST(0 == intProperty.setValue(1));     // validation OK  - 4 tests
        TEST(1 == dn.notifiedValue);


        // Now without listener
        intProperty.delListener(dn, &DummyNumeric::listen);
        dn.notifiedValue = -2;
        dn.newValue = 0;
        dn.code     = 0;
        TEST(0 == intProperty.setValue(0));     // 3 tests
        TEST(-2 == dn.notifiedValue);

        dn.newValue = 0;
        dn.code     = 1;
        TEST(0 == intProperty.setValue(0));     // 2 tests (no change)
        TEST(-2 == dn.notifiedValue);

        dn.newValue = 1;
        dn.code     = 0;
        TEST(0 == intProperty.setValue(1));     // 3 tests
        TEST(-2 == dn.notifiedValue);

        return 0;
    }
}

int main()
{
    MTestHarnesses::startTests(10 + 10 + 26);

    testValidatorCallback();

    testListenerCallback();

    testProperty();

    return MTestHarnesses::endTests();
}