/* ###
 * IP: GHIDRA
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/**
 * 
 */
package ghidra.app.util.opinion;

import static ghidra.program.model.pcode.AttributeId.*;

import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.util.*;

import org.xml.sax.*;

import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.DecompileDebugXmlLoader.DecompileDebugProgramInfo;
import ghidra.formats.gfilesystem.FSRL;
import ghidra.formats.gfilesystem.FileSystemService;
import ghidra.framework.store.LockException;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressOverflowException;
import ghidra.program.model.data.DataType;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.*;
import ghidra.program.model.pcode.AddressXML;
import ghidra.program.model.symbol.*;
import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
import ghidra.util.xml.SpecXmlUtils;
import ghidra.xml.*;

/**
 * Main manager for handling the coordination of the parsing of the XML and loading of the program 
 * details into Ghidra.
 */
public class DecompileDebugFormatManager {

	private File file;
	private DecompileDebugProgramInfo progInfo;
	private Map<Long, Namespace> scopeMap;

	/**
	 * Constructs a new program Decompiler Debug XML manager using the provided file.
	 * The file should be an XML file generated by the Ghidra Decompiler as a debug file.
	 * 
	 * @param file generated XML Decompile Debug file
	 */
	public DecompileDebugFormatManager(File file) {
		this.file = file;
	}

	/**
	 * Constructs a new program Decompiler Debug XML manager using the provided ByteProvider.
	 * <p>
	 * If {@link ByteProvider} has a {@link FSRL} and it is a simple local filepath,
	 * convert that to a normal local java.io.File instance instead of using the
	 * {@link ByteProvider}'s File property which is probably located in the
	 * {@link FileSystemService} filecache directory, which will break the ability
	 * to find the *.bytes file associated with this .xml file.
	 * 
	 * @param provider The provider
	 */
	public DecompileDebugFormatManager(ByteProvider provider) {
		this.file = (provider.getFSRL() != null && provider.getFSRL().getNestingDepth() == 1)
				? new File(provider.getFSRL().getPath())
				: provider.getFile();
	}

	/**
	 * Initial parsing of the XML file to obtain the binary image info with load specs.
	 * 
	 * @return DecompileDebugProgramInfo binary image / load spec details 
	 * @throws SAXException If there was a problem parsing the XML
	 * @throws IOException If there was an IO-related error
	 */
	public DecompileDebugProgramInfo getProgramInfo() throws SAXException, IOException {
		XmlPullParser parser =
			XmlPullParserFactory.create(file, new MyErrorHandler(new MessageLog()), false);

		String loadSpecString = "";
		String offset = "";

		while (parser.hasNext()) {
			XmlElement element = parser.next();
			if (element.isStart("binaryimage")) {
				loadSpecString = element.getAttribute("arch");
			}
			else if (element.isStart("bytechunk")) {
				offset = element.getAttribute(ATTRIB_OFFSET.name());
				break;
			}

		}
		String compilerString =
			loadSpecString.substring(loadSpecString.lastIndexOf(':') + 1, loadSpecString.length());
		loadSpecString = loadSpecString.substring(0, loadSpecString.lastIndexOf(':'));

		progInfo = new DecompileDebugProgramInfo(offset, compilerString, loadSpecString);
		parser.dispose();
		return progInfo;
	}

