File: IChunkBehavior.cpp

package info (click to toggle)
exempi 2.6.6-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 11,780 kB
  • sloc: cpp: 79,791; sh: 4,606; ansic: 538; makefile: 383
file content (595 lines) | stat: -rw-r--r-- 16,744 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
// =================================================================================================
// Copyright Adobe
// Copyright 2010 Adobe
// All Rights Reserved
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
// of the Adobe license agreement accompanying it. 
// =================================================================================================

#include "public/include/XMP_Environment.h"	// ! XMP_Environment.h must be the first included header.
#include "public/include/XMP_Const.h"

#include "XMPFiles/source/FormatSupport/IFF/IChunkBehavior.h"
#include "XMPFiles/source/FormatSupport/IFF/Chunk.h"

#include <algorithm>

using namespace IFF_RIFF;

//-----------------------------------------------------------------------------
// 
// getIndex(...)
// 
// Purpose: [static] Calculate index of chunk in tree
// 
//-----------------------------------------------------------------------------

static XMP_Uns32 getIndex( const IChunkContainer& tree, const Chunk& chunk )
{
	const Chunk& parent = dynamic_cast<const Chunk&>( tree );

	return (XMP_Uns32)(std::find( parent.firstChild(), parent.lastChild(), &chunk ) - parent.firstChild());
}

//-----------------------------------------------------------------------------
// 
// IChunkBehavior::arrangeChunksInPlace(...)
// 
// Purpose: Try to arrange all chunks of the source tree at their current location. 
//			In a loop the method takes each chunk of the srcTree.
//			* If a chunk is a FREE chunk then it is removed.
//			* If a chunk is a known movable chunk then adjust its offset so that there's
//			  no gap to its previous chunk. If the chunk offset was adjusted and/or the
//			  chunk grew/shrank in its size then remember the offset difference for
//			  further processing
//			* If the chunk is neither movable nor a FREE chunk and there is a offset 
//			  difference then fill possible gaps with FREE chunk or move chunks to
//			  the destTree.
// 
//-----------------------------------------------------------------------------

