/*
 * [The "BSD license"]
 *  Copyright (c) 2010 Terence Parr
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *  1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *  3. The name of the author may not be used to endorse or promote products
 *      derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 *  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 *  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 *  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 *  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.antlr.tool;

import org.antlr.Tool;
import org.antlr.analysis.DFAState;
import org.antlr.analysis.DecisionProbe;
import org.antlr.analysis.NFAState;
import org.antlr.misc.BitSet;
import org.antlr.runtime.RecognitionException;
import org.antlr.runtime.Token;
import org.stringtemplate.v4.ST;
import org.stringtemplate.v4.STErrorListener;
import org.stringtemplate.v4.STGroup;
import org.stringtemplate.v4.STGroupFile;
import org.stringtemplate.v4.misc.STMessage;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;

/** Defines all the errors ANTLR can generator for both the tool and for
 *  issues with a grammar.
 *
 *  Here is a list of language names:
 *
 *  http://ftp.ics.uci.edu/pub/ietf/http/related/iso639.txt
 *
 *  Here is a list of country names:
 *
 *  http://www.chemie.fu-berlin.de/diverse/doc/ISO_3166.html
 *
 *  I use constants not strings to identify messages as the compiler will
 *  find any errors/mismatches rather than leaving a mistyped string in
 *  the code to be found randomly in the future.  Further, Intellij can
 *  do field name expansion to save me some typing.  I have to map
 *  int constants to template names, however, which could introduce a mismatch.
 *  Someone could provide a .stg file that had a template name wrong.  When
 *  I load the group, then, I must verify that all messages are there.
 *
 *  This is essentially the functionality of the resource bundle stuff Java
 *  has, but I don't want to load a property file--I want to load a template
 *  group file and this is so simple, why mess with their junk.
 *
 *  I use the default Locale as defined by java to compute a group file name
 *  in the org/antlr/tool/templates/messages dir called en_US.stg and so on.
 *
 *  Normally we want to use the default locale, but often a message file will
 *  not exist for it so we must fall back on the US local.
 *
 *  During initialization of this class, all errors go straight to System.err.
 *  There is no way around this.  If I have not set up the error system, how
 *  can I do errors properly?  For example, if the string template group file
 *  full of messages has an error, how could I print to anything but System.err?
 *
 *  TODO: how to map locale to a file encoding for the stringtemplate group file?
 *  ST knows how to pay attention to the default encoding so it
 *  should probably just work unless a GUI sets the local to some chinese
 *  variation but System.getProperty("file.encoding") is US.  Hmm...
 *
 *  TODO: get antlr.g etc.. parsing errors to come here.
 */
public class ErrorManager {
	// TOOL ERRORS
	// file errors
	public static final int MSG_CANNOT_WRITE_FILE = 1;
	public static final int MSG_CANNOT_CLOSE_FILE = 2;
	public static final int MSG_CANNOT_FIND_TOKENS_FILE = 3;
	public static final int MSG_ERROR_READING_TOKENS_FILE = 4;
	public static final int MSG_DIR_NOT_FOUND = 5;
	public static final int MSG_OUTPUT_DIR_IS_FILE = 6;
	public static final int MSG_CANNOT_OPEN_FILE = 7;
	public static final int MSG_FILE_AND_GRAMMAR_NAME_DIFFER = 8;
	public static final int MSG_FILENAME_EXTENSION_ERROR = 9;

	public static final int MSG_INTERNAL_ERROR = 10;
	public static final int MSG_INTERNAL_WARNING = 11;
	public static final int MSG_ERROR_CREATING_ARTIFICIAL_RULE = 12;
	public static final int MSG_TOKENS_FILE_SYNTAX_ERROR = 13;
	public static final int MSG_CANNOT_GEN_DOT_FILE = 14;
	public static final int MSG_BAD_AST_STRUCTURE = 15;
	public static final int MSG_BAD_ACTION_AST_STRUCTURE = 16;

	// code gen errors
	public static final int MSG_MISSING_CODE_GEN_TEMPLATES = 20;
	public static final int MSG_MISSING_CYCLIC_DFA_CODE_GEN_TEMPLATES = 21;
	public static final int MSG_CODE_GEN_TEMPLATES_INCOMPLETE = 22;
	public static final int MSG_CANNOT_CREATE_TARGET_GENERATOR = 23;
	//public static final int MSG_CANNOT_COMPUTE_SAMPLE_INPUT_SEQ = 24;
	public static final int MSG_STRING_TEMPLATE_ERROR = 24;

