1 // =================================================================================================
2 // ADOBE SYSTEMS INCORPORATED
3 // Copyright 2010 Adobe Systems Incorporated
4 // All Rights Reserved
5 //
6 // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
7 // of the Adobe license agreement accompanying it.
8 // =================================================================================================
9 
10 #include "public/include/XMP_Environment.h"	// ! XMP_Environment.h must be the first included header.
11 #include "public/include/XMP_Const.h"
12 #include "source/XIO.hpp"
13 
14 #include "XMPFiles/source/FormatSupport/IFF/ChunkController.h"
15 #include "XMPFiles/source/FormatSupport/IFF/Chunk.h"
16 
17 #include <cstdio>
18 
19 using namespace IFF_RIFF;
20 
21 //-----------------------------------------------------------------------------
22 //
23 // ChunkController::ChunkController(...)
24 //
25 // Purpose: ctor/dtor
26 //
27 //-----------------------------------------------------------------------------
28 
ChunkController(IChunkBehavior * chunkBehavior,XMP_Bool bigEndian)29 ChunkController::ChunkController( IChunkBehavior* chunkBehavior, XMP_Bool bigEndian )
30 : mEndian					(NULL),
31   mChunkBehavior			(chunkBehavior),
32   mFileSize					(0),
33   mRoot						(NULL),
34   mTrailingGarbageOffset	(0),
35   mTrailingGarbageSize		(0)
36 {
37 	if (bigEndian)
38 	{
39 		mEndian = &BigEndian::getInstance();
40 	} else {
41 		mEndian = &LittleEndian::getInstance();
42 	}
43 
44 	// create virtual root chunk
45 	mRoot = Chunk::createChunk(*mEndian);
46 
47 	// share chunk paths with behavior
48 	mChunkBehavior->setMovablePaths( &mChunkPaths );
49 }
50 
~ChunkController()51 ChunkController::~ChunkController() noexcept(false)
52 {
53 	XMP_Validate( mRoot != NULL, "ERROR inserting Chunk. mRoot is NULL.", kXMPErr_InternalFailure );
54 	XMP_Assert(dynamic_cast<Chunk*>(mRoot) == static_cast<Chunk*>(mRoot));
55 	delete dynamic_cast<Chunk*>(mRoot);
56 }
57 
58 //-----------------------------------------------------------------------------
59 //
60 // ChunkController::addChunkPath(...)
61 //
62 // Purpose: Adds the given path to the array of "Chunk's of interest"
63 //
64 //-----------------------------------------------------------------------------
65 
addChunkPath(const ChunkPath & path)66 void ChunkController::addChunkPath( const ChunkPath& path )
67 {
68 	mChunkPaths.push_back(path);
69 }
70 
71 //-----------------------------------------------------------------------------
72 //
73 // ChunkController::compareChunkPaths(...)
74 //
75 // Purpose: The function parses all the sibling chunks. For every chunk it
76 //			either caches the chunk, skips it, or calls the function recusivly
77 //			for the children chunks
78 //
79 //-----------------------------------------------------------------------------
80 
compareChunkPaths(const ChunkPath & currentPath)81 ChunkPath::MatchResult ChunkController::compareChunkPaths(const ChunkPath& currentPath)
82 {
83 	ChunkPath::MatchResult result = ChunkPath::kNoMatch;
84 
85 	for( PathIterator iter = mChunkPaths.begin(); ( result == ChunkPath::kNoMatch ) && ( iter != mChunkPaths.end() ); iter++ )
86 	{
87 		result = iter->match(currentPath);
88 	}
89 
90 	return result;
91 }
92 
93 //-----------------------------------------------------------------------------
94 //
95 // ChunkController::parseChunks(...)
96 //
97 // Purpose: The function Parses all the sibling chunks. For every chunk it
98 //			either caches the chunk, skips it, or calls the function recusivly
99 //			for the children chunks
100 //
101 //-----------------------------------------------------------------------------
102 
parseChunks(XMP_IO * stream,ChunkPath & currentPath,XMP_OptionBits * options,Chunk * parent)103 void ChunkController::parseChunks( XMP_IO* stream, ChunkPath& currentPath, XMP_OptionBits* options /* = NULL */, Chunk* parent /* = NULL */)
104 {
105 	XMP_Uns64 filePos 		= stream->Offset();
106 	XMP_Bool isRoot			= (parent == mRoot);
107 	XMP_Uns64 parseLimit	= mFileSize;
108 	XMP_Uns32 chunkCnt		= 0;
109 
110 	XMP_Validate( mRoot != NULL, "ERROR inserting Chunk. mRoot is NULL.", kXMPErr_InternalFailure );
111 	XMP_Assert(dynamic_cast<Chunk*>(mRoot) == static_cast<Chunk*>(mRoot));
112 	parent = ( parent == NULL ? dynamic_cast<Chunk*>(mRoot) : parent );
113 
114 	//
115 	// calculate the parse limit
116 	//
117 	if ( !isRoot )
118 	{
119 		parseLimit = parent->getOriginalOffset() + parent->getSize( true );
120 
121 		if( parseLimit > mFileSize )
122 		{
123 			parseLimit = mFileSize;
124 		}
125 	}
126 
127 	while ( filePos < parseLimit )
128 	{
129 		XMP_Uns64 fileTail = mFileSize - filePos;
130 
131 		//
132 		// check if there is enough space (at least for id and size)
133 		//
134 		if ( fileTail < Chunk::HEADER_SIZE )
135 		{
136 			//preserve rest of bytes (fileTail)
137 			mTrailingGarbageOffset = filePos;
138 			mTrailingGarbageSize = fileTail;
139 			break; // stop parsing
140 		}
141 		else
142 		{
143 			bool chunkJump = false;
144 
145 			//
146 			// create a new Chunk
147 			//
148 			Chunk* chunk = Chunk::createChunk(* mEndian );
149 
150 			bool readFailure = false;
151 			//
152 			// read the Chunk (id, size, [type]) without caching the data
153 			//
154 			try
155 			{
156 				chunk->readChunk( stream );
157 			}
158 			catch( ... )
159 			{
160 				// remember exception during reading the chunk
161 				readFailure = true;
162 			}
163 
164 			//
165 			// validate chunk ID for top-level chunks
166 			//
167 			if( isRoot && ! mChunkBehavior->isValidTopLevelChunk( chunk->getIdentifier(), chunkCnt ) )
168 			{
169 				// notValid: preserve rest of bytes (fileTail)
170 				mTrailingGarbageOffset = filePos;
171 				mTrailingGarbageSize = fileTail;
172 				//delete unused chunk (because these are undefined trailing bytes)
173 				delete chunk;
174 				break; // stop parsing
175 			}
176 			else if ( readFailure )
177 			{
178 				delete chunk;
179 				XMP_Throw ( "Bad RIFF chunk", kXMPErr_BadFileFormat );
180 			}
181 
182 			//
183 			// parenting
184 			// (as early as possible in order to be able to clean up
185 			// the tree correctly in the case of an exception)
186 			//
187 			parent->appendChild(chunk, false);
188 
189 			// count top-level chunks
190 			if( isRoot )
191 			{
192 				chunkCnt++;
193 			}
194 
195 			//
196 			// check size if value exceeds 4GB border
197 			//
198 			if( chunk->getSize() >= 0x00000000FFFFFFFFLL )
199 			{
200 				// remember file position
201 				XMP_Int64 currentFilePos = stream->Offset();
202 
203 				// ask for the "real" size value
204 				XMP_Uns64 realSize = mChunkBehavior->getRealSize( chunk->getSize(),
205 																  chunk->getIdentifier(),
206 																  *mRoot,
207 																  stream );
208 
209 				// set new size at chunk
210 				chunk->setSize( realSize, true );
211 
212 				// set flag if the file position changed
213 				chunkJump = currentFilePos < stream->Offset();
214 			}
215 
216 			//
217 			// Repair if needed
218 			//
219 			if ( filePos + chunk->getSize(true) > mFileSize )
220 			{
221 				bool isUpdate =		( options != NULL ? XMP_OptionIsSet ( *options, kXMPFiles_OpenForUpdate ) : false );
222 				bool repairFile =	( options != NULL ? XMP_OptionIsSet ( *options, kXMPFiles_OpenRepairFile ) : false );
223 
224 				if ( ( ! isUpdate ) || ( repairFile && isRoot ) )
225 				{
226 					chunk->setSize( mFileSize-filePos-Chunk::HEADER_SIZE, true );
227 				}
228 				else
229 				{
230 					XMP_Throw ( "Bad RIFF chunk size", kXMPErr_BadFileFormat );
231 				}
232 			}
233 
234 			// extend search path
235 			currentPath.append( chunk->getIdentifier() );
236 
237 			// first 4 bytes might be already read by the chunk->readChunk function
238 			XMP_Uns64 offsetOfChunkRead = stream->Offset() -  filePos - Chunk::HEADER_SIZE;
239 
240 			switch ( compareChunkPaths(currentPath) )
241 			{
242 				case ChunkPath::kFullMatch :
243 				{
244 					chunk->cacheChunkData( stream );
245 				}
246 				break;
247 
248 				case ChunkPath::kPartMatch :
249 				{
250 					parseChunks( stream, currentPath, options, chunk);
251 					// recalculate the size based on the sizes of its children
252 					chunk->calculateSize( true );
253 				}
254 				break;
255 
256 				case ChunkPath::kNoMatch :
257 				{
258 					// Not a chunk we are interested in, so mark it as not changed
259 					// It will then be ignored by any further logic
260 					chunk->resetChanges();
261 
262 					if ( !chunkJump && chunk->getSize() > 0) // if chunk not empty
263 					{
264 						XMP_Validate( stream->Offset() + chunk->getSize() - offsetOfChunkRead <= mFileSize , "ERROR: want's to skip beyond EOF", kXMPErr_InternalFailure);
265 						stream->Seek ( chunk->getSize() - offsetOfChunkRead , kXMP_SeekFromCurrent );
266 					}
267 				}
268 				break;
269 			}
270 
271 			// remove last identifier from current path
272 			currentPath.remove();
273 
274 			// update current file position
275 			filePos = stream->Offset();
276 
277 			// skip pad byte if there is one (if size odd)
278 			if( filePos < mFileSize &&
279 				( ( chunkJump && ( stream->Offset() & 1 ) > 0 ) ||
280 				( !chunkJump && ( chunk->getSize() & 1 ) > 0 ) ) )
281 			{
282 				stream->Seek ( 1 , kXMP_SeekFromCurrent );
283 				filePos++;
284 			}
285 		}
286 	}
287 }
288 
289 
290 //-----------------------------------------------------------------------------
291 //
292 // ChunkController::parseFile(...)
293 //
294 // Purpose: construct the tree, parse children for list of interesting Chunks
295 //			All requested leaf chunks are cached, the parent chunks are created
296 //			but not cached and the rest is skipped
297 //
298 //-----------------------------------------------------------------------------
299 
parseFile(XMP_IO * stream,XMP_OptionBits * options)300 void ChunkController::parseFile( XMP_IO* stream, XMP_OptionBits* options /* = NULL */ )
301 {
302 	// store file information in root node
303 	mFileSize = stream ->Length();
304 	ChunkPath currentPath;
305 
306 	// Make sure the tree is clean before parsing
307 	cleanupTree();
308 
309 	try
310 	{
311 		parseChunks( stream, currentPath, options, dynamic_cast<Chunk*>(mRoot) );
312 	}
313 	catch( ... )
314 	{
315 		this->cleanupTree();
316 		throw;
317 	}
318 }
319 
320 
321 //-----------------------------------------------------------------------------
322 //
323 // ChunkController::writeFile(...)
324 //
325 // Purpose: Called by the handler to write back the changes to the file.
326 //
327 //-----------------------------------------------------------------------------
writeFile(XMP_IO * stream,XMP_ProgressTracker * progressTracker)328 void ChunkController::writeFile( XMP_IO* stream ,XMP_ProgressTracker * progressTracker )
329 
330 {
331 	//
332 	// if any of the top-level chunks exceeds their maximum size then skip writing and throw an exception
333 	//
334 	for( XMP_Uns32 i=0; i<mRoot->numChildren(); i++ )
335 	{
336 		Chunk* toplevel = mRoot->getChildAt(i);
337 		XMP_Validate( toplevel->getSize() < mChunkBehavior->getMaxChunkSize(), "Exceeded maximum chunk size.", kXMPErr_AssertFailure );
338 	}
339 
340 	//
341 	// if exception is thrown write chunk is skipped
342 	//
343 	mChunkBehavior->fixHierarchy(*mRoot);
344 
345 	if (mRoot->numChildren() > 0)
346 	{
347 		// The new file size (without trailing garbage) is the offset of the last top-level chunk + its size.
348 		// NOTE: the padding bytes can be ignored, as the top-level chunk is always a node, not a leaf.
349 		Chunk* lastChild = mRoot->getChildAt(mRoot->numChildren() - 1);
350 		XMP_Uns64 newFileSize = lastChild->getOffset() + lastChild->getSize(true);
351 		if ( progressTracker != 0 )
352 		{
353 			float fileWriteSize=0.0f;
354 			for( XMP_Uns32 i = 0; i < mRoot->numChildren(); i++ )
355 			{
356 				Chunk* child = mRoot->getChildAt(i);
357 				fileWriteSize+=child->calculateWriteSize(  );
358 			}
359 			XMP_Assert ( progressTracker->WorkInProgress() );
360 			progressTracker->AddTotalWork ( fileWriteSize );
361 		}
362 
363 		// Move garbage tail after last top-level chunk,
364 		// BEFORE the chunks are written -- in case the file shrinks
365 		if (mTrailingGarbageSize > 0  &&  newFileSize != mTrailingGarbageOffset)
366 		{
367 			if ( progressTracker != 0 )
368 			{
369 				XMP_Assert ( progressTracker->WorkInProgress() );
370 				progressTracker->AddTotalWork ( (float)mTrailingGarbageSize );
371 			}
372 			XIO::Move( stream, mTrailingGarbageOffset, stream, newFileSize, mTrailingGarbageSize );
373 			newFileSize += mTrailingGarbageSize;
374 		}
375 
376 		// Write changed and new chunks to the file
377 		for( XMP_Uns32 i = 0; i < mRoot->numChildren(); i++ )
378 		{
379 			Chunk* child = mRoot->getChildAt(i);
380 			child->writeChunk( stream );
381 		}
382 
383 		// file has been completely written,
384 		// truncate the file it has been bigger before
385 		if (newFileSize < mFileSize)
386 		{
387 			stream->Truncate ( newFileSize  );
388 		}
389 	}
390 }
391 
392 //-----------------------------------------------------------------------------
393 //
394 // ChunkController::getChunk(...)
395 //
396 // Purpose: returns a certain Chunk
397 //
398 //-----------------------------------------------------------------------------
399 
getChunk(const ChunkPath & path,XMP_Bool last) const400 IChunkData* ChunkController::getChunk( const ChunkPath& path, XMP_Bool last ) const
401 {
402 	IChunkData* ret = NULL;
403 
404 	if( path.length() > 0 )
405 	{
406 		ChunkPath current;
407 		ret = this->findChunk( path, current, *(dynamic_cast<Chunk*>(mRoot)), last );
408 	}
409 
410 	return ret;
411 }
412 
413 
414 //-----------------------------------------------------------------------------
415 //
416 // ChunkController::findChunk(...)
417 //
418 // Purpose: Find a chunk described by path in the hierarchy of chunks starting
419 //			at the passed chunk.
420 //			The position of chunk in the hierarchy is described by the parameter
421 //			currentPath.
422 //			This method is supposed to be recursively.
423 //
424 //-----------------------------------------------------------------------------
425 
findChunk(const ChunkPath & path,ChunkPath & currentPath,const Chunk & chunk,XMP_Bool last) const426 Chunk* ChunkController::findChunk( const ChunkPath& path, ChunkPath& currentPath, const Chunk& chunk, XMP_Bool last ) const
427 {
428 	Chunk* ret = NULL;
429 	XMP_Uns32 cnt = 0;
430 
431 	if( path.length() > currentPath.length() )
432 	{
433 		for( XMP_Uns32 i=0; i<chunk.numChildren() && ret == NULL; i++ )
434 		{
435 			//if last is true go backwards
436 			last ? cnt=chunk.numChildren()-1-i : cnt=i;
437 
438 			Chunk* child = NULL;
439 
440 			try
441 			{
442 				child = chunk.getChildAt(cnt);
443 			}
444 			catch(...)
445 			{
446 				child = NULL;
447 			}
448 
449 			if( child != NULL )
450 			{
451 				currentPath.append( child->getIdentifier() );
452 
453 				switch( path.match( currentPath ) )
454 				{
455 					case ChunkPath::kFullMatch:
456 					{
457 						ret = child;
458 					}
459 					break;
460 
461 					case ChunkPath::kPartMatch:
462 					{
463 						ret = this->findChunk( path, currentPath, *child, last );
464 					}
465 					break;
466 
467 					case ChunkPath::kNoMatch:
468 					{
469 						// Nothing to do
470 					}
471 					break;
472 				}
473 
474 				currentPath.remove();
475 			}
476 		}
477 	}
478 
479 	return ret;
480 }
481 
482 
483 //-----------------------------------------------------------------------------
484 //
485 // ChunkController::getChunks(...)
486 //
487 // Purpose: Returns all chunks that match completely to the passed path.
488 //
489 //-----------------------------------------------------------------------------
490 
getChunks(const ChunkPath & path)491 const std::vector<IChunkData*>& ChunkController::getChunks( const ChunkPath& path )
492 {
493 	mSearchResults.clear();
494 
495 	if( path.length() > 0 )
496 	{
497 		ChunkPath current;
498 		this->findChunks( path, current, *(dynamic_cast<Chunk*>(mRoot)) );
499 	}
500 
501 	return mSearchResults;
502 }//getChunks
503 
504 
505 //-----------------------------------------------------------------------------
506 //
507 // ChunkController::getTopLevelTypes(...)
508 //
509 // Purpose: Return an array containing the types of the top level nodes
510 //			Top level nodes are the ones beneath ROOT
511 //
512 //-----------------------------------------------------------------------------
513 
getTopLevelTypes()514 const std::vector<XMP_Uns32> ChunkController::getTopLevelTypes()
515 {
516 	std::vector<XMP_Uns32> typeList;
517 
518 	for( XMP_Uns32 i = 0; i < mRoot->numChildren(); i++ )
519 	{
520 		typeList.push_back( mRoot->getChildAt( i )->getType() );
521 	}
522 
523 	return typeList;
524 }// getTopLevelTypes
525 
526 
527 //-----------------------------------------------------------------------------
528 //
529 // ChunkController::findChunks(...)
530 //
531 // Purpose: Find all chunks described by path in the hierarchy of chunks starting
532 //			at the passed chunk.
533 //			The position of chunks in the hierarchy is described by the parameter
534 //			currentPath. Found chunks that match to the path are stored in the
535 //			member mSearchResults.
536 //			This method is supposed to be recursively.
537 //
538 //-----------------------------------------------------------------------------
539 
findChunks(const ChunkPath & path,ChunkPath & currentPath,const Chunk & chunk)540 void ChunkController::findChunks( const ChunkPath& path, ChunkPath& currentPath, const Chunk& chunk )
541 {
542 	if( path.length() > currentPath.length() )
543 	{
544 		for( XMP_Uns32 i=0; i<chunk.numChildren(); i++ )
545 		{
546 			Chunk* child = NULL;
547 
548 			try
549 			{
550 				child = chunk.getChildAt(i);
551 			}
552 			catch(...)
553 			{
554 				child = NULL;
555 			}
556 
557 			if( child != NULL )
558 			{
559 				currentPath.append( child->getIdentifier() );
560 
561 				switch( path.match( currentPath ) )
562 				{
563 					case ChunkPath::kFullMatch:
564 					{
565 						mSearchResults.push_back( child );
566 					}
567 					break;
568 
569 					case ChunkPath::kPartMatch:
570 					{
571 						this->findChunks( path, currentPath, *child );
572 					}
573 					break;
574 
575 					case ChunkPath::kNoMatch:
576 					{
577 						// Nothing to do
578 					}
579 					break;
580 				}
581 
582 				currentPath.remove();
583 			}
584 		}
585 	}
586 }//findChunks
587 
588 
589 //-----------------------------------------------------------------------------
590 //
591 // ChunkController::cleanupTree(...)
592 //
593 // Purpose: Cleanup function called from destructor and in case of an exception
594 //
595 //-----------------------------------------------------------------------------
596 
cleanupTree()597 void ChunkController::cleanupTree()
598 {
599 	XMP_Validate( mRoot != NULL, "ERROR inserting Chunk. mRoot is NULL.", kXMPErr_InternalFailure );
600 	XMP_Assert(dynamic_cast<Chunk*>(mRoot) == static_cast<Chunk*>(mRoot));
601 	delete dynamic_cast<Chunk*>(mRoot);
602 	mRoot = Chunk::createChunk(*mEndian);
603 }
604 
605 
606 //-----------------------------------------------------------------------------
607 //
608 // ChunkController::dumpTree(...)
609 //
610 // Purpose: dumps the tree structure
611 //
612 //-----------------------------------------------------------------------------
613 
dumpTree()614 std::string ChunkController::dumpTree( )
615 {
616 	std::string ret;
617 	char buffer[256];
618 
619 	if ( mRoot != NULL )
620 	{
621 		ret = mRoot->toString();
622 	}
623 
624 	if ( mTrailingGarbageSize != 0 )
625 	{
626 		snprintf( buffer, 255, "\n Trailing Bytes: %llu", (long long unsigned)mTrailingGarbageSize );
627 
628 		std::string str(buffer);
629 		ret.append(str);
630 	}
631 	return ret;
632 }
633 
634 //-----------------------------------------------------------------------------
635 //
636 // ChunkController::createChunk(...)
637 //
638 // Purpose: Create a new empty chunk
639 //
640 //-----------------------------------------------------------------------------
641 
createChunk(XMP_Uns32 id,XMP_Uns32 type)642 IChunkData* ChunkController::createChunk( XMP_Uns32 id, XMP_Uns32 type /*= kType_NONE*/ )
643 {
644 	Chunk* chunk = Chunk::createChunk(* mEndian );
645 
646 	chunk->setID( id );
647 	if( type != kType_NONE )
648 	{
649 		chunk->setType( type );
650 	}
651 
652 	return chunk;
653 }
654 
655 //-----------------------------------------------------------------------------
656 //
657 // ChunkController::insertChunk(...)
658 //
659 // Purpose: Insert a new chunk. The position of this new chunk within the
660 //			hierarchy is determined internally by the behavior.
661 //			Throws an exception if a chunk cannot be inserted into the tree
662 //
663 //-----------------------------------------------------------------------------
664 
insertChunk(IChunkData * chunk)665 void ChunkController::insertChunk( IChunkData* chunk )
666 {
667 	XMP_Validate( chunk != NULL, "ERROR inserting Chunk. Chunk is NULL.", kXMPErr_InternalFailure );
668 	XMP_Assert(dynamic_cast<Chunk*>(chunk) == static_cast<Chunk*>(chunk));
669 
670 	Chunk* ch = dynamic_cast<Chunk*>(chunk);
671 	mChunkBehavior->insertChunk( *mRoot, *ch );
672 	// sets OriginalSize = Size / OriginalOffset = Offset
673 	ch->setAsNew();
674 	// force set dirty flag
675 	ch->setChanged();
676 }
677 
678 //-----------------------------------------------------------------------------
679 //
680 // ChunkController::removeChunk(...)
681 //
682 // Purpose: Delete a chunk or remove/delete it from the tree.
683 //			If the chunk exists within the chunk hierarchy the chunk gets removed
684 //			from the tree and deleted.
685 //			If it is not in the tree, then it is only destroyed.
686 //
687 //-----------------------------------------------------------------------------
688 
removeChunk(IChunkData * chunk)689 void ChunkController::removeChunk( IChunkData* chunk )
690 {
691 	if( chunk != NULL )
692 	{
693 		Chunk* chk = dynamic_cast<Chunk*>(chunk);
694 
695 		if( this->isInTree( chk ) )
696 		{
697 			if( mChunkBehavior->removeChunk( *mRoot, *chk ) )
698 			{
699 				delete chk;
700 			}
701 		}
702 		else
703 		{
704 			delete chk;
705 		}
706 	}
707 }
708 
709 //-----------------------------------------------------------------------------
710 //
711 // ChunkController::isInTree(...)
712 //
713 // Purpose: return true if the passed in Chunk is part of the Chunk tree
714 //
715 //-----------------------------------------------------------------------------
716 
isInTree(Chunk * chunk)717 bool ChunkController::isInTree( Chunk* chunk )
718 {
719 	bool ret = ( mRoot == chunk );
720 
721 	if( !ret && chunk != NULL )
722 	{
723 		Chunk* parent = chunk->getParent();
724 
725 		while( !ret && parent != NULL )
726 		{
727 			ret = ( mRoot == parent );
728 			parent = parent->getParent();
729 		}
730 	}
731 
732 	return ret;
733 }
734