void IChunkBehavior::arrangeChunksInPlace( IChunkContainer& srcTree, IChunkContainer& destTree )
{
	XMP_Validate( &srcTree != &destTree, "Source and destination tree mustn't be the same", kXMPErr_InternalFailure );

	XMP_Int64 offsetAdjust = 0;

	for( XMP_Int32 index=0; index<static_cast<XMP_Int32>( srcTree.numChildren() ); index++ )
	{
		Chunk* chunk = srcTree.getChildAt( index );

		//
		// Is chunk one that might be moved in the tree
		// (and so it's a chunk that might be modified)
		//
		if( this->isMovable( *chunk ) )
		{
			//
			// Are there FREE chunks above this chunk?
			// Then remove it.
			//
			Chunk* freeChunk = NULL;

			if( index > 0 )
			{
				// find FREE chunk and merge possible multiple FREE chunks to one chunk
				freeChunk = this->mergeFreeChunks( srcTree, index-1 );
			}

			if( freeChunk != NULL )
			{
				// update running index
				index = ::getIndex( srcTree, *chunk );

				// subtract size of FREE chunk from offset adjust value
				offsetAdjust -= static_cast<XMP_Int64>( freeChunk->getPadSize(true) );

				// remove FREE chunk from the tree 
				srcTree.removeChildAt( index-1 );
				delete freeChunk;

				// update running index because one chunk was removed from the tree
				index--;
			}

			//
			// offset needs to be adjusted
			//
			if( offsetAdjust != 0 )
			{
				chunk->setOffset( chunk->getOffset() + offsetAdjust );
			}

			//
			// update adjust value if the size of the chunk has changed 
			// (and so the offsets of following chunks needs to be adjusted)
			//
			offsetAdjust += chunk->getPadSize() - chunk->getOriginalPadSize();
		}
		else if( this->isFREEChunk( *chunk ) && offsetAdjust != 0 )
		{
			//
			// chunk is a FREE chunk, just remove it
			//

			// merge FREE chunks
			chunk = this->mergeFreeChunks( srcTree, index );

			// update running index (in case multiple FREE chunk were merged)
			index = ::getIndex( srcTree, *chunk );

			// update adjust value about the total size of the FREE chunk
			offsetAdjust -= static_cast<XMP_Int64>( chunk->getPadSize(true) );

			// remove FREE chunk from tree
			srcTree.removeChildAt( index );
			delete chunk;

			// update running index
			index--;
		}
		else if( offsetAdjust != 0 )
		{
			//
			// the current chunk can't be moved,
			// so we can't adjust the offset of this chunk
			//
			XMP_Uns64 gap = 0;

			if( offsetAdjust > 0 )
			{
				//
				// One or more foregoing chunks grew in their seize and so
				// the offset of following chunks needs to be adjusted.
				// But since the current chunk can't be moved one or more previous
				// chunks are now overlapping over the current chunk.
				//
				// So now one or more of the previous chunks needs to be removed 
				// (moved to the destTree) so that the offset value of the current
				// chunk can stay where it is.
				// A possible gap will be filled with a FREE chunk.
				//

				Chunk* preChunk = NULL;

				//
				// count Chunks that needs to be moved
				//
				XMP_Validate( index-1 >= 0, "There shouldn't be an offset adjust value for the first chunk", kXMPErr_InternalFailure );

				XMP_Int32 preIndex = index;
				XMP_Uns64 preSize  = 0;

				do 
				{
					preIndex--;
					preChunk = srcTree.getChildAt( preIndex );

					XMP_Validate( this->isMovable( *preChunk ) || this->isFREEChunk( *preChunk ), "Movable or FREE chunk expected", kXMPErr_InternalFailure );

					preSize += preChunk->getPadSize( true );

				} while( static_cast<XMP_Int64>( preSize ) < offsetAdjust && preIndex > 0 );

				//
				// move chunks
				//
				for( XMP_Uns32 rem=preIndex; rem<static_cast<XMP_Uns32>( index ); rem++ )
				{
					// always fetch chunk at the first index of the range because
					// these chunks are removed from the tree
					preChunk = srcTree.removeChildAt( preIndex );

					if( this->isFREEChunk( *preChunk ) )
					{
						delete preChunk;
					}
					else
					{
						destTree.appendChild( preChunk, false );
					}
				}

				// update current index
				index = ::getIndex( srcTree, *chunk );

				//
				// calculate size of gap
				//
				XMP_Uns64 curOffset = chunk->getOffset();
				XMP_Uns64 preOffset = Chunk::HEADER_SIZE + Chunk::TYPE_SIZE;

				if( index > 0 )
				{
					preChunk = srcTree.getChildAt( index-1 );
					preOffset = preChunk->getOffset() + preChunk->getPadSize( true );
				}

				gap = curOffset - preOffset;
			}
			else if( offsetAdjust < 0 )
			{
				//
				// There is a gap between the previous chunk and the current one.
				// Fill the gap with a FREE chunk.
				//
				gap = offsetAdjust * (-1);
			}

			//
			// if there is a gap we need to fill it with a FREE chunk
			//
			if( gap > 0 )
			{
				//
				// The gap must be at least as big as the minimum size of FREE chunks.
				// If that's not the case we need to move more chunks to expand
				// the gap.
				//
				while( gap < this->getMinFREESize() )
				{
					XMP_Validate( index > 0, "Not enough space to insert FREE chunk", kXMPErr_Unimplemented );

					Chunk* preChunk = srcTree.removeChildAt( index-1 );
					gap += preChunk->getPadSize(true);
					destTree.appendChild( preChunk, false );

					// update running index
					index--;
				}

				//
				// Fill the gap with a FREE chunk.
				//
				Chunk* freeChunk = this->createFREE( gap );
				srcTree.insertChildAt( index, freeChunk );
				freeChunk->setAsNew();

				// update running index
				index++;
			}

			// reset adjust value
			offsetAdjust = 0;
		}
	}
}

//-----------------------------------------------------------------------------
// 
// IChunkBehavior::arrangeChunksInTree(...)
// 
// Purpose: This method proceeds the list of Chunks of the source tree in the 
//			passed range and looks for FREE chunks in the destination tree to 
//			move the source chunks to.
//			Source tree and destination tree could be one and the same but it's 
//			not required. If both trees are the	same then it's not allowed to 
//			cross source and destination ranges.
// 
//-----------------------------------------------------------------------------