	// GRAMMAR ERRORS
	public static final int MSG_SYNTAX_ERROR = 100;
	public static final int MSG_RULE_REDEFINITION = 101;
	public static final int MSG_LEXER_RULES_NOT_ALLOWED = 102;
	public static final int MSG_PARSER_RULES_NOT_ALLOWED = 103;
	public static final int MSG_CANNOT_FIND_ATTRIBUTE_NAME_IN_DECL = 104;
	public static final int MSG_NO_TOKEN_DEFINITION = 105;
	public static final int MSG_UNDEFINED_RULE_REF = 106;
	public static final int MSG_LITERAL_NOT_ASSOCIATED_WITH_LEXER_RULE = 107;
	public static final int MSG_CANNOT_ALIAS_TOKENS_IN_LEXER = 108;
	public static final int MSG_ATTRIBUTE_REF_NOT_IN_RULE = 111;
	public static final int MSG_INVALID_RULE_SCOPE_ATTRIBUTE_REF = 112;
	public static final int MSG_UNKNOWN_ATTRIBUTE_IN_SCOPE = 113;
	public static final int MSG_UNKNOWN_SIMPLE_ATTRIBUTE = 114;
	public static final int MSG_INVALID_RULE_PARAMETER_REF = 115;
	public static final int MSG_UNKNOWN_RULE_ATTRIBUTE = 116;
	public static final int MSG_ISOLATED_RULE_SCOPE = 117;
	public static final int MSG_SYMBOL_CONFLICTS_WITH_GLOBAL_SCOPE = 118;
	public static final int MSG_LABEL_CONFLICTS_WITH_RULE = 119;
	public static final int MSG_LABEL_CONFLICTS_WITH_TOKEN = 120;
	public static final int MSG_LABEL_CONFLICTS_WITH_RULE_SCOPE_ATTRIBUTE = 121;
	public static final int MSG_LABEL_CONFLICTS_WITH_RULE_ARG_RETVAL = 122;
	public static final int MSG_ATTRIBUTE_CONFLICTS_WITH_RULE = 123;
	public static final int MSG_ATTRIBUTE_CONFLICTS_WITH_RULE_ARG_RETVAL = 124;
	public static final int MSG_LABEL_TYPE_CONFLICT = 125;
	public static final int MSG_ARG_RETVAL_CONFLICT = 126;
	public static final int MSG_NONUNIQUE_REF = 127;
	public static final int MSG_FORWARD_ELEMENT_REF = 128;
	public static final int MSG_MISSING_RULE_ARGS = 129;
	public static final int MSG_RULE_HAS_NO_ARGS = 130;
	public static final int MSG_ARGS_ON_TOKEN_REF = 131;
	public static final int MSG_RULE_REF_AMBIG_WITH_RULE_IN_ALT = 132;
	public static final int MSG_ILLEGAL_OPTION = 133;
	public static final int MSG_LIST_LABEL_INVALID_UNLESS_RETVAL_STRUCT = 134;
	public static final int MSG_UNDEFINED_TOKEN_REF_IN_REWRITE = 135;
	public static final int MSG_REWRITE_ELEMENT_NOT_PRESENT_ON_LHS = 136;
	public static final int MSG_UNDEFINED_LABEL_REF_IN_REWRITE = 137;
	public static final int MSG_NO_GRAMMAR_START_RULE = 138;
	public static final int MSG_EMPTY_COMPLEMENT = 139;
	public static final int MSG_UNKNOWN_DYNAMIC_SCOPE = 140;
	public static final int MSG_UNKNOWN_DYNAMIC_SCOPE_ATTRIBUTE = 141;
	public static final int MSG_ISOLATED_RULE_ATTRIBUTE = 142;
	public static final int MSG_INVALID_ACTION_SCOPE = 143;
	public static final int MSG_ACTION_REDEFINITION = 144;
	public static final int MSG_DOUBLE_QUOTES_ILLEGAL = 145;
	public static final int MSG_INVALID_TEMPLATE_ACTION = 146;
	public static final int MSG_MISSING_ATTRIBUTE_NAME = 147;
	public static final int MSG_ARG_INIT_VALUES_ILLEGAL = 148;
	public static final int MSG_REWRITE_OR_OP_WITH_NO_OUTPUT_OPTION = 149;
	public static final int MSG_NO_RULES = 150;
	public static final int MSG_WRITE_TO_READONLY_ATTR = 151;
	public static final int MSG_MISSING_AST_TYPE_IN_TREE_GRAMMAR = 152;
	public static final int MSG_REWRITE_FOR_MULTI_ELEMENT_ALT = 153;
	public static final int MSG_RULE_INVALID_SET = 154;
	public static final int MSG_HETERO_ILLEGAL_IN_REWRITE_ALT = 155;
	public static final int MSG_NO_SUCH_GRAMMAR_SCOPE = 156;
	public static final int MSG_NO_SUCH_RULE_IN_SCOPE = 157;
	public static final int MSG_TOKEN_ALIAS_CONFLICT = 158;
	public static final int MSG_TOKEN_ALIAS_REASSIGNMENT = 159;
	public static final int MSG_TOKEN_VOCAB_IN_DELEGATE = 160;
	public static final int MSG_INVALID_IMPORT = 161;
	public static final int MSG_IMPORTED_TOKENS_RULE_EMPTY = 162;
	public static final int MSG_IMPORT_NAME_CLASH = 163;
	public static final int MSG_AST_OP_WITH_NON_AST_OUTPUT_OPTION = 164;
	public static final int MSG_AST_OP_IN_ALT_WITH_REWRITE = 165;
    public static final int MSG_WILDCARD_AS_ROOT = 166;
    public static final int MSG_CONFLICTING_OPTION_IN_TREE_FILTER = 167;
	public static final int MSG_ILLEGAL_OPTION_VALUE = 168;
	public static final int MSG_ALL_OPS_NEED_SAME_ASSOC = 169;
	public static final int MSG_RANGE_OP_ILLEGAL = 170;