	/**
	 * Perform the parsing from the underlying decompile debug XML file and populates the program fields.
	 * See @DecompileDebug.java for reference on the generation of the XML file.
	 * Tags currently supported/expected: 
	 * <ul>
	 * <li>{@code <binaryimage>}</li>
	 * <li>{@code <coretypes>}</li>
	 * <li>{@code <typegrp>}</li>
	 * <li>{@code <save_state>}</li>
	 * <li>{@code <db>}</li>
	 * <li>{@code <commentdb>}</li>
	 * <li>{@code <stringmanage>}</li>
	 *</ul>
	 *
	 * NOTE: the following subtree tags are not yet supported:
	 * <ul>
	 * <li>{@code <context_points>}</li>
	 * <li>{@code <optionslist>}</li>
	 * </ul>
	 *
	 * @param prog created program 
	 * @param monitor task monitor
	 * @param programName  name of program
	 * @return MessageLog
	 * @throws LoadException If there is a parsing issue with the XML file. 
	 */
	public MessageLog read(Program prog, TaskMonitor monitor, String programName)
			throws LoadException {
		XmlMessageLog log = new XmlMessageLog();
		MyErrorHandler errorHandler = new MyErrorHandler(log);
		scopeMap = new TreeMap<Long, Namespace>();
		scopeMap.put((long) 0, prog.getGlobalNamespace());
		int transactionId = prog.startTransaction("Loading");
		XmlPullParser parser = null;
		try {
			parser = XmlPullParserFactory.create(file, errorHandler, false);
			log.setParser(parser);
			monitor.setMessage("Beginning Load");
			log.appendMsg("Beginning Load");
			XmlElement startSavefileElement = parser.start("xml_savefile");
			XmlElement saveStateElement = null;
			DecompileDebugDataTypeManager dataTypeManager =
				new DecompileDebugDataTypeManager(monitor, prog);
			while (parser.peek().isStart() && !monitor.isCancelled()) {
				XmlElement element = null;
				String name = parser.peek().getName();
				switch (name) {

					case "binaryimage":
						element = parser.start("binaryimage");
						handleBinaryImageElements(parser, monitor, prog, programName, log);
						parser.end(element);
						break;

					case "coretypes":
						monitor.setMessage("Processing Core Data Types");
						element = parser.start("coretypes");
						parseDataTypes(parser, monitor, prog, dataTypeManager, log);
						parser.end(element);
						break;

					case "typegrp":
						monitor.setMessage("Processing Composite Data Types");
						element = parser.start("typegrp");
						parseDataTypes(parser, monitor, prog, dataTypeManager, log);
						parser.end(element);
						break;

					case "save_state":
						saveStateElement = parser.start("save_state"); // wrapper tag holds all the program details aside from the binaryimage/memory
						break;

					case "db":
						element = parser.start("db");
						handleDBElements(parser, monitor, prog, dataTypeManager, programName, log);
						parser.end(element);
						break;

					case "commentdb":
						element = parser.start("commentdb");
						parseComments(parser, monitor, prog, log);
						parser.end(element);
						break;

					case "stringmanage":
						element = parser.start("stringmanage");
						parseStrings(parser, monitor, prog, log);
						parser.end(element);
						break;

					case "context_points":
						element = parser.start("context_points");
						parseContextPoints(parser, monitor, prog, log);
						parser.end(element);
						break;

					default:
						log.appendMsg(parser.getLineNumber(), "Level " + parser.getCurrentLevel() +
							" tag not currently supported: " + name);
						parser.discardSubTree(name);
						break;
				}
			}

			parser.end(saveStateElement);
			parser.end(startSavefileElement);
		}

		catch (SAXException | IOException e) {
			log.appendException(e);
			throw new LoadException("File read error.");
		}

		finally {
			monitor.setMessage("Finished import");
			log.appendMsg("Finished import");
			prog.endTransaction(transactionId, true);
			parser.dispose();
		}

		return log;
	}

	/**
	 * Parse elements in the {@code <db>} subtree. Elements we currently handle include:
	 * <ul>
	 * <li>{@code <scope>}</li>
	 * </ul>
	 * 
	 * @param parser XmlPullParser
	 * @param monitor TaskMonitor
	 * @param prog Program
	 */
	private void handleDBElements(XmlPullParser parser, TaskMonitor monitor, Program prog,
			DecompileDebugDataTypeManager dataTypeManager, String programName,
			XmlMessageLog log) {

		while (parser.peek().isStart() && !monitor.isCancelled()) {
			if (parser.peek().getName().equals("scope")) {
				XmlElement scopeElement = parser.start("scope");
				Long scopeId =
					SpecXmlUtils.decodeLong(scopeElement.getAttribute(ATTRIB_ID.name()));
				String namespaceName = scopeElement.getAttribute(ATTRIB_NAME.name());

				long parentId = 0;
				if (parser.peek().getName().equals("parent")) {
					XmlElement parentElement = parser.start("parent");
					parentId =
						SpecXmlUtils.decodeLong(parentElement.getAttribute(ATTRIB_ID.name()));
					parser.end(parentElement);
				}

				// scopeMap is initialized with the global namespace, the first <scope> tag will be the global one if it doesn't have a parent tag 
				Namespace namespace = scopeMap.get(scopeId);

				if (namespace == null) {
					Namespace parentNamespace = scopeMap.get(parentId);
					try {
						namespace = prog.getSymbolTable()
								.createNameSpace(parentNamespace, namespaceName,
									SourceType.IMPORTED);
						scopeMap.put(scopeId, namespace);
					}
					catch (DuplicateNameException | InvalidInputException e) {
						log.appendException(e);
					}
				}

				handleScopeSubtree(namespace, parser, monitor, prog, dataTypeManager, programName,
					log);
				parser.end(scopeElement);

			}
			else {
				log.appendMsg(parser.getLineNumber(), "Level " + parser.getCurrentLevel() +
					" tag not currently supported: " + parser.peek().getName());
				parser.discardSubTree();
			}
		}

	}

