package ch.werner_randelshofer.iff;/*Copyright (C) 1999 Werner Randelshoferwerner.randelshofer@mythen.chhttp://www.mythen.ch/w.randelshofer/    Permission to use this release of IFFParser is herebygranted without fee provided that the complete copyrightnotice and this permission notice appear in all copiesand in supporting documentation.*/import java.lang.Object;import java.io.*;import java.util.Hashtable;import ch.werner_randelshofer.util.*;/**Interprets IFF streams.<p><b>Design Pattern</b><li>	Interpreter	<p><b>Design Role</b><li>	Interpreter	<p><b>Responsibility</b><li>	Hide the physical representation of an IFF stream from the client.<li>	Let the client declare chunks to be visited by the visitor object.<li>	Let a visitor object traverse the IFF parse tree.<p><b>Abstract</b><p>"EA IFF 85" is the standard interchange file format on Commodore Amiga Computers.An IFF File is built up of primitive data types, local chunks and group chunks.<p>The format for primitive data types is the same as used by the Motorla MC68000processor. The high byte and high word of a number are stored first. All primitiveslarger than one byte are aligned on even byte addresses relative to the start ofthe file. Zeros should be stored in all the pad bytes. Characters and strings areusually coded according to ISO/DIS 6429.2 and ANSI X3.64-1979. <p>Data objects are built up with information blocks (or C structs) called local chunks.IFF supports the three different kinds called 'data chunk', 'property chunk','collection chunk'.<br>Data chunks contain the essential information that build up an object, say the bitmapdata of a picture or the text of a document.<br>Property chunks specify attributes for following data chunks.A property chunk essentially says "identifier=value". When designing an object, propertychunks do describe context information like the size of an image or thecolor of the text. Specifying the same property chunk more then once overrides the previoussetting.<br>Collection chunks describe a property that can occur multiple times. All occurencesof a collection chunk must be collected within the scope of the current group chunk.<p>Group chunks are full fledged selfcontained data objects. A FORM Group standsfor a single data object where as a CAT Group stands for an untyped group of dataobjects. A LIST defines a group very much like CAT but also gives a scope forshared properties (stored in PROP Groups).<p>For more information refer to "Amiga ROM Kernel Reference Manual, Devices, Third Edition, Addison Wesley".<p><b>Grammar for IFF streams</b><pre>IFFFile     ::= 'FORM' FormGroup | 'CAT ' CatGroup | 'LIST' ListGroup<br>GroupChunk  ::= FormGroup | CatGroup | ListGroup | PropGroup | IFFStreamFormGroup   ::= size formType [ chunkID LocalChunk [pad] | 'FORM' FormGroup [pad] | 'LIST ' ListGroup  [pad] | 'CAT ' CatGroup [pad] ]...CatGroup    ::= size contentType [ 'FORM' FormGroup [pad] | 'LIST ' ListGroup  [pad] | 'CAT ' CatGroup [pad] ]...ListGroup   ::= size formType [ 'PROP' PropGroup [pad] ]... [ 'FORM' FormGroup [pad] | 'LIST' CatGroup  [pad] | 'CAT ' ListGroup [pad] } PropGroup   ::= size formType [ chunkID PropertyChunk [pad] ]...<br>LocalChunk      ::= DataChunk | CollectionChunk | PropertyChunkDataChunk       ::= size { struct }CollectionChunk ::= size { struct }PropertyChunk   ::= size { struct }<br>size        ::= ULONGformType    ::= ULONGcontentType ::= ULONGchunkID     ::= ULONGpad         ::= (BYTE)0struct      ::= any C language struct built with primitive data types.</pre><p><b>Examples</b><p><b>Traversing the raw structure of an IFF file</b><p>To traverse the file structure you must first set up an IFFVisitor object that doessomething useful at each call to the visit method. Then create an instance ofIFFParser and invoke the #interpret method.<pre>class IFFRawTraversal.	{.	static class Visitor.	implements IFFVisitor.		{.		...implement the visitor interface here....		}..	public static void main(String[] args).		{.		try	{.			Visitor visitor = new Visitor();.			FileInputStream stream = new FileInputStream(args[0]);.			IFFParser p = new IFFParser();.			p.interpret(stream,visitor);.			stream.close();.			}.		catch (IOException e) { System.out.println(e); }.		catch (InterpreterException e)  { System.out.println(e); }.		catch (AbortedException e)  { System.out.println(e); }.		}.	}</pre><p><b>Traversing the IFF file and interpreting its content.</b><p>Since IFF files are not completely self describing (there is no information thathelps differentiate between data chunks, property chunks and collection chunks) areader must set up the interpreter with some contextual information before startingthe interpreter.<p>Once at least one chunk has been declared, the interpreter will only call thevisitor for occurences of the declared group chunks and data chunks. The propertychunks and the collection chunks can be obtained from the current group chunkby calling #getProperty or #getCollection.<br>Note: All information the visitor can obtain during interpretation is onlyvalid during the actual #visit... call. Dont try to get information about propertiesor collections for chunks that the visitor is not visiting right now.<pre>class InterpretingAnILBMFile.	{.	static class Visitor.	implements IFFVisitor.		{.		....		}..	public static void main(String[] args).		{.		try	{.			Visitor visitor = new Visitor();.			FileInputStream stream = new FileInputStream(args[0]);.			IFFParser p = new IFFParser();.			p.declareGroupChunk('FORM','ILBM');.			p.declarePropertyChunk('ILBM','BMHD');.			p.declarePropertyChunk('ILBM','CMAP');.			p.declareCollectionChunk('ILBM','CRNG');.			p.declareDataChunk('ILBM','BODY');.			p.interpret(stream,visitor);.			stream.close();.			}.		catch (IOException e) { System.out.println(e); }.		catch (InterpreterException e)  { System.out.println(e); }.		catch (AbortedException e)  { System.out.println(e); }.		}.	}</pre>@see	IFFContext@see	IFFVisitor	@author	Werner Randelshofer, Staldenmattweg 2, CH-6405 Immensee, Switzerland@version	1999-01-02	Reworked.<br>history	1997-08-30	Created.*/public class IFFParserextends Object	{	/* ---- instance variables ---- */	/** Reference to the input stream. */	private MC68000InputStream in_;	/** Contains contextual information to the content of the stream. */	private IFFContext context_;		/* ---- constructors ---- */	/**	Constructs a new IFF parser.	*/	public IFFParser()		{		context_ = new IFFContext();		}			/* ---- accessor methods ---- */		/** Set the data context object. */	protected void setContext(IFFContext context)		{ context_ = context; }	/** Get the data context object. */	protected IFFContext getContext()		{ return context_; }			/* ---- action methods ---- */	/**	Interprets the IFFFileExpression located at the	current position of the indicated InputStream.	Lets the visitor traverse the IFF parse tree during	interpretation.	<p>Pre condition	<li>	Data-, property- and collection chunks must have been		declared prior to this call.	<li>	When the client never declared chunks, then all local		chunks will be interpreted as data chunks.	<li>	The stream must be positioned at the beginning of the		IFFFileExpression.	<p>Post condition	<li>	When no exception was thrown then the stream is positioned		after the IFFFileExpression.	 	<p>Obligation		The visitor may throw an ParseException or an		AbortException during tree traversal.	@exception ParseException		Is thrown when an interpretation error occured.		The stream is positioned where the error occured.	@exception	AbortException					Is thrown when the visitor decided to abort the		interpretation.	*/	public void parse(InputStream in, IFFVisitor v)	throws ParseException, AbortException, IOException		{		in_ = new MC68000InputStream(new BufferedInputStream(in));		context_.setVisitor(v);		parseFile();		}		/**	Parses an IFF-85 file.	<pre>		IFF = 'FORM' FormGroup | 'CAT ' CatGroup | 'LIST' ListGroup	</pre>	*/			private void parseFile()	throws ParseException, AbortException, IOException		{		int id = in_.readLONG();		switch(id)			{			case IFFContext.FORM_ID :				parseFORM(null);				break;			case IFFContext.CAT_ID :				parseCAT(null);				break;			case IFFContext.LIST_ID :				parseLIST(null);				break;			default :				throw new ParseException("IFF-85 files must start with 'FORM', 'CAT ', or 'LIST'. But not with: '" + IFFContext.idToString(id) + "'");			}		}	/**	Parses a FORM group chunk.	<pre>		FormGroup = size FormType { 'FORM' FormGroup [Pad] |	                            'CAT ' CatGroup  [Pad] |	                            'LIST' ListGroup [Pad] | 	                            ChunkID LocalChunk [Pad] }	</pre>		*/			private void parseFORM(Hashtable props)	throws ParseException, AbortException, IOException		{		long size = in_.readULONG();		long scan = in_.getScan();		int type = in_.readLONG();		if (! IFFContext.isFormType(type) )				{ throw new ParseException("Invalid Form Type: " + IFFContext.idToString(type)); }		IFFChunk propGroup = props == null ? null : (IFFChunk)props.get(new Integer(type));		IFFChunk chunk = new IFFChunk(type,IFFContext.FORM_ID,size,scan,propGroup);				if (context_.isGroupChunk(chunk))			{ context_.getVisitor().enterGroup(chunk); }		long finish = scan + size;				while (in_.getScan() < finish)			{			int id = in_.readLONG();			switch(id)				{				case IFFContext.FORM_ID :					parseFORM(props);					break;				case IFFContext.CAT_ID :					parseCAT(props);					break;				case IFFContext.LIST_ID :					parseLIST(props);					break;				default :					if (IFFContext.isLocalChunkID(id))						{ parseLocalChunk(chunk,id,context_); }					else						{							throw new ParseException("Invalid IFFChunk within FORM: " + IFFContext.idToString(id));						}				}			in_.align();			}				if (context_.isGroupChunk(chunk))			{ context_.getVisitor().leaveGroup(chunk); }	}	/**	Parses a CAT group chunk.	<pre>		CatGroup = size ContentsType { 'FORM' FormGroup [Pad] |	                               'CAT ' CatGroup  [Pad] |	                               'LIST' ListGroup [Pad] }	</pre>		*/			private void parseCAT(Hashtable props)	throws ParseException, AbortException, IOException		{		long size = in_.readULONG();		long scan = in_.getScan();		int type = in_.readLONG();				if (! IFFContext.isContentType(type) )			{ throw new ParseException("Invalid Content Type: " + IFFContext.idToString(type)); }		IFFChunk propGroup = props == null ? null : (IFFChunk)props.get(new Integer(type));		IFFChunk chunk = new IFFChunk(type,IFFContext.CAT_ID,size,scan,propGroup);		if (context_.isGroupChunk(chunk))			{ context_.getVisitor().enterGroup(chunk); }				long finish = scan+size;		while (in_.getScan() < finish)			{			int id = in_.readLONG();						switch (id)				{				case IFFContext.FORM_ID :					parseFORM(props);					break;				case IFFContext.CAT_ID :					parseCAT(props);					break;				case IFFContext.LIST_ID :					parseLIST(props);					break;				default :					throw new ParseException("Invalid IFFChunk within CAT: " + IFFContext.idToString(id));				}			in_.align();		}				if (context_.isGroupChunk(chunk))			{ context_.getVisitor().leaveGroup(chunk); }		}		/**	Parses a LIST group chunk.	<pre>		ListGroup = size FormType { 'PROP' PropGroup [Pad] } { 'FORM' FormGroup [Pad] |	                                                       'CAT ' CatGroup  [Pad] |	                                                       'LIST' ListGroup [Pad] } 	</pre>		*/			private void parseLIST(Hashtable props)	throws ParseException, AbortException, IOException		{		long size = in_.readULONG();		long scan = in_.getScan();		int type = in_.readLONG();				if (! IFFContext.isFormType(type))			{ throw new ParseException("Invalid Form Type: " + IFFContext.idToString(type)); }				IFFChunk propGroup = props == null ? null : (IFFChunk)props.get(new Integer(type));		IFFChunk chunk = new IFFChunk(type,IFFContext.LIST_ID,size,scan,propGroup);				if (context_.isGroupChunk(chunk))			{ context_.getVisitor().enterGroup(chunk); }		props = new Hashtable();		long finish = scan + size;		while (in_.getScan() < finish)			{			int id = in_.readLONG();						switch(id)				{				case IFFContext.FORM_ID :					parseFORM(props);					break;				case IFFContext.CAT_ID :					parseCAT(props);					break;				case IFFContext.LIST_ID :					parseLIST(props);					break;				case IFFContext.PROP_ID :					IFFChunk prop = parsePROP();					props.put(new Integer(prop.getType()),prop);					break;				default :					if (IFFContext.isLocalChunkID(id))						{ parseLocalChunk(chunk,id,context_); }					else						{						throw new ParseException("Invalid IFFChunk ID within LIST: " + IFFContext.idToString(id));						}				}			in_.align();			}				if (context_.isGroupChunk(chunk))			{ context_.getVisitor().leaveGroup(chunk); }		}	/**	Parses a PROP group chunk.	<pre>		PropGroup   ::= size formType [ chunkID PropertyChunk [pad] ]...	</pre>		*/			private IFFChunk parsePROP()	throws ParseException, AbortException, IOException		{		long size = in_.readULONG();		long scan = in_.getScan();		int type = in_.readLONG();				if (! IFFContext.isFormType(type))			{ throw new ParseException("Invalid Form Type: " + IFFContext.idToString(type)); }		IFFChunk chunk = new IFFChunk(type, IFFContext.PROP_ID, size, scan);		if (context_.isGroupChunk(chunk))			{ context_.getVisitor().enterGroup(chunk); }				long finish = scan+size;		while(in_.getScan() < finish)			{			int id = in_.readLONG();						if (IFFContext.isLocalChunkID(id))				{ parseLocalChunk(chunk,id,context_); }			else				{				throw new ParseException("Invalid IFFChunk ID within PROP: " + IFFContext.idToString(id));				}			in_.align();			}				if (context_.isGroupChunk(chunk))			{ context_.getVisitor().leaveGroup(chunk); }		return chunk;		}	/**	Parses a local chunk.	<pre>		LocalChunk  ::= size { DataChunk | PropertyChunk | CollectionChunk }	DataChunk = PropertyChunk = CollectionChunk ::= { byte }...*size	</pre>		*/	private void parseLocalChunk(IFFChunk parent, int id, IFFContext context_)	throws ParseException, AbortException, IOException		{		long size = in_.readULONG();		long scan = in_.getScan();				IFFChunk chunk = new IFFChunk(parent.getType(), id, size, scan);				if (context_.isDataChunk(chunk))			{			byte[] data = new byte[(int)size];			in_.read(data,0,(int)size);			chunk.setData(data);			context_.getVisitor().visitChunk(parent,chunk);			}		else if (context_.isPropertyChunk(chunk))			{			byte[] data = new byte[(int)size];			in_.read(data,0,(int)size);			chunk.setData(data);			parent.putPropertyChunk(chunk);			}		else if (context_.isCollectionChunk(chunk))			{			byte[] data = new byte[(int)size];			in_.read(data,0,(int)size);			chunk.setData(data);			parent.addCollectionChunk(chunk);			}		else			{			in_.skip((int)size);			}		}	/**	Declares a data chunk.		<p>Pre condition	<li>	The chunk must not have already been declared as of a		different type.	<li>	Declarations may not be done during interpretation		of an IFFFileExpression.			<p>Post condition	 <li>	Data chunk declared	 			@param	type		Type of the chunk. Must be formulated as a TypeID conforming		to IFFContext#isFormType.	@param	id		ID of the chunk. Must be formulated as a ChunkID conforming		to IFFContext#isLocalChunkID.	*/	public void declareDataChunk(int type, int id)		{		IFFChunk chunk = new IFFChunk(type,id);		context_.declareDataChunk(chunk);		}	/**	Convenience method.	*/	public void declareDataChunk(String type, String id)		{		declareDataChunk(IFFContext.stringToID(type),IFFContext.stringToID(id));		}			/**	Declares a FORM group chunk.			<p>Pre condition	<li>	The chunk must not have already been declared as of a		different type.	<li>	Declarations may not be done during interpretation		of an IFFFileExpression.			<p>Post condition	 <li>	Group chunk declared	 			@param	type		Type of the chunk. Must be formulated as a TypeID conforming		to IFFContext#isFormType.	@param	id		ID of the chunk. Must be formulated as a ChunkID conforming		to IFFContext#isContentsType.	*/	public void declareGroupChunk(int type, int id)		{		IFFChunk chunk = new IFFChunk(type,id);		context_.declareGroupChunk(chunk);		}	/**	Convenience method.	*/	public void declareGroupChunk(String type, String id)		{		declareGroupChunk(IFFContext.stringToID(type),IFFContext.stringToID(id));		}	/**	Declares a property chunk.			<p>Pre condition	<li>	The chunk must not have already been declared as of a		different type.	<li>	Declarations may not be done during interpretation		of an IFFFileExpression.			<p>Post condition	 <li>	Group chunk declared	 			@param	type		Type of the chunk. Must be formulated as a TypeID conforming		to IFFContext#isFormType.	@param	id		ID of the chunk. Must be formulated as a ChunkID conforming		to IFFContext#isLocalChunkID.	*/	public void declarePropertyChunk(int type, int id)		{		IFFChunk chunk = new IFFChunk(type,id);		context_.declarePropertyChunk(chunk);		}	/**	Convenience method.	*/	public void declarePropertyChunk(String type, String id)		{		declarePropertyChunk(IFFContext.stringToID(type),IFFContext.stringToID(id));		}			/**	Declares a collection chunk.	<p>Pre condition	<li>	The chunk must not have already been declared as of a		different type.	<li>	Declarations may not be done during interpretation		of an IFFFileExpression.			<p>Post condition	 <li>	Group chunk declared	 			@param	type		Type of the chunk. Must be formulated as a TypeID conforming		to IFFContext#isFormType.	@param	id		ID of the chunk. Must be formulated as a ChunkID conforming		to IFFContext#isLocalChunkID.	*/	public void declareCollectionChunk(int type, int id)		{		IFFChunk chunk = new IFFChunk(type,id);		context_.declareCollectionChunk(chunk);		}	/**	Convenience method.	*/	public void declareCollectionChunk(String type, String id)		{		declareCollectionChunk(IFFContext.stringToID(type),IFFContext.stringToID(id));		}	}