	// GRAMMAR WARNINGS
	public static final int MSG_GRAMMAR_NONDETERMINISM = 200; // A predicts alts 1,2
	public static final int MSG_UNREACHABLE_ALTS = 201;       // nothing predicts alt i
	public static final int MSG_DANGLING_STATE = 202;         // no edges out of state
	public static final int MSG_INSUFFICIENT_PREDICATES = 203;
	public static final int MSG_DUPLICATE_SET_ENTRY = 204;    // (A|A)
	public static final int MSG_ANALYSIS_ABORTED = 205;
	public static final int MSG_RECURSION_OVERLOW = 206;
	public static final int MSG_LEFT_RECURSION = 207;
	public static final int MSG_UNREACHABLE_TOKENS = 208; // nothing predicts token
	public static final int MSG_TOKEN_NONDETERMINISM = 209; // alts of Tokens rule
	public static final int MSG_LEFT_RECURSION_CYCLES = 210;
	public static final int MSG_NONREGULAR_DECISION = 211;


    // Dependency sorting errors
    //
    public static final int MSG_CIRCULAR_DEPENDENCY = 212; // t1.g -> t2.g -> t3.g ->t1.g

	public static final int MAX_MESSAGE_NUMBER = 212;

	/** Do not do perform analysis if one of these happens */
	public static final BitSet ERRORS_FORCING_NO_ANALYSIS = new BitSet() {
		{
			add(MSG_RULE_REDEFINITION);
			add(MSG_UNDEFINED_RULE_REF);
			add(MSG_LEFT_RECURSION_CYCLES);
			add(MSG_REWRITE_OR_OP_WITH_NO_OUTPUT_OPTION);
			add(MSG_NO_RULES);
			add(MSG_NO_SUCH_GRAMMAR_SCOPE);
			add(MSG_NO_SUCH_RULE_IN_SCOPE);
			add(MSG_LEXER_RULES_NOT_ALLOWED);
            add(MSG_WILDCARD_AS_ROOT);
            add(MSG_CIRCULAR_DEPENDENCY);
            // TODO: ...
		}
	};

	/** Do not do code gen if one of these happens */
	public static final BitSet ERRORS_FORCING_NO_CODEGEN = new BitSet() {
		{
			add(MSG_NONREGULAR_DECISION);
			add(MSG_RECURSION_OVERLOW);
			add(MSG_UNREACHABLE_ALTS);
			add(MSG_FILE_AND_GRAMMAR_NAME_DIFFER);
			add(MSG_INVALID_IMPORT);
			add(MSG_AST_OP_WITH_NON_AST_OUTPUT_OPTION);
            add(MSG_CIRCULAR_DEPENDENCY);
			// TODO: ...
		}
	};

	/** Only one error can be emitted for any entry in this table.
	 *  Map<String,Set> where the key is a method name like danglingState.
	 *  The set is whatever that method accepts or derives like a DFA.
	 */
	public static final Map<String, Set<String>> emitSingleError = new HashMap<String, Set<String>>() {
		{
			put("danglingState", new HashSet<String>());
		}
	};

	/** Messages should be sensitive to the locale. */
	private static Locale locale;
	private static String formatName;