	/**
	 * Parse element subtrees within the scope tag. 
	 * <p>
	 * NOTE: The scope tag must be parsed prior to a call to this message with the value 
	 * of the namespace object sent as the first parameter. 
	 * <p>
	 * NOTE: it is expected that the wrapper {@code <symbollist>} tag is being used around the 
	 * collection of {@code <mapsymp>} tags.
	 * 
	 * @param namespace Namespace
	 * @param parser XmlPullParser
	 * @param monitor TaskMonitor
	 * @param prog Program
	 * @param log XmlMessageLog
	 */
	private void handleScopeSubtree(Namespace namespace, XmlPullParser parser, TaskMonitor monitor,
			Program prog, DecompileDebugDataTypeManager dataTypeManager, String programName,
			XmlMessageLog log) {
		XmlElement symbollistElement = null;
		DecompileDebugFunctionManager functionManager =
			new DecompileDebugFunctionManager(prog, monitor, dataTypeManager);

		while (parser.peek().isStart() && !monitor.isCancelled()) {
			String name = parser.peek().getName();
			switch (name) {
				case "symbollist": // this is a wrapper tag for <mapsym> tags 
					symbollistElement = parser.start("symbollist");
					break;
				case "mapsym":
					XmlElement mapsymTypeElement = parser.start("mapsym");
					monitor.setMessage("Processing Symbols");
					String symbolType = parser.peek().getName();
					if (symbolType.equals("function")) {
						functionManager.parseFunctionSignature(parser, scopeMap, log);
						while (parser.peek().isStart()) {
							log.appendMsg(parser.getLineNumber(),
								"Level " + parser.getCurrentLevel() +
									" tag not currently supported: " + name);
							parser.discardSubTree(); // any extra tags after the function tag before mapsym can be thrown away for now
						}
					}
					else if (symbolType.equals("labelsym")) {
						parseLabelSymbol(prog, parser, log);
					}
					else if (symbolType.equals("symbol")) {
						try {
							parseSymbol(prog, parser, dataTypeManager, namespace, programName,
								monitor, log);
						}
						catch (LoadException e) {
							log.appendException(e);
						}
					}
					parser.end(mapsymTypeElement);
					break;
				default:
					log.appendMsg(parser.getLineNumber(), "Level " + parser.getCurrentLevel() +
						" tag not currently supported: " + name);
					parser.discardSubTree();
			}
		}
		parser.end(symbollistElement);
	}

	/**
	 * Handle the {@code <binaryimage>} tag and subtree which includes the {@code <bytechunk>} 
	 * tag(s).
	 * 
	 * @param parser XmlPullparser
	 * @param monitor TaskMonitor
	 * @param prog Program
	 * @param programName String
	 * @param log MessageLog 
	 */
	private void handleBinaryImageElements(XmlPullParser parser, TaskMonitor monitor, Program prog,
			String programName, XmlMessageLog log) {
		monitor.setMessage("Processing binary image");
		while (parser.peek().isStart() && !monitor.isCancelled()) {
			if (parser.peek().getName().equals("bytechunk")) {
				monitor.setMessage("Processing Byte Chunk(s)");
				DecompileDebugByteManager byteMngr =
					new DecompileDebugByteManager(monitor, prog, programName);
				byteMngr.parse(parser, log);
			}
			else {
				log.appendMsg(parser.getLineNumber(), "Level " + parser.getCurrentLevel() +
					" tag not currently supported: " + parser.peek().getName());
				parser.discardSubTree();
			}
		}
	}