void IChunkBehavior::arrangeChunksInTree( IChunkContainer& srcTree, IChunkContainer& destTree )
{
	XMP_Validate( &srcTree != &destTree, "Source and destination tree mustn't be the same", kXMPErr_InternalFailure );

	if( srcTree.numChildren() > 0 )
	{
		//
		// for all chunks that were moved to the end try to find a FREE chunk for them
		//
		for( XMP_Int32 index=srcTree.numChildren()-1; index>=0; index-- )
		{
			Chunk* chunk = srcTree.getChildAt(index);

			//
			// find a FREE chunk where the chunk would fit in
			//
			XMP_Int32 freeIndex = this->findFREEChunk( destTree, chunk->getSize(true) );

			if( freeIndex >= 0 )
			{
				Chunk* freeChunk = destTree.getChildAt( freeIndex );

				// remove chunk from source tree
				srcTree.removeChildAt( index );

				// insert chunk at new location
				destTree.insertChildAt( freeIndex, chunk );

				// remove the FREE chunk
				destTree.removeChildAt( freeIndex+1 );

				//
				// if the size of the FREE chunk is larger than the size of the chunk then fill
				// the gap with a new FREE chunk (the method findFREEChunk takes care that the
				// remaining space is large enough for a new FREE chunk, but findFREEChunk also
				// takes account of possible pad bytes in its calculations! Therefore following
				// calculations have to take account of a possible pad byte as well!)
				//
				if( freeChunk->getPadSize( true ) > chunk->getPadSize( true ) )
				{
					Chunk* remainFreeChunk = this->createFREE( freeChunk->getPadSize( true ) - chunk->getPadSize( true ) );
					destTree.insertChildAt( freeIndex+1, remainFreeChunk );
					remainFreeChunk->setAsNew();
				}

				delete freeChunk;
			}
		}
	}
}

//-----------------------------------------------------------------------------
// 
// IChunkBehavior::validateOffsets(...)
// 
// Purpose: Fix recursively the offset values of all modified chunks. 
//			At the same time the method checks the offset value of all not 
//			modified chunks and throws an exception if there is any discrepance
//			with the calculated offset.
// 
//-----------------------------------------------------------------------------

void IChunkBehavior::validateOffsets( IChunkContainer& tree, XMP_Uns64 startOffset /*= 0*/ )
{
	XMP_Uns64 offset = startOffset;

	//
	// for all children of the tree
	//
	for( XMP_Uns32 i=0; i<tree.numChildren(); i++ )
	{
		Chunk* chunk = tree.getChildAt(i);

		// the offset of a not modified chunk should match the calculated offset
		XMP_Validate( chunk->getOffset() == offset, "Invalid offset", kXMPErr_InternalFailure );

		if( !this->isMovable( *chunk ) )
		{
			XMP_Validate( chunk->getOffset() == chunk->getOriginalOffset(), "Invalid offset non-modified chunk", kXMPErr_InternalFailure );
		}

		// go through children
		if( chunk->getChunkMode() == CHUNK_NODE )
		{
			this->validateOffsets( *chunk, offset + Chunk::HEADER_SIZE + Chunk::TYPE_SIZE );
		}

		// calculate next offset
		offset += chunk->getPadSize(true);
	}
}

//-----------------------------------------------------------------------------
// 
// IChunkBehavior::getFreeSpace(...)
// 
// Purpose: Retrieve the free space at the passed position in the child list of 
//			the parent tree. If there's	a FREE chunk then return it.
// 
//-----------------------------------------------------------------------------

Chunk* IChunkBehavior::getFreeSpace( XMP_Int64& outFreeBytes, const IChunkContainer& tree, XMP_Uns32 index ) const
{
	// validate index
	XMP_Validate( index < tree.numChildren(), "Invalid index", kXMPErr_InternalFailure );

	Chunk* ret = NULL;

	Chunk* chunk = tree.getChildAt( index );

	if( this->isFREEChunk( *chunk ) )
	{
		//
		// chunk is a FREE chunk
		//
		outFreeBytes = chunk->getSize( true );
		ret = chunk;
	}
	else if( chunk->getChunkMode() != CHUNK_UNKNOWN && chunk->hasChanged() )
	{
		//
		// chunk is NOT a FREE chunk but the size of this chunk has changed
		//
		outFreeBytes = chunk->getOriginalSize() - chunk->getSize();
	}

	return ret;
}

//-----------------------------------------------------------------------------
// 
// IChunkBehavior::mergeFreeChunks(...)
// 
// Purpose: Try to merge existing FREE chunks at the passed position in the 
//			child list of the passed parent tree. The algorithm looks at the 
//			position, before the position and after the position.
// 
//-----------------------------------------------------------------------------