	/** Each thread might need it's own error listener; e.g., a GUI with
	 *  multiple window frames holding multiple grammars.
	 */
	private static final Map<Thread, ANTLRErrorListener> threadToListenerMap = new WeakHashMap<Thread, ANTLRErrorListener>();

	public static class ErrorState {
		public int errors;
		public int warnings;
		public int infos;
		/** Track all msgIDs; we use to abort later if necessary
		 *  also used in Message to find out what type of message it is via getMessageType()
		 */
		public BitSet errorMsgIDs = new BitSet();
		public BitSet warningMsgIDs = new BitSet();
		// TODO: figure out how to do info messages. these do not have IDs...kr
		//public BitSet infoMsgIDs = new BitSet();
	}

	/** Track the number of errors regardless of the listener but track
	 *  per thread.
	 */
	private static final Map<Thread, ErrorState> threadToErrorStateMap = new WeakHashMap<Thread, ErrorState>();

	/** Each thread has its own ptr to a Tool object, which knows how
	 *  to panic, for example.  In a GUI, the thread might just throw an Error
	 *  to exit rather than the suicide System.exit.
	 */
	private static final Map<Thread, Tool> threadToToolMap = new WeakHashMap<Thread, Tool>();

	/** The group of templates that represent all possible ANTLR errors. */
	private static STGroup messages;
	/** The group of templates that represent the current message format. */
	private static STGroup format;

	/** From a msgID how can I get the name of the template that describes
	 *  the error or warning?
	 */
	private static final String[] idToMessageTemplateName = new String[MAX_MESSAGE_NUMBER+1];

	static ANTLRErrorListener theDefaultErrorListener = new ANTLRErrorListener() {
		@Override
		public void info(String msg) {
			if (formatWantsSingleLineMessage()) {
				msg = msg.replaceAll("\n", " ");
			}
			System.err.println(msg);
		}

		@Override
		public void error(Message msg) {
			String outputMsg = msg.toString();
			if (formatWantsSingleLineMessage()) {
				outputMsg = outputMsg.replaceAll("\n", " ");
			}
			System.err.println(outputMsg);
		}

		@Override
		public void warning(Message msg) {
			String outputMsg = msg.toString();
			if (formatWantsSingleLineMessage()) {
				outputMsg = outputMsg.replaceAll("\n", " ");
			}
			System.err.println(outputMsg);
		}

		@Override
		public void error(ToolMessage msg) {
			String outputMsg = msg.toString();
			if (formatWantsSingleLineMessage()) {
				outputMsg = outputMsg.replaceAll("\n", " ");
			}
			System.err.println(outputMsg);
		}
	};

	/** Handle all ST error listeners here (code gen, Grammar, and this class
	 *  use templates.
	 */
	static STErrorListener initSTListener =
		new STErrorListener() {
			@Override
			public void compileTimeError(STMessage msg) {
				System.err.println("ErrorManager init error: "+msg);
			}

			@Override
			public void runTimeError(STMessage msg) {
				System.err.println("ErrorManager init error: "+msg);
			}

			@Override
			public void IOError(STMessage msg) {
				System.err.println("ErrorManager init error: "+msg);
			}

			@Override
			public void internalError(STMessage msg) {
				System.err.println("ErrorManager init error: "+msg);
			}

		};

	/** During verification of the messages group file, don't gen errors.
	 *  I'll handle them here.  This is used only after file has loaded ok
	 *  and only for the messages STG.
	 */
	static STErrorListener blankSTListener =
		new STErrorListener() {
			@Override public void compileTimeError(STMessage msg) {			}
			@Override public void runTimeError(STMessage msg) {			}
			@Override public void IOError(STMessage msg) {			}
			@Override public void internalError(STMessage msg) {			}
		};

	/** Errors during initialization related to ST must all go to System.err.
	 */
	static STErrorListener theDefaultSTListener =
		new STErrorListener() {
			@Override
			public void compileTimeError(STMessage msg) {
				ErrorManager.error(ErrorManager.MSG_STRING_TEMPLATE_ERROR, msg.toString(), msg.cause);
			}

			@Override
			public void runTimeError(STMessage msg) {
				switch (msg.error) {
				case NO_SUCH_ATTRIBUTE:
				case NO_SUCH_ATTRIBUTE_PASS_THROUGH:
				case NO_SUCH_PROPERTY:
					ErrorManager.warning(ErrorManager.MSG_STRING_TEMPLATE_ERROR, msg.toString());
					return;

				default:
					ErrorManager.error(ErrorManager.MSG_STRING_TEMPLATE_ERROR, msg.toString(), msg.cause);
					return;
				}
			}

			@Override
			public void IOError(STMessage msg) {
				ErrorManager.error(ErrorManager.MSG_STRING_TEMPLATE_ERROR, msg.toString(), msg.cause);
			}

			@Override
			public void internalError(STMessage msg) {
				ErrorManager.error(ErrorManager.MSG_STRING_TEMPLATE_ERROR, msg.toString(), msg.cause);
			}
		};