	/**
	 * Handle generation of labels from the {@code <labelsym>} tag
	 * @param prog program 
	 * @param parser XmlPullParser
	 * @param log XmlMessageLog
	 */
	private void parseLabelSymbol(Program prog, XmlPullParser parser, XmlMessageLog log) {
		XmlElement symbolElement = parser.start("labelsym");
		String symbolName = symbolElement.getAttribute(ATTRIB_NAME.name());
		parser.end(symbolElement);
		XmlElement addrElement = parser.start("addr");
		Address symbolAddr;
		try {
			symbolAddr =
				AddressXML.restoreXml(addrElement, prog.getCompilerSpec()).getFirstAddress();
			SymbolTable st = prog.getSymbolTable();
			Symbol createdSymbol =
				st.createLabel(symbolAddr, symbolName, SourceType.IMPORTED);
			createdSymbol.setPrimary();

			parser.end(addrElement);
			while (parser.peek().isStart()) {
				log.appendMsg(parser.getLineNumber(), "Level " + parser.getCurrentLevel() +
					" tag not currently supported: " + parser.peek().getName());
				parser.discardSubTree();
			}
		}
		catch (XmlParseException | InvalidInputException e) {
			log.appendException(e);
		}

	}

	/**
	 * Parse {@code <symbol>} tag under the {@code <mapsym>} tag -- meaning, they are outside of a 
	 * function, most likely these are data references.
	 * <p>
	 * NOTE: We are currently not pulling the bytes for referenced functions or data, as a result
	 * we need to generate an initialized memory block for data references to avoid errors 
	 * in the Listing pane. 
	 * 
	 * @param prog program
	 * @param parser XmlPullParser
	 * @param dataTypeManager DecompileDebugDataTypeManager
	 * @param namespace Namespace
	 * @param log XmlMessageLog
	 * 
	 * @throws LoadException Memory allocation will block program load.
	 */
	private void parseSymbol(Program prog, XmlPullParser parser,
			DecompileDebugDataTypeManager dataTypeManager, Namespace namespace, String programName,
			TaskMonitor monitor, XmlMessageLog log) throws LoadException {
		XmlElement symbolElement = parser.start("symbol");
		String symbolName = symbolElement.getAttribute(ATTRIB_NAME.name());
		DataType dt = dataTypeManager.parseDataTypeTag(parser, log);
		Boolean readOnly = symbolElement.hasAttribute(ATTRIB_READONLY.name()); // readOnly is only present if it's true
		parser.end(symbolElement);
		XmlElement addrElement = parser.start("addr");
		Address symbolAddr = null; // make available for display in the error dialog below

		try {
			AddressXML xmlAddr = AddressXML.restoreXml(addrElement, prog.getCompilerSpec());
			symbolAddr = xmlAddr.getFirstAddress();
			Symbol createdSymbol =
				SymbolUtilities.createPreferredLabelOrFunctionSymbol(prog,
					symbolAddr, namespace, symbolName, SourceType.IMPORTED);
			createdSymbol.setPrimary();

			Memory memory = prog.getMemory();
			Address end = symbolAddr.addNoWrap(xmlAddr.getSize() - 1);
			// check to see if the data element would overlap existing blocks 
			if (!memory.contains(symbolAddr, end)) {
				MemoryBlock generatedBlock = memory.createInitializedBlock(programName, symbolAddr,
					xmlAddr.getSize(), (byte) 0,
					monitor, false);
				generatedBlock.setWrite(!readOnly); // if readOnly is true, write should be false
				prog.getListing().createData(symbolAddr, dt, (int) xmlAddr.getSize());
			}

			else {   // just generate the symbol 
				prog.getListing().createData(symbolAddr, dt, (int) xmlAddr.getSize());
			}
		}

		catch (MemoryConflictException mce) {
			log.appendMsg("Attempted to allocate overlapping memory block for data: " +
				dt.getDisplayName() + " at address: " + symbolAddr);
			log.appendException(mce);
		}

		catch (LockException | IllegalArgumentException
				| AddressOverflowException | CancelledException | InvalidInputException
				| XmlParseException | CodeUnitInsertionException e) {
			log.appendException(e);
		}

		parser.end(addrElement);
		while (parser.peek().isStart()) {
			log.appendMsg(parser.getLineNumber(), "Level " + parser.getCurrentLevel() +
				" tag not currently supported: " + parser.peek().getName());
			parser.discardSubTree(); // skip rangelist tag and any others not yet handled 
		}
	}

