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();
}