	// make sure that this class is ready to use after loading
	static {
		initIdToMessageNameMapping();
		// it is inefficient to set the default locale here if another
		// piece of code is going to set the locale, but that would
		// require that a user call an init() function or something.  I prefer
		// that this class be ready to go when loaded as I'm absentminded ;)
		setLocale(Locale.getDefault());
		// try to load the message format group
		// the user might have specified one on the command line
		// if not, or if the user has given an illegal value, we will fall back to "antlr"
		setFormat("antlr");
	}

    public static STErrorListener getSTErrorListener() {
		return theDefaultSTListener;
	}

	/** We really only need a single locale for entire running ANTLR code
	 *  in a single VM.  Only pay attention to the language, not the country
	 *  so that French Canadians and French Frenchies all get the same
	 *  template file, fr.stg.  Just easier this way.
	 */
	public static void setLocale(Locale locale) {
		ErrorManager.locale = locale;
		String language = locale.getLanguage();
		String fileName = "org/antlr/tool/templates/messages/languages/"+language+".stg";
		try {
			messages = new STGroupFile(fileName);
		}
		catch (IllegalArgumentException iae) {
			if ( language.equals(Locale.US.getLanguage()) ) {
				rawError("ANTLR installation corrupted; cannot find English messages file "+fileName);
				panic();
			}
			else {
				setLocale(Locale.US); // recurse on this rule, trying the US locale
			}
		}

		messages.setListener(blankSTListener);
		boolean messagesOK = verifyMessages();
		if ( !messagesOK && language.equals(Locale.US.getLanguage()) ) {
			rawError("ANTLR installation corrupted; English messages file "+language+".stg incomplete");
			panic();
		}
		else if ( !messagesOK ) {
			setLocale(Locale.US); // try US to see if that will work
		}
	}

	/** The format gets reset either from the Tool if the user supplied a command line option to that effect
	 *  Otherwise we just use the default "antlr".
	 */
	public static void setFormat(String formatName) {
		ErrorManager.formatName = formatName;
		String fileName = "org/antlr/tool/templates/messages/formats/"+formatName+".stg";
		format = new STGroupFile(fileName);
		format.setListener(initSTListener);
		if ( !format.isDefined("message") ) { // pick random msg to load
			if ( formatName.equals("antlr") ) {
				rawError("no such message format file "+fileName+" retrying with default ANTLR format");
				setFormat("antlr"); // recurse on this rule, trying the default message format
				return;
			}
			else {
				setFormat("antlr"); // recurse on this rule, trying the default message format
			}
		}

		format.setListener(blankSTListener);
		boolean formatOK = verifyFormat();
		if ( !formatOK && formatName.equals("antlr") ) {
			rawError("ANTLR installation corrupted; ANTLR messages format file "+formatName+".stg incomplete");
			panic();
		}
		else if ( !formatOK ) {
			setFormat("antlr"); // recurse on this rule, trying the default message format
		}
	}

	/** Encodes the error handling found in setLocale, but does not trigger
	 *  panics, which would make GUI tools die if ANTLR's installation was
	 *  a bit screwy.  Duplicated code...ick.
	public static Locale getLocaleForValidMessages(Locale locale) {
		ErrorManager.locale = locale;
		String language = locale.getLanguage();
		String fileName = "org/antlr/tool/templates/messages/"+language+".stg";
		ClassLoader cl = Thread.currentThread().getContextClassLoader();
		InputStream is = cl.getResourceAsStream(fileName);
		if ( is==null && language.equals(Locale.US.getLanguage()) ) {
			return null;
		}
		else if ( is==null ) {
			return getLocaleForValidMessages(Locale.US); // recurse on this rule, trying the US locale
		}

		boolean messagesOK = verifyMessages();
		if ( !messagesOK && language.equals(Locale.US.getLanguage()) ) {
			return null;
		}
		else if ( !messagesOK ) {
			return getLocaleForValidMessages(Locale.US); // try US to see if that will work
		}
		return true;
	}
	 */