	/**
	 * Handle parsing and loading of comments.
	 * @param parser XmlPullParser
	 * @param monitor TaskMonitor
	 * @param prog Ghidra Program
	 * @param log XmlMessageLog
	 */
	private void parseComments(XmlPullParser parser, TaskMonitor monitor, Program prog,
			XmlMessageLog log) {
		while (parser.peek().isStart("comment") && !monitor.isCancelled()) {
			parseAndAddComment(parser, prog, log);
		}
	}

	/**
	 * Parse comments from the {@code <commentdb>} tag and add to listing via setupComments method. 
	 * <p>
	 * Note: {@code <commentdb>} has two {@code <addr>} tags.  The first is the function address, 
	 * and the second is the {@link CodeUnit} address where the comment should be placed. Discard 
	 * the first address.   
	 * 
	 * @param parser XmlPullParser
	 * @param prog Program
	 * @param log XmlMessageLog
	 */
	private void parseAndAddComment(XmlPullParser parser, Program prog, XmlMessageLog log) {
		XmlElement commentElement = parser.start("comment");
		String commentType = commentElement.getAttribute(ATTRIB_TYPE.name());
		CommentType decodedType = decodeCommentType(commentType);
		parser.discardSubTree("addr"); // this is the address of the function
		XmlElement addrElement = parser.start("addr"); // this is the CodeUnit address where the comment goes

		try {
			Address commentAddr =
				AddressXML.restoreXml(addrElement, prog.getCompilerSpec()).getFirstAddress();
			parser.end(addrElement);

			parser.start("text");
			String commentText = parser.end().getText();
			setupComments(decodedType, commentAddr, commentText, prog);
			parser.end(commentElement);
		}

		catch (XmlParseException e) {
			log.appendException(e);
		}
	}

	/**
	 * Setup comments from the <comment> tag. Follows the comment type constants found in
	 * @CodeUnit.java. 
	 * 
	 * @param decodedType int
	 * @param commentAddr Address
	 * @param commentText String
	 * @param prog Program 
	 */
	private void setupComments(CommentType decodedType, Address commentAddr, String commentText,
			Program prog) {

		CodeUnit cu = prog.getListing().getCodeUnitAt(commentAddr);
		cu.setComment(decodedType, commentText);
	}

	/**
	 * See @DecompileCallback.java for the encoding of the comments by @DecompileDebug.java. 
	 * The method {@code <encodeCommentsType>} encodes the comment types found in @CodeUnit.java 
	 * in 4 alternative labels:
	 * <ul>
	 * <li>{@code CodeUnit.EOL_COMMENT = "user1"}</li>
	 * <li>{@code CodeUnit.PRE_COMMENT = "user2"}</li>
	 * <li>{@code CodeUnit.POST_COMMENT = "user3"}</li>
	 * <li>{@code CodeUnit.PLATE_COMMENT = "header"}</li>
	 * </ul>
	 * In order to generate comments using @CodeUnit.java, we will need to re-encode the user[1-3] 
	 * and header type labels from the DecompileDebug XML back into the CodeUnit constant values of:
	 * 0-3 (respectively).
	 * 
	 * @param typeName String label from DecompileDebug.java and DecompileCallback.java 
	 * 
	 * @return CodeUnit comment type (0-3) 
	 */
	private CommentType decodeCommentType(String typeName) {
		return switch (typeName) {
			case "user1" -> CommentType.EOL;
			case "user2" -> CommentType.PRE;
			case "user3" -> CommentType.POST;
			case "header" -> CommentType.PLATE;
			default -> CommentType.valueOf("");
		};
	}