Chunk* IChunkBehavior::mergeFreeChunks( IChunkContainer& tree, XMP_Uns32 index )
{
	// validate index
	XMP_Validate( index < tree.numChildren(), "Invalid index", kXMPErr_InternalFailure );

	Chunk* ret = NULL;

	Chunk* chunk = tree.getChildAt( index );

	//
	// is chunk a FREE chunk
	//
	if( this->isFREEChunk( *chunk ) )
	{
		XMP_Uns32 indexStart	= index;
		XMP_Uns32 indexEnd		= index;

		XMP_Uns64 size			= chunk->getPadSize( true );

		//
		// find FREE chunks before start chunk
		//
		if( index > 0 )
		{
			XMP_Int32 i = XMP_Int32( index-1 );
			Chunk* c	= NULL;

			do
			{
				c = tree.getChildAt(i);

				if( this->isFREEChunk( *c ) )
				{
					size += c->getPadSize( true );
					indexStart = XMP_Uns32(i);
					i--;
				}
				else
				{
					c = NULL;
				}

			} while( i >= 0 && c != NULL );
		}

		//
		// find FREE chunks after start chunk
		//
		if( index+1 < tree.numChildren() )
		{
			XMP_Uns32 i = index+1;
			Chunk* c	= NULL;

			do
			{
				c = tree.getChildAt(i);

				if( this->isFREEChunk( *c ) )
				{
					size += c->getPadSize( true );
					indexEnd = i;
					i++;
				}
				else
				{
					c = NULL;
				}

			} while( i < tree.numChildren() && c != NULL );
		}

		if( indexStart < indexEnd )
		{
			//
			// more than one FREE chunks, so merge them
			//
			for( XMP_Uns32 i=indexStart; i<=indexEnd; i++ )
			{
				Chunk* f = tree.getChildAt( indexStart );
				tree.removeChildAt( indexStart );
				delete f;
			}

			ret  = this->createFREE( size );
			tree.insertChildAt( indexStart, ret );
			ret->setAsNew();
		}
		else
		{
			//
			// one single FREE chunk
			//
			ret = chunk;
		}
	}

	return ret;
}

//-----------------------------------------------------------------------------
// 
// IChunkBehavior::findFREEChunk(...)
// 
// Purpose: Find a FREE chunk with the passed total size (including header). 
//			If the FREE chunk is found then take care of the fact that is has 
//			to be that large (or larger) then the minimum size of a FREE chunk.
//			The method takes also into account that the passed size probably 
//			not includes a pad byte
// 
//-----------------------------------------------------------------------------

XMP_Int32 IChunkBehavior::findFREEChunk( const IChunkContainer& tree, XMP_Uns64 requiredSize /*including header*/ )
{
	XMP_Int32 ret = -1;

	for( XMP_Uns32 i=0; i<tree.numChildren(); i++ )
	{
		Chunk* chunk = tree.getChildAt(i);

		XMP_Uns64 requiredSizePad	= requiredSize + ( requiredSize % 2 );	// required size including pad byte

		if( this->isFREEChunk( *chunk ) && 
			( chunk->getPadSize( true ) == requiredSizePad || 
			chunk->getPadSize( true ) >= requiredSizePad + getMinFREESize() ) )
		{
			ret = i;
			break;
		}
	}

	return ret;
}

//-----------------------------------------------------------------------------
// 
// IChunkBehavior::moveChunks(...)
// 
// Purpose: Move a range of chunks from one container to another.
// 
//-----------------------------------------------------------------------------

void IChunkBehavior::moveChunks( IChunkContainer& srcTree, IChunkContainer& destTree, XMP_Uns32 start )
{
	XMP_Validate( &srcTree != &destTree, "Source tree and destination tree shouldn't be the same", kXMPErr_InternalFailure );

	XMP_Uns32 end = srcTree.numChildren();

	for( XMP_Uns32 index=start; index<end; index++ )
	{
		Chunk* chunk = srcTree.removeChildAt( start );
		destTree.appendChild( chunk, true );
	}
}

//-----------------------------------------------------------------------------
// 
// IChunkBehavior::isMovable(...)
// 
// Purpose: May we move a chunk of passed id/type
// 
//-----------------------------------------------------------------------------

bool IChunkBehavior::isMovable( const Chunk& chunk ) const
{
	bool ret = false;

	if( !this->isFREEChunk( chunk ) && mMovablePaths != NULL )
	{
		ChunkPath path( chunk.getIdentifier() );
		Chunk* parent = chunk.getParent();

		while( parent != NULL && parent->getID() != kChunk_NONE )
		{
			path.insert( parent->getIdentifier() );
			parent = parent->getParent();
		}

		for( std::vector<ChunkPath>::iterator iter=mMovablePaths->begin(); iter!=mMovablePaths->end() && !ret; iter++ )
		{
			ret = ( iter->match( path ) == ChunkPath::kFullMatch );
		}
	}

	return ret;
}