	/** In general, you'll want all errors to go to a single spot.
	 *  However, in a GUI, you might have two frames up with two
	 *  different grammars.  Two threads might launch to process the
	 *  grammars--you would want errors to go to different objects
	 *  depending on the thread.  I store a single listener per
	 *  thread.
	 */
	public static void setErrorListener(ANTLRErrorListener listener) {
		threadToListenerMap.put(Thread.currentThread(), listener);
	}

    public static void removeErrorListener() {
        threadToListenerMap.remove(Thread.currentThread());
    }

	public static void setTool(Tool tool) {
		threadToToolMap.put(Thread.currentThread(), tool);
	}

	/** Given a message ID, return a ST that somebody can fill
	 *  with data.  We need to convert the int ID to the name of a template
	 *  in the messages ST group.
	 */
	public static ST getMessage(int msgID) {
        String msgName = idToMessageTemplateName[msgID];
		return messages.getInstanceOf(msgName);
	}
	public static String getMessageType(int msgID) {
		if (getErrorState().warningMsgIDs.member(msgID)) {
			return messages.getInstanceOf("warning").render();
		}
		else if (getErrorState().errorMsgIDs.member(msgID)) {
			return messages.getInstanceOf("error").render();
		}
		assertTrue(false, "Assertion failed! Message ID " + msgID + " created but is not present in errorMsgIDs or warningMsgIDs.");
		return "";
	}

	/** Return a ST that refers to the current format used for
	 * emitting messages.
	 */
	public static ST getLocationFormat() {
		return format.getInstanceOf("location");
	}
	public static ST getReportFormat() {
		return format.getInstanceOf("report");
	}
	public static ST getMessageFormat() {
		return format.getInstanceOf("message");
	}
	public static boolean formatWantsSingleLineMessage() {
		return format.getInstanceOf("wantsSingleLineMessage").render().equals("true");
	}

	public static ANTLRErrorListener getErrorListener() {
		ANTLRErrorListener el =
			threadToListenerMap.get(Thread.currentThread());
		if ( el==null ) {
			return theDefaultErrorListener;
		}
		return el;
	}

	public static ErrorState getErrorState() {
		ErrorState ec =
			threadToErrorStateMap.get(Thread.currentThread());
		if ( ec==null ) {
			ec = new ErrorState();
			threadToErrorStateMap.put(Thread.currentThread(), ec);
		}
		return ec;
	}

	public static int getNumErrors() {
		return getErrorState().errors;
	}

	public static void resetErrorState() {
        threadToListenerMap.clear();
        ErrorState ec = new ErrorState();
		threadToErrorStateMap.put(Thread.currentThread(), ec);
	}

	public static void info(String msg) {
		getErrorState().infos++;
		getErrorListener().info(msg);
	}

	public static void error(int msgID) {
		getErrorState().errors++;
		getErrorState().errorMsgIDs.add(msgID);
		getErrorListener().error(new ToolMessage(msgID));
	}

	public static void error(int msgID, Throwable e) {
		getErrorState().errors++;
		getErrorState().errorMsgIDs.add(msgID);
		getErrorListener().error(new ToolMessage(msgID,e));
	}

	public static void error(int msgID, Object arg) {
		getErrorState().errors++;
		getErrorState().errorMsgIDs.add(msgID);
		getErrorListener().error(new ToolMessage(msgID, arg));
	}

	public static void error(int msgID, Object arg, Object arg2) {
		getErrorState().errors++;
		getErrorState().errorMsgIDs.add(msgID);
		getErrorListener().error(new ToolMessage(msgID, arg, arg2));
	}

	public static void error(int msgID, Object arg, Throwable e) {
		getErrorState().errors++;
		getErrorState().errorMsgIDs.add(msgID);
		getErrorListener().error(new ToolMessage(msgID, arg, e));
	}

	public static void warning(int msgID, Object arg) {
		getErrorState().warnings++;
		getErrorState().warningMsgIDs.add(msgID);
		getErrorListener().warning(new ToolMessage(msgID, arg));
	}

	public static void nondeterminism(DecisionProbe probe,
									  DFAState d)
	{
		getErrorState().warnings++;
		Message msg = new GrammarNonDeterminismMessage(probe,d);
		getErrorState().warningMsgIDs.add(msg.msgID);
		getErrorListener().warning(msg);
	}

	public static void danglingState(DecisionProbe probe,
									 DFAState d)
	{
		getErrorState().errors++;
		Message msg = new GrammarDanglingStateMessage(probe,d);
		getErrorState().errorMsgIDs.add(msg.msgID);
		Set<String> seen = emitSingleError.get("danglingState");
		if ( !seen.contains(d.dfa.decisionNumber+"|"+d.getAltSet()) ) {
			getErrorListener().error(msg);
			// we've seen this decision and this alt set; never again
			seen.add(d.dfa.decisionNumber+"|"+d.getAltSet());
		}
	}