	/**
	 * Loop through the {@code <type>} tags in the {@code <coretypes>} subtree
	 * 
	 * @param parser XmlPullParser
	 * @param prog built program
	 * @param monitor TaskMonitor
	 * @param dataTypeManager Program's DataTypeManager for parsing and managing the DataTypeManager Map 
	 * @param log XmlMessageLog
	 */
	private void parseDataTypes(XmlPullParser parser, TaskMonitor monitor, Program prog,
			DecompileDebugDataTypeManager dataTypeManager, XmlMessageLog log) {

		while (parser.peek().isStart() && !monitor.isCancelled()) {
			dataTypeManager.parseDataTypeTag(parser, log);
		}
	}

	/**
	 * Parse the {@code <stringmanage>} subtree 
	 * 
	 * @param parser XmlPullParser
	 * @param monitor TaskMonitor
	 * @param prog Program
	 * @param log XmlMessageLog
	 */
	private void parseStrings(XmlPullParser parser, TaskMonitor monitor, Program prog,
			XmlMessageLog log) {
		while (parser.peek().isStart("string") && !monitor.isCancelled()) {
			parseAndAddStrings(parser, monitor, prog, log);
		}
	}

	/**
	 * Parse the {@code <string>} tag and insert into the program
	 * 
	 * @param parser XmlPullParser 
	 * @param monitor TaskMonitor
	 * @param prog Program
	 * @param log XmlMessageLog
	 */
	private void parseAndAddStrings(XmlPullParser parser, TaskMonitor monitor, Program prog,
			XmlMessageLog log) {
		XmlElement stringElement = parser.start("string");
		XmlElement addrElement = parser.start("addr");
		try {
			Address stringAddr =
				AddressXML.restoreXml(addrElement, prog.getCompilerSpec()).getFirstAddress();
			parser.end(addrElement);

			parser.start("bytes");
			String hexString = parser.end().getText().trim().replaceAll("\n", "");
			hexString = hexString.replaceAll(" ", "");
			byte[] rawBytes = HexFormat.of().parseHex(hexString);
			Memory memory = prog.getMemory();
			memory.setBytes(stringAddr, rawBytes);
		}
		catch (XmlParseException | IllegalArgumentException | MemoryAccessException e) {
			log.appendException(e);
		}
		parser.end(stringElement);
	}

	/**
	 * Handle the parsing of the context pointset inside of the {@code <contextpointset>} subtree
	 * 
	 * @param parser XmlPullParser
	 * @param monitor TaskMonitor
	 * @param prog Program
	 * @param log XmlMessageLog
	 */
	private void parseContextPoints(XmlPullParser parser, TaskMonitor monitor, Program prog,
			XmlMessageLog log) {
		while (parser.peek().isStart("context_pointset")) {
			XmlElement contextElement = parser.start("context_pointset");
			Address addr;

			try {
				addr =
					AddressXML.restoreXml(contextElement, prog.getCompilerSpec()).getFirstAddress();

				ProgramContext pc = prog.getProgramContext();
				while (parser.peek().isStart("set") && !monitor.isCancelled()) {
					XmlElement setElement = parser.start("set");
					String regName = setElement.getAttribute(ATTRIB_NAME.name());
					BigInteger regVal = new BigInteger(setElement.getAttribute(ATTRIB_VAL.name()));
					Register reg = pc.getRegister(regName);
					pc.setValue(reg, addr, addr, regVal);
					parser.end(setElement);
				}
			}
			catch (XmlParseException | ContextChangeException e) {
				log.appendException(e);
			}
			parser.end(contextElement);
		}

		while (parser.peek().isStart("tracked_pointset")) {
			log.appendMsg(parser.getLineNumber(), "Level " + parser.getCurrentLevel() +
				" tag not currently supported: " + parser.peek().getName());
			parser.discardSubTree();
		}

	}

	/**
	 * Simple handling of error messages 
	 */
	class MyErrorHandler implements ErrorHandler {
		private MessageLog log;

		MyErrorHandler(MessageLog log) {
			this.log = log;
		}

		@Override
		public void warning(SAXParseException exception) throws SAXException {
			log.appendMsg(exception.getMessage());

		}

		@Override
		public void error(SAXParseException exception) throws SAXException {
			log.appendMsg(exception.getMessage());

		}

		@Override
		public void fatalError(SAXParseException exception) throws SAXException {
			log.appendMsg(exception.getMessage());

		}
	}
}
