#include "lchange.h"
#include <QDebug>
LobChange::LobChange ()
: QObject(), ty( NoChange ), recordingInProgress( false )
{
}
LobChange::LobChange ( const LobChange& other )
: QObject(), recordingInProgress( false )
{
*this = other;
}
LobChange& LobChange::operator= ( const LobChange& other )
{
root = other.root;
addr = other.addr;
ty = other.ty;
data0 = other.data0.copy( false );
data1 = other.data1.copy( false );
name = other.name;
cd = other.cd;
steps = other.steps;
return *this;
}
bool LobChange::operator== ( const LobChange& other ) const
{
return ( root == other.root ) && ( addr == other.addr ) && ( ty == other.ty )
&& data0.equivalentTo( other.data0 ) && data1.equivalentTo( other.data1 )
&& ( name == other.name ) && ( cd == other.cd ) && ( steps == other.steps );
}
Lob LobChange::context () const
{
return root;
}
Lob LobChange::result () const
{
return context().index( address() );
}
LobChange::Type LobChange::type () const
{
return ty;
}
LobAddress LobChange::address () const
{
return ( ( ty == NoChange ) || ( ty == Compound ) ) ? LobAddress() : addr;
}
Lob LobChange::newData () const
{
return ( ( ty == Insert ) || ( ty == InsertPair ) || ( ty == Change ) ) ? data1 : Lob();
}
Lob LobChange::oldData () const
{
return ( ( ty == Remove ) || ( ty == RemovePair ) || ( ty == Change ) ) ? data0 : Lob();
}
QString LobChange::keyName () const
{
return ( ( ty == InsertPair ) || ( ty == RemovePair ) ) ? name : QString();
}
QString LobChange::keyCD () const
{
return ( ( ty == InsertPair ) || ( ty == RemovePair ) ) ? cd : QString();
}
unsigned int LobChange::count () const
{
return ( ty == Compound ) ? steps.count() : 0;
}
LobChange LobChange::step ( unsigned int n ) const
{
return ( n < count() ) ? steps[n] : LobChange();
}
bool LobChange::apply ( Lob toThis )
{
if ( toThis.isEmpty() )
toThis = root;
switch ( ty )
{
case NoChange:
return true;
case Compound:
foreach ( LobChange step, steps )
if ( !step.apply( toThis ) )
return false;
return true;
case Insert:
{
Q_ASSERT_X( addr.numSteps() > 0, "LobChange::apply",
"cannot insert a child node at an empty address" );
LobAddress allButOne = addr.up();
Lob parent = toThis.index( allButOne );
unsigned int index = addr.stepIndex( addr.numSteps() - 1 );
if ( data1.isEmpty() || ( index > parent.numChildren() ) )
return false;
parent.insertChild( index, data1.copy( false ) );
return true;
}
case InsertPair:
{
Q_ASSERT_X( addr.numSteps() > 0, "LobChange::apply",
"cannot insert an attribute pair at an empty address" );
LobAddress allButOne = addr.up();
Lob container = toThis.index( allButOne );
unsigned int index = addr.stepIndex( addr.numSteps() - 1 );
if ( data1.isEmpty() || ( index > container.numAttributes() ) )
return false;
container.insertAttribute( index, name, cd, data1.copy( false ) );
return true;
}
case Remove:
{
LobAddress allButOne = addr.up();
Lob parent = toThis.index( allButOne );
unsigned int index = addr.stepIndex( addr.numSteps() - 1 );
if ( index >= parent.numChildren() )
return false;
data0 = parent.child( index ).copy( false );
parent.removeChild( index );
return true;
}
case RemovePair:
{
LobAddress allButOne = addr.up();
Lob container = toThis.index( allButOne );
unsigned int index = addr.stepIndex( addr.numSteps() - 1 );
if ( index >= container.numAttributes() )
return false;
OmSymbolNode* key;
OmNode* value;
container.omobj->consultAttribute( index, key, value );
name = key->getName();
cd = key->getCD();
data0 = Lob( value ).copy( false );
container.removeAttribute( index );
return true;
}
case Change:
{
Lob toChange = toThis.index( addr );
if ( toChange.isEmpty() || data1.isEmpty() )
return false;
data0 = toChange.copy( false );
toChange.set( data1 );
return true;
}
default:
// the above enum is exhaustive, so this will never happen,
// but putting it here keeps the compiler from generating a silly warning
return false;
}
}
LobChange LobChange::inverse () const
{
// first copy into result all the fields it should mimick from this object
LobChange result;
result.root = root;
result.addr = addr;
// but the type gets flipped in four cases
if ( ty == Insert )
result.ty = Remove;
else if ( ty == InsertPair )
result.ty = RemovePair;
else if ( ty == Remove )
result.ty = Insert;
else if ( ty == RemovePair )
result.ty = InsertPair;
else
result.ty = ty;
// not all of these are needed for every case, but it's not too inefficient:
result.data0 = data1.copy( false );
result.data1 = data0.copy( false );
result.name = name;
result.cd = cd;
for ( uint i = count() ; i > 0 ; i-- )
result.steps << step( i - 1 ).inverse();
// done, return result
return result;
}
LobChange LobChange::combine ( const LobChange& withThis ) const
{
LobChange result;
result.ty = Compound;
result.root = root;
// now put this actions into result as one step, or as many
if ( ty == Compound ) {
foreach ( const LobChange& step, steps )
result.steps << LobChange( step );
} else {
result.steps << LobChange( *this );
}
// now put withThis into result as one step, or as many
if ( withThis.ty == Compound ) {
foreach ( const LobChange& step, withThis.steps )
result.steps << LobChange( step );
} else {
result.steps << LobChange( withThis );
}
// that's it!
return result;
}
LobChange LobChange::operator+ ( const LobChange& other ) const
{
return combine( other );
}
void LobChange::startRecording ( Lob target )
{
stopRecording(); // just in case there's some already going on
root = target;
root.prepareModifiedSignal();
connect( &root, SIGNAL(modified(const LobChange&)),
this, SLOT(recordStep(const LobChange&)) );
recordingInProgress = true;
}
void LobChange::stopRecording ()
{
disconnect( &root, 0, this, SLOT(recordStep(const LobChange&)) );
recordingInProgress = false;
}
bool LobChange::isRecording ()
{
return recordingInProgress;
}
void LobChange::recordStep ( const LobChange& step )
{
if ( step.ty == NoChange )
return;
if ( ty == NoChange )
*this = step;
else if ( ty != Compound )
*this = combine( step );
else
steps << LobChange( step );
}
LobChange LobChange::newInsert ( Lob data )
{
LobChange result;
result.ty = Insert;
result.data1 = data.copy( false );
return result;
}
LobChange LobChange::newInsertPair ( QString keyName, QString keyCD, Lob value )
{
LobChange result;
result.ty = InsertPair;
result.data1 = value.copy( false );
result.name = keyName;
result.cd = keyCD;
return result;
}
LobChange LobChange::newRemove ( Lob data )
{
LobChange result;
result.ty = Remove;
result.data0 = data.copy( false );
return result;
}
LobChange LobChange::newRemovePair ( QString keyName, QString keyCD, Lob value )
{
LobChange result;
result.ty = RemovePair;
result.data0 = value.copy( false );
result.name = keyName;
result.cd = keyCD;
return result;
}
LobChange LobChange::newChange ( Lob before, Lob after )
{
LobChange result;
result.ty = Change;
result.data0 = before.copy( false );
result.data1 = after.copy( false );
return result;
}
QString LobChange::toString ( bool verbose ) const
{
QString typeName;
switch ( ty ) {
case Insert: typeName = "Insert"; break;
case InsertPair: typeName = "InsertPair"; break;
case Remove: typeName = "Remove"; break;
case RemovePair: typeName = "RemovePair"; break;
case Change: typeName = "Change"; break;
case Compound: typeName = "Compound"; break;
case NoChange: typeName = "NoChange"; break;
default: typeName = "?ERROR?"; break;
}
QString result = QString( "LobChange::%1 in %2 at %3" )
.arg( typeName ).arg( (ulong)root.omobj ).arg( addr.toString() );
if ( ty == Compound ) {
result += QString( ", %3 steps" ).arg( count() );
for ( unsigned int i = 0 ; i < count() ; i++ )
result += "\n\t" + step( i ).toString( verbose );
} else {
QString data0str = data0.toString();
QString data1str = data1.toString();
if ( !verbose ) {
QString dots = ( data0str.length() > 30 ) ? "..." : "";
data0str = data0str.left( 30 ).replace( "\n", "\\n " ) + dots;
dots = ( data1str.length() > 30 ) ? "..." : "";
data1str = data1str.left( 30 ).replace( "\n", "\\n " ) + dots;
}
result += QString( ", data0 %4, data1 %5, key name/cd %6" )
.arg( data0.isEmpty() ? "empty" : data0str )
.arg( data1.isEmpty() ? "empty" : data1str )
.arg( ( name.isEmpty() && cd.isEmpty() ) ?
QString( "both empty" ) : name + "/" + cd );
}
return result;
}