	public static void analysisAborted(DecisionProbe probe)
	{
		getErrorState().warnings++;
		Message msg = new GrammarAnalysisAbortedMessage(probe);
		getErrorState().warningMsgIDs.add(msg.msgID);
		getErrorListener().warning(msg);
	}

	public static void unreachableAlts(DecisionProbe probe,
									   List<Integer> alts)
	{
		getErrorState().errors++;
		Message msg = new GrammarUnreachableAltsMessage(probe,alts);
		getErrorState().errorMsgIDs.add(msg.msgID);
		getErrorListener().error(msg);
	}

	public static void insufficientPredicates(DecisionProbe probe,
											  DFAState d,
											  Map<Integer, Set<Token>> altToUncoveredLocations)
	{
		getErrorState().warnings++;
		Message msg = new GrammarInsufficientPredicatesMessage(probe,d,altToUncoveredLocations);
		getErrorState().warningMsgIDs.add(msg.msgID);
		getErrorListener().warning(msg);
	}

	public static void nonLLStarDecision(DecisionProbe probe) {
		getErrorState().errors++;
		Message msg = new NonRegularDecisionMessage(probe, probe.getNonDeterministicAlts());
		getErrorState().errorMsgIDs.add(msg.msgID);
		getErrorListener().error(msg);
	}

	public static void recursionOverflow(DecisionProbe probe,
										 DFAState sampleBadState,
										 int alt,
										 Collection<String> targetRules,
										 Collection<? extends Collection<? extends NFAState>> callSiteStates)
	{
		getErrorState().errors++;
		Message msg = new RecursionOverflowMessage(probe,sampleBadState, alt,
										 targetRules, callSiteStates);
		getErrorState().errorMsgIDs.add(msg.msgID);
		getErrorListener().error(msg);
	}

	/*
	// TODO: we can remove I think.  All detected now with cycles check.
	public static void leftRecursion(DecisionProbe probe,
									 int alt,
									 Collection targetRules,
									 Collection callSiteStates)
	{
		getErrorState().warnings++;
		Message msg = new LeftRecursionMessage(probe, alt, targetRules, callSiteStates);
		getErrorState().warningMsgIDs.add(msg.msgID);
		getErrorListener().warning(msg);
	}
	*/

	public static void leftRecursionCycles(Collection<? extends Set<? extends Rule>> cycles) {
		getErrorState().errors++;
		Message msg = new LeftRecursionCyclesMessage(cycles);
		getErrorState().errorMsgIDs.add(msg.msgID);
		getErrorListener().error(msg);
	}

	public static void grammarError(int msgID,
									Grammar g,
									Token token,
									Object arg,
									Object arg2)
	{
		getErrorState().errors++;
		Message msg = new GrammarSemanticsMessage(msgID,g,token,arg,arg2);
		getErrorState().errorMsgIDs.add(msgID);
		getErrorListener().error(msg);
	}

	public static void grammarError(int msgID,
									Grammar g,
									Token token,
									Object arg)
	{
		grammarError(msgID,g,token,arg,null);
	}

	public static void grammarError(int msgID,
									Grammar g,
									Token token)
	{
		grammarError(msgID,g,token,null,null);
	}

	public static void grammarWarning(int msgID,
									  Grammar g,
									  Token token,
									  Object arg,
									  Object arg2)
	{
		getErrorState().warnings++;
		Message msg = new GrammarSemanticsMessage(msgID,g,token,arg,arg2);
		getErrorState().warningMsgIDs.add(msgID);
		getErrorListener().warning(msg);
	}

	public static void grammarWarning(int msgID,
									  Grammar g,
									  Token token,
									  Object arg)
	{
		grammarWarning(msgID,g,token,arg,null);
	}

	public static void grammarWarning(int msgID,
									  Grammar g,
									  Token token)
	{
		grammarWarning(msgID,g,token,null,null);
	}

	public static void syntaxError(int msgID,
								   Grammar grammar,
								   Token token,
								   Object arg,
								   RecognitionException re)
	{
		getErrorState().errors++;
		getErrorState().errorMsgIDs.add(msgID);
		getErrorListener().error(
			new GrammarSyntaxMessage(msgID,grammar,token,arg,re)
		);
	}

	public static void internalError(Object error, Throwable e) {
		StackTraceElement location = getLastNonErrorManagerCodeLocation(e);
		String msg = "Exception "+e+"@"+location+": "+error;
		error(MSG_INTERNAL_ERROR, msg);
	}

