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 #ifndef _Chunk_h_
11 #define _Chunk_h_
12 
13 #include "public/include/XMP_Environment.h"	// ! XMP_Environment.h must be the first included header.
14 
15 #include "public/include/XMP_Const.h"
16 #include "public/include/XMP_IO.hpp"
17 
18 #include "XMPFiles/source/XMPFiles_Impl.hpp"
19 #include "source/XMPFiles_IO.hpp"
20 
21 #include "source/Endian.h"
22 #include "XMPFiles/source/FormatSupport/IFF/ChunkPath.h"
23 #include "XMPFiles/source/FormatSupport/IFF/IChunkData.h"
24 #include "XMPFiles/source/FormatSupport/IFF/IChunkContainer.h"
25 
26 namespace IFF_RIFF
27 {
28 	/**
29 	 * CHUNK_UNKNOWN	= Either new chunk or a chunk that was read, but not cached
30 	 *					  (it is not decided yet whether it becones a node or leaf or is not cached at all)
31 	 * CHUNK_NODE		= Node chunk that contains children, but no own data (except the optional type)
32 	 * CHUNK_LEAF		= Leaf chunk that contains data but no children
33 	 */
34 	enum ChunkMode { CHUNK_UNKNOWN = 0, CHUNK_NODE = 1, CHUNK_LEAF = 2 };
35 
36 /**
37  *	Each Chunk of the IFF/RIFF based file formats (e.g. WAVE, AVI, AIFF) are represented by
38  *	instances of the class Chunk.
39  *  A chunk can be a node chunk containing children, a leaf chunk containing data or an "unknown" chunk,
40  *	which means that its content has not cached/loaded yet (or will never be during the file handling);
41  *	see ChunkMode for more details.
42  *
43  *	Note: A Chunk can either have a chunk OR a list of child chunks, but never both.
44  *
45  *	This class provides access to the children or the data of a chunk, depending of the type.
46  *	It keeps track of its size. When the size is changed (by adding or removing data/ or children),
47  *  the size is also fixed for the parent hierarchy.
48  *
49  *  The dirty flag (hasChanged()) that is set for each change of a chunk is also promoted to the parents.
50  *  The chunk stores its original and new offset within the host file, but its *not* automatically correctin the offset;
51  *	this is done by the IChunkBehavior class.
52  *	The Chunk class provides an interface to iterate through the tree structure of Chunks.
53  *	There are methods to insert, remove and move Chunk's in its children tree structure.
54  *
55  *	The chunk can read itself from a host file (readChunk()), but it does not automatically read its children,
56  *	because they are not necessarily used by the file handler.
57  *	The method writeChunk() recurses through the complete chunk tree and writes the *changed* chunks back to the host file.
58  *	It is important that the offsets have been fixed before.
59  *
60  *	Table about endianess in the different RIFF file formats:
61  *
62  *			|	ID 		size 	type 	data
63  *	-----------------------------------------
64  *	AVI		|	BE		LE		BE		LE
65  *	WAV		|	BE		LE		BE		LE
66  *	AIFF	|	BE		BE		BE		BE
67  */
68 class Chunk : public IChunkData,
69 			  public IChunkContainer
70 {
71 	public:
72 		/** Factory to create an empty chunk */
73 		static Chunk* createChunk( const IEndian& endian );
74 
75 		/** Factory to create an empty chunk */
76 		static Chunk* createUnknownChunk(
77 			const IEndian& endian,
78 			const XMP_Uns32 id,
79 			const XMP_Uns32 type,
80 			const XMP_Uns64 size,
81 			const XMP_Uns64 originalOffset = 0,
82 			const XMP_Uns64 offset = 0
83 		);
84 
85 		/** Static factory to create a leaf chunk with no data area or only the type in the data area */
86 		static Chunk* createHeaderChunk( const IEndian& endian,	const XMP_Uns32 id,	const XMP_Uns32 type = kType_NONE );
87 
88 		/**
89 		 * dtor
90 		 */
91 		~Chunk();
92 
93 
94 		//===================== IChunkData interface implementation ================
95 
96 		/**
97 		 * Get the chunk ID
98 		 *
99 		 * @return	Return the ID, 0 if the chunk does not have an ID.
100 		 */
getID()101 		inline XMP_Uns32 getID() const		{ return mChunkId.id; }
102 
103 		/**
104 		 * Get the chunk type (if available)
105 		 * (the first four data bytes of the chunk could be a chunk type)
106 		 *
107 		 * @return	Return the type, kType_NONE if the chunk does not contain data.
108 		 */
getType()109 		inline XMP_Uns32 getType() const		{ return mChunkId.type; }
110 
111 		/**
112 		 * Get the chunk identifier [id and type]
113 		 *
114 		 * @return	Return the identifier
115 		 */
getIdentifier()116 		inline const ChunkIdentifier& getIdentifier() const		{ return mChunkId; }
117 
118 		/**
119 		 * Access the data of the chunk.
120 		 *
121 		 * @param data OUT pointer to the byte array
122 		 * @return size of the data block, 0 if no data is available
123 		 */
124 		XMP_Uns64 getData( const XMP_Uns8** data ) const;
125 
126 		/**
127 		 * Set new data for the chunk.
128 		 * Will delete an existing internal buffer and recreate a new one
129 		 * and copy the given data into that new buffer.
130 		 *
131 		 * @param data pointer to the data to put into the chunk
132 		 * @param size Size of the data block
133 		 * @param writeType if true, the type of the chunk (getType()) is written in front of the data block.
134 		 */
135 		void setData( const XMP_Uns8* const data, XMP_Uns64 size, XMP_Bool writeType = false );
136 
137 		/**
138 		* Returns the current size of the Chunk.
139 		*
140 		* @param includeHeader if set, the returned size will be the whole chunk size including the header
141 		* @return Returns either the size of the data block of the chunk or size of the whole chunk, including the eight byte header.
142 		*/
143 		XMP_Uns64 getSize( bool includeHeader = false ) const		{ return includeHeader ? mSize + HEADER_SIZE : mSize; }
144 
145 		/**
146 		 * Returns the current size of the Chunk including a pad byte if the size isn't a even number
147 		 *
148 		 * @param includeHeader		if set, the returned size will be the whole chunk size including the header
149 		 * @return					Returns either the size of the data block of the chunk or size of the whole chunk, including the eight byte header.
150 		 */
151 		XMP_Uns64 getPadSize( bool includeHeader = false ) const;
152 		/**
153 		 * @return Returns the mode of the chunk (see ChunkMode definition).
154 		 */
getChunkMode()155 		ChunkMode getChunkMode() const { return mChunkMode; }
156 
157 		/* The following methods are getter/setter for certain data types.
158 		 * They always take care of little-endian/big-endian issues.
159 		 * The offset starts at the data area of the Chunk. */
160 
161 		XMP_Uns32 getUns32( XMP_Uns64 offset=0 ) const;
162 		void setUns32( XMP_Uns32 value, XMP_Uns64 offset=0 );
163 
164 		XMP_Uns64 getUns64( XMP_Uns64 offset=0 ) const;
165 		void setUns64( XMP_Uns64 value, XMP_Uns64 offset=0 );
166 
167 		XMP_Int32 getInt32( XMP_Uns64 offset=0 ) const;
168 		void setInt32( XMP_Int32 value, XMP_Uns64 offset=0 );
169 
170 		XMP_Int64 getInt64( XMP_Uns64 offset=0 ) const;
171 		void setInt64( XMP_Int64 value, XMP_Uns64 offset=0 );
172 
173 		std::string getString( XMP_Uns64 size = 0, XMP_Uns64 offset=0 ) const;
174 		void setString( std::string value, XMP_Uns64 offset=0 );
175 
176 
177 		//===================== IChunk interface implementation ================
178 
179 		//FIXME XMP exception if size cast from 64 to 32 looses data
180 
181 		/**
182 		 * Sets the chunk id.
183 		 */
184 		void setID( XMP_Uns32 id );
185 
186 		/**
187 		 * Sets the chunk type.
188 		 */
189 		void setType( XMP_Uns32 type );
190 
191 		/**
192 		 * Sets the chunk size.
193 		 * NOTE: Should only be used for repairing wrong sizes in files (repair flag).
194 		 * Normally Size is changed by changing the data automatically!
195 		 */
196 		inline void setSize( XMP_Uns64 newSize, bool setOriginal = false )	{ mDirty = mSize != newSize; mSize = newSize; mOriginalSize = setOriginal ? newSize : mOriginalSize; }
197 
198 		/**
199 		 * Calculate the size of the chunks that are dirty including the size
200 		 * of its children
201 		 */
202 		XMP_Int64 calculateWriteSize( ) const;
203 
204 		/**
205 		 * Calculate the size of this chunks based on its children sizes.
206 		 * If this chunk has no children then no new size will be calculated.
207 		 */
208 		XMP_Uns64 calculateSize( bool setOriginal = false );
209 
210 		/**
211 		 * @return Returns the offset of the chunk within the stream.
212 		 */
getOffset()213 		inline XMP_Uns64 getOffset () const		{ return mOffset; }
214 
215 		/**
216 		 * @return Returns the original offset of the chunk within the stream.
217 		 */
getOriginalOffset()218 		inline XMP_Uns64 getOriginalOffset () const		{ return mOriginalOffset; }
219 
220 		/**
221 		* Returns the original size of the Chunk
222 		*
223 		 * @param includeHeader		if set, the returned original size will be the whole chunk size including the header
224 		 * @return					Returns the original size of the chunk within the stream (inluding/excluding headerSize).
225 		 */
226 		inline XMP_Uns64 getOriginalSize( bool includeHeader = false ) const		{ return includeHeader ? mOriginalSize + HEADER_SIZE : mOriginalSize; }
227 
228 		/**
229 		 * Returns the original size of the Chunk including a pad byte if the size isn't a even number
230 		 *
231 		 * @param includeHeader		if set, the returned size will be the whole chunk size including the header
232 		 * @return					Returns either the size of the data block of the chunk or size of the whole chunk, including the eight byte header.
233 		 */
234 		XMP_Uns64 getOriginalPadSize( bool includeHeader = false ) const;
235 
236 		/**
237 		 * Adjust the offset that this chunk has within the file.
238 		 *
239 		 * @param	newOffset the new offset within the file stream
240 		 */
241 		void setOffset (XMP_Uns64 newOffset);   // changes during rearranging
242 
243 		/**
244 		 * Has the Chunk class changes, or has the position within the file been changed?
245 		 * If the result is true the chunk has to be written back to the file
246 		 * (all parent chunks are also set to dirty in that case).
247 		 *
248 		 * @return		Returns true if the chunk node has been modified.
249 		 */
hasChanged()250 		XMP_Bool hasChanged() const		{ return mDirty; }
251 
252 		/**
253 		 * Sets this node and all of its parents up to the tree root dirty.
254 		 */
255 		void setChanged();
256 
257 		/**
258 		 * Resets the dirty status for this chunk and its children to false
259 		 */
260 		void resetChanges();
261 
262 		/**
263 		 *Sets all necessary member variables to flag this chunk as a new one being inserted into the tree
264 		 */
265 		void setAsNew();
266 
267 		/**
268 		 * @return Returns the parent chunk (can be NULL if this is the root of the tree).
269 		 */
getParent()270 		inline Chunk* getParent() const		{ return mParent; }
271 
272 		/**
273 		 * Creates a string representation of the chunk (debug method).
274 		 */
275 		std::string toString( std::string tabs = std::string() , XMP_Bool showOriginal = false );
276 
277 
278 		//-------------------
279 		//  file access
280 		//-------------------
281 
282 		/**
283 		 * Read id, size and offset and create a chunk with mode CHUNK_UNKNOWN.
284 		 * The file is expected to be open and is not closed!
285 		 *
286 		 * @param	file	File reference to read the chunk from
287 		 */
288 		void readChunk( XMP_IO* file );
289 
290 		/**
291 		 * Stores the data in the class (only called if required).
292 		 * The file is expected to be open and is not closed!
293 		 *
294 		 * @param	file	File reference to cache the chunk data
295 		 */
296 		void cacheChunkData( XMP_IO* file );
297 
298 		/**
299 		 * Write or updates chunk (new data, new size, new position).
300 		 * The file is expected to be open and is not closed!
301 		 *
302 		 * Behavior for the different chunk types:
303 		 *
304 		 * CHUNK_UNKNOWN:
305 		 *     - does not write anything back
306 		 *     - throws exception if hasChanged == true
307 		 *
308 		 * CHUNK_LEAF:
309 		 *     - writes ID (starting with offset)
310 		 *     - writes size
311 		 *     - writes buffer (including the optional type at the beginning)
312 		 *
313 		 * CHUNK_NODE:
314 		 *     - writes ID (starting with offset)
315 		 *     - writes size
316 		 *     - writes type if defined
317 		 *     - calls writeChunk on it's children
318 		 *
319 		 * Note: readChunk() and optionally cacheChunkData() has to be called before!
320 		 *
321 		 * @param	file	File reference to write the chunk to
322 		 */
323 		void writeChunk( XMP_IO* file );
324 
325 
326 		//-------------------
327 		//  children access
328 		//-------------------
329 
330 		/**
331 		 * @return	Returns the number children chunks.
332 		 */
333 		XMP_Uns32 numChildren() const;
334 
335 		/**
336 		 * Returns a child node.
337 		 *
338 		 * @param	pos		position of the child node to return
339 		 * @return			Returns the child node at the given position.
340 		 */
341 		Chunk* getChildAt( XMP_Uns32 pos ) const;
342 
343 		/**
344 		 * Appends a child node at the end of the children list.
345 		 *
346 		 * @param	node		the new node
347 		 * @param	adjustSizes	adjust size of chunk and parents
348 		 * @return				Returns the added node.
349 		 */
350 		void appendChild( Chunk* node, XMP_Bool adjustSizes = true );
351 
352 		/**
353 		 * Inserts a child node at a certain position.
354 		 *
355 		 * @param	pos			position in the children list to add the new node
356 		 * @param	node		the new node
357 		 * @return				Returns the added node.
358 		 */
359 		void insertChildAt( XMP_Uns32 pos, Chunk* node );
360 
361 		/**
362 		 * Removes a child node at a given position.
363 		 *
364 		 * @param	pos				position of the node to delete in the children list
365 		 *
366 		 * @return					The removed chunk
367 		 */
368 		Chunk* removeChildAt( XMP_Uns32 pos );
369 
370 		/**
371 		 * Remove child at the passed position and insert the new chunk
372 		 *
373 		 * @param pos			Position of chunk that will be replaced
374 		 * @param chunk			New chunk
375 		 *
376 		 * @return		Replaced chunk
377 		 */
378 		Chunk* replaceChildAt( XMP_Uns32 pos, Chunk* node );
379 
380 		//--------------------
381 		//  children iteration
382 		//--------------------
383 
384 		typedef std::vector<Chunk*>::iterator ChunkIterator;
385 		typedef std::vector<Chunk*>::const_iterator ConstChunkIterator;
386 
387 		ConstChunkIterator firstChild() const;
388 
389 		ConstChunkIterator lastChild() const;
390 
391 		/** The size of the header (id+size) */
392 		static const XMP_Uns8 HEADER_SIZE = 8;
393 		/** The size of the type */
394 		static const XMP_Uns8 TYPE_SIZE = 4;
395 
396 
397 	private:
398 		/** stores the chunk header */
399 		ChunkIdentifier mChunkId;
400 		/** Original size of chunk without the header */
401 		XMP_Uns64 mOriginalSize;
402 		/** size of chunk without the header */
403 		XMP_Uns64 mSize;
404 		/** size of the internal buffer */
405 		XMP_Uns64 mBufferSize;
406 		/** buffer for the chunk data without the header, but including the type (first 4 bytes). */
407 		XMP_Uns8* mData;
408 		/** Buffer to hold the first 4 bytes that are used for either the type or as data.
409 		 * Only used for ReadChunk and CacheChunk */
410 		ChunkMode mChunkMode;
411 
412 		/**
413 		 * Current position in stream (file). Can only be changed by moving the chunks around
414 		 * (by using an IChunkBehavior class).
415 		 * Note: Sizes are stored in chunk because it can be changed by the handler (i.e. by changing the data)
416 		 */
417 		XMP_Uns64 mOriginalOffset;
418 		/**
419 		 * New position of the chunk in the stream.
420 		 * It is initialized to MAXINT64 when there is no new offset.
421 		 * If the offset has been changed the dirty flag has to be set.
422 		 */
423 		XMP_Uns64 mOffset;
424 
425 		/**
426 		 * The dirty flag indicates that the chunk (and all parent chunks) has been modified or moved and
427 		 * that it therefore needs to be written to file.
428 		 */
429 		XMP_Bool  mDirty; // has Chunk data changed? has Chunk position changed?
430 
431 		/** The parent of this node; only the root node does not have a parent. */
432 		Chunk* mParent;
433 
434 		/** Stores the byte order for this node.
435 		 *	Note: The endianess does not change within one file */
436 		const IEndian& mEndian;
437 
438 		/** The list of child nodes. */
439 		std::vector<Chunk*>	mChildren;
440 
441 		/**
442 		 * private ctor, prevents direct invokation.
443 		 *
444 		 *	@param	endian	Endian util
445 		 */
446         Chunk( const IEndian& endian );
447 
448 		/**
449 		 * Resizes the internal byte buffer to the given size if the new size is bigger than the current one.
450 		 * If the new size is smaller, the buffer is not adjusted
451 		 */
452 		void adjustInternalBuffer( XMP_Uns64 newSize );
453 
454 		/**
455 		 * Adjusts the chunk size and the parents chunk sizes.
456 		 * - Leaf chunks always have the size of their data, inluding the 4-byte type and excluding the header.
457 		 *   Leaf chunks can have an ODD size!
458 		 * - Node chunks have the added size of all of their children, including the childrens header, but excluding it's own header.
459 		 *   IMPORTANT: When a leaf child node has an ODD size of data,
460 		 *   a pad byte is added during the writing process and the parent's size INCLUDES the pad byte.
461 		 */
462 		void adjustSize( XMP_Int64 sizeChange = 0 );
463 
464 }; // Chunk
465 
466 } // namespace
467 
468 #endif
469