	public static void internalError(Object error) {
		StackTraceElement location =
			getLastNonErrorManagerCodeLocation(new Exception());
		String msg = location+": "+error;
		error(MSG_INTERNAL_ERROR, msg);
	}

	public static boolean doNotAttemptAnalysis() {
		return !getErrorState().errorMsgIDs.and(ERRORS_FORCING_NO_ANALYSIS).isNil();
	}

	public static boolean doNotAttemptCodeGen() {
		return doNotAttemptAnalysis() ||
			   !getErrorState().errorMsgIDs.and(ERRORS_FORCING_NO_CODEGEN).isNil();
	}

	/** Return first non ErrorManager code location for generating messages */
	private static StackTraceElement getLastNonErrorManagerCodeLocation(Throwable e) {
		StackTraceElement[] stack = e.getStackTrace();
		int i = 0;
		for (; i < stack.length; i++) {
			StackTraceElement t = stack[i];
			if ( t.toString().indexOf("ErrorManager")<0 ) {
				break;
			}
		}
		StackTraceElement location = stack[i];
		return location;
	}

	// A S S E R T I O N  C O D E

	public static void assertTrue(boolean condition, String message) {
		if ( !condition ) {
			internalError(message);
		}
	}

	// S U P P O R T  C O D E

	protected static boolean initIdToMessageNameMapping() {
		// make sure a message exists, even if it's just to indicate a problem
		for (int i = 0; i < idToMessageTemplateName.length; i++) {
			idToMessageTemplateName[i] = "INVALID MESSAGE ID: "+i;
		}
		// get list of fields and use it to fill in idToMessageTemplateName mapping
		Field[] fields = ErrorManager.class.getFields();
		for (int i = 0; i < fields.length; i++) {
			Field f = fields[i];
			String fieldName = f.getName();
			if ( !fieldName.startsWith("MSG_") ) {
				continue;
			}
			String templateName =
				fieldName.substring("MSG_".length(),fieldName.length());
			int msgID;
			try {
				// get the constant value from this class object
				msgID = f.getInt(ErrorManager.class);
			}
			catch (IllegalAccessException iae) {
				System.err.println("cannot get const value for "+f.getName());
				continue;
			}
			if ( fieldName.startsWith("MSG_") ) {
                idToMessageTemplateName[msgID] = templateName;
			}
		}
		return true;
	}

	/** Use reflection to find list of MSG_ fields and then verify a
	 *  template exists for each one from the locale's group.
	 */
	protected static boolean verifyMessages() {
		boolean ok = true;
		Field[] fields = ErrorManager.class.getFields();
		for (int i = 0; i < fields.length; i++) {
			Field f = fields[i];
			String fieldName = f.getName();
			String templateName =
				fieldName.substring("MSG_".length(),fieldName.length());
			if ( fieldName.startsWith("MSG_") ) {
				if ( !messages.isDefined(templateName) ) {
					System.err.println("Message "+templateName+" in locale "+
									   locale+" not found");
					ok = false;
				}
			}
		}
		// check for special templates
		if (!messages.isDefined("warning")) {
			System.err.println("Message template 'warning' not found in locale "+ locale);
			ok = false;
		}
		if (!messages.isDefined("error")) {
			System.err.println("Message template 'error' not found in locale "+ locale);
			ok = false;
		}
		return ok;
	}

	/** Verify the message format template group */
	protected static boolean verifyFormat() {
		boolean ok = true;
		if (!format.isDefined("location")) {
			System.err.println("Format template 'location' not found in " + formatName);
			ok = false;
		}
		if (!format.isDefined("message")) {
			System.err.println("Format template 'message' not found in " + formatName);
			ok = false;
		}
		if (!format.isDefined("report")) {
			System.err.println("Format template 'report' not found in " + formatName);
			ok = false;
		}
		return ok;
	}

	/** If there are errors during ErrorManager init, we have no choice
	 *  but to go to System.err.
	 */
	static void rawError(String msg) {
		System.err.println(msg);
	}

	static void rawError(String msg, Throwable e) {
		rawError(msg);
		e.printStackTrace(System.err);
	}

	/** I *think* this will allow Tool subclasses to exit gracefully
	 *  for GUIs etc...
	 */
	public static void panic() {
		Tool tool = threadToToolMap.get(Thread.currentThread());
		if ( tool==null ) {
			// no tool registered, exit
			throw new Error("ANTLR ErrorManager panic");
		}
		else {
			tool.panic();
		}
	}
}
