Source code for /engineering/autohit-2003/src/autohit/creator/compiler/XmlCompiler.javaOriginal file XmlCompiler.java
   1 /**
   2  * AUTOHIT 2003
   3  * Copyright Erich P Gatejen (c) 1989,1997,2003,2004
   4  * 
   5  * This program is free software; you can redistribute it and/or modify 
   6  * it under the terms of the GNU General Public License as published by 
   7  * the Free Software Foundation; either version 2 of the License, or (at
   8  * your option) any later version.
   9  * This program is distributed in the hope that it will be useful, but
  10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  11  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
  12  * more details.
  13  * 
  14  * You should have received a copy of the GNU General Public License along
  15  * with this program; if not, write to the Free Software Foundation, Inc.,
  16  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  17  *
  18  * Additional license information can be found in the documentation.
  19  * @author Erich P Gatejen
  20  */
  21 package autohit.creator.compiler;
  22 
  23 import java.io.EOFException;
  24 import java.io.File;
  25 import java.io.FileInputStream;
  26 import java.io.IOException;
  27 import java.io.InputStream;
  28 import java.io.InputStreamReader;
  29 
  30 import javax.xml.parsers.DocumentBuilder;
  31 import javax.xml.parsers.DocumentBuilderFactory;
  32 
  33 import org.w3c.dom.Document;
  34 import org.xml.sax.InputSource;
  35 import org.xml.sax.SAXException;
  36 
  37 import autohit.common.AutohitErrorCodes;
  38 import autohit.common.AutohitException;
  39 import autohit.common.AutohitLogInjectorWrapper;
  40 import autohit.common.AutohitProperties;
  41 import autohit.server.SystemContext;
  42 
  43 /**
  44  * This is the a base XML compiler.  It must be extended by a specific compiler.  
  45  * Users of an extended class will call the compile() method in this class, which
  46  * will first parse the XML then call the abstract method build().  An extended
  47  * class must override the build() method and use to to compile from the 
  48  * xml document tree.
  49  * <p>
  50  * This will load/cache the DTD by providing a new Resolver that will 
  51  * return a string reader to the cached DTD.  It assumes that each execution
  52  * of "build" is for a new compile.
  53  * <p>
  54  * WARNING!!!  For the compiler to work, the root property must be set
  55  * and passed in the prop to the constructor.
  56  *
  57  * @author Erich P. Gatejen
  58  * @version 1.1
  59  * <i>Version History</i>
  60  * <code>EPG - Initial - 14Apr03</code> 
  61  * 
  62  */
  63 public abstract class XmlCompiler {
  64 
  65 	private final static int BUFFER_SIZE = 1024;
  66 
  67 	/**
  68 	 *  Runtime logger.  Used for compile-time logging.  Use the two
  69 	 *  helper methods instead of using it directly--runtimeError and
  70 	 *  runtimeWarning.  
  71 	 * @see #runtimeError(String t)
  72 	 * @see #runtimeWarning(String t)
  73 	 */
  74 	public AutohitLogInjectorWrapper runtimeLog;
  75 	public AutohitLogInjectorWrapper myLog;
  76 	private int warnings;
  77 	private int errors;
  78 
  79 	/**
  80 	 *  Handles parse/compile errors and warnings.  Also serves as the ErrorHandler
  81 	 *  for the XML parser.
  82 	 *  @see autohit.creator.compiler.XmlParseErrorHandler
  83 	 */
  84 	public XmlParseErrorHandler myErrorHandler;
  85 
  86 	/**
  87 	 *  The DTD to use when parsing the source text.
  88 	 */
  89 	private XmlCompilerResolver myResolver;
  90 
  91 	/**
  92 	 *  System context
  93 	 */
  94 	private SystemContext sc;
  95 
  96 	/**
  97 	 *  XML internals
  98 	 */
  99 	private DocumentBuilder builder;
 100 	private DocumentBuilderFactory factory;
 101 
 102 	// --- PUBLIC METHODS ----------------------------------------------------	
 103 
 104 	/**
 105 	 *  Constructor.  You must use this and NOT the default.  
 106 	 *  It will make sure that the DTD for the SimLanguage is available.
 107 	 *
 108 	 *  If you use the defaulty constructor, the compiler will not know
 109 	 *  which DTD to use.  That is a BAD THING(tm).
 110 	 *
 111 	 *  @param dtdURI URI of the DTD used in the !DOCTYPE * SYSTEM clause in the
 112 	 *                  compile targets.
 113 	 *  @param sc  A system context containing valid references to a root logger
 114 	 *             and the system properties.
 115 	 *  @throws Exception any exception invalidates the compiler.
 116 	 */
 117 	public XmlCompiler(String dtdURI, SystemContext sc) throws Exception {
 118 
 119 		StringBuffer tempDTD;
 120 		String scrubbedURI;
 121 
 122 		// See if we have a logger
 123 		// CHEAT!!!  I'm going to seperate the logs later.
 124 		// TODO seperate the logs
 125 		runtimeLog = sc.getRootLogger();
 126 		myLog = runtimeLog;
 127 
 128 		// Find the dtd
 129 		String location =
 130 			sc.getPropertiesSet().getString(AutohitProperties.ROOT_PATH);
 131 		if (location == null) {
 132 			myLog.debug(
 133 				"COMPILER ERROR.  Root property not set!",
 134 				AutohitErrorCodes.CODE_COMPILE_CONFIGURATION_FAULT);
 135 			throw new AutohitException(
 136 				"Root property not set.",
 137 				AutohitErrorCodes.CODE_COMPILE_CONFIGURATION_FAULT);
 138 		}
 139 
 140 		location = location + AutohitProperties.literal_DTD_PATH + "/";
 141 		scrubbedURI = dtdURI.substring(dtdURI.indexOf(":") + 1);
 142 		String dtdPath = location + scrubbedURI;
 143 		myLog.debug(
 144 			"XMLCompiler:dtdPath=" + dtdPath,
 145 			AutohitErrorCodes.CODE_INFORMATIONAL_OK);
 146 
 147 		// load the dtd
 148 		File dtdFile = new File(dtdPath);
 149 		InputStreamReader inFile =
 150 			new InputStreamReader(new FileInputStream(dtdFile));
 151 		tempDTD = new StringBuffer();
 152 		try {
 153 			char[] buf = new char[BUFFER_SIZE];
 154 			int len;
 155 
 156 			len = inFile.read(buf, 0, BUFFER_SIZE);
 157 			while (len > 0) {
 158 				tempDTD.append(buf, 0, len);
 159 				len = inFile.read(buf, 0, BUFFER_SIZE);
 160 			}
 161 		} catch (EOFException e) {
 162 			// Dont do anything.  this is A-OK.  For some odd reason, java.io
 163 			// will sometimes throw an EOF instead of just returning a -1.
 164 		} catch (Exception e) {
 165 			throw (e);
 166 		}
 167 
 168 		// Set up a resolver from which the XML parser can get our DTD      
 169 		myResolver = new XmlCompilerResolver(myLog);
 170 		myResolver.register(scrubbedURI, tempDTD.toString());
 171 
 172 		// Set up our Error Handler
 173 		myErrorHandler = new XmlParseErrorHandler(this);
 174 
 175 		// Create my builders
 176 		factory = DocumentBuilderFactory.newInstance();
 177 		factory.setValidating(true);
 178 		//factory.setNamespaceAware(true);
 179 
 180 		builder = factory.newDocumentBuilder();
 181 		builder.setErrorHandler(myErrorHandler);
 182 		builder.setEntityResolver(myResolver);
 183 
 184 		myLog.debug(
 185 			"XMLCompiler: constructed.",
 186 			AutohitErrorCodes.CODE_INFORMATIONAL_OK);
 187 	}
 188 
 189 	/**
 190 	 *  Constructor.  Don't use the default--ever.
 191 	 */
 192 	public XmlCompiler() throws AutohitException {
 193 		throw new AutohitException(
 194 			"BAD PROGRAMMER DETECTED.  PUNISH HIM/HER SEVERELY!   DONT USE THE DEFAULT CONSTRUCTOR FOR autohit.creator.XmlCompiler.",
 195 			AutohitErrorCodes.CODE_CATASTROPHIC_FRAMEWORK_FAULT);
 196 	}
 197 
 198 	/**
 199 	 *  Compile a stream into object code.  It will abort on a major error
 200 	 *  and return a null instead of an object.  This base class does not
 201 	 *  specify the format of the object code.   Any compile errors or
 202 	 *  warnings can be found in the errors field.
 203 	 *
 204 	 *  @param is An input stream to the text that is to be compiled.
 205 	 *  @return a reference to the target object.
 206 	 */
 207 	synchronized public Object compile(InputStream is) {
 208 
 209 		Document myDocument;
 210 		Object objectCode = null;
 211 		InputSource isource;
 212 
 213 		// Any exception aborts the compile
 214 		try {
 215 
 216 			// Setup the log for this run		
 217 			this.setRuntimeLog(myLog); // CHEAT
 218 
 219 			// Parse.  This needs to be a singleton, because I can't trust the stock parser.
 220 			synchronized (builder) {
 221 				isource = new InputSource(is);
 222 				isource.setSystemId("//");
 223 				myDocument = builder.parse(isource);
 224 			}
 225 			myLog.debug(
 226 				"XMLCompiler: parse successful.",
 227 				AutohitErrorCodes.CODE_INFORMATIONAL_OK);
 228 
 229 			objectCode = build(myDocument);
 230 
 231 			// Reset the log
 232 			this.resetRuntimeLog();
 233 
 234 		} catch (SAXException sxe) {
 235 			// None of these should happen!
 236 			myLog.debug(
 237 				"XMLCompiler: Unrecoverable parsing error.",
 238 				AutohitErrorCodes.CODE_COMPILE_ABORT);
 239 			myLog.debug(":" + sxe.getMessage());
 240 		} catch (IOException ioe) {
 241 			myLog.debug(
 242 				"XMLCompiler: build failed to IOException.",
 243 				AutohitErrorCodes.CODE_CATASTROPHIC_FRAMEWORK_FAULT);
 244 			myLog.debug(":" + ioe.getMessage());
 245 		} catch (Exception e) {
 246 			myLog.debug(
 247 				"XMLCompiler: Software Detected Fault:  Unexpected general Exception.",
 248 				AutohitErrorCodes.CODE_SW_DETECTED_FAULT);
 249 			myLog.debug(":" + e.getMessage());
 250 		}
 251 		// this will return null unless it was set at the end of the try.
 252 		return objectCode;
 253 	}
 254 
 255 	/**
 256 	 *  Posts a warning to the runtime log and increments the error count.  
 257 	 * @param t the warning message
 258 	 */
 259 	public void runtimeWarning(String t) {
 260 		runtimeLog.warning(
 261 			"WARNING: " + t,
 262 			AutohitErrorCodes.CODE_COMPILE_WARNING);
 263 		warnings++;
 264 	}
 265 
 266 	/**
 267 	 *  Posts a warning to the runtime log and increments the error count.  
 268 	 * @param t the error message
 269 	 */
 270 	public void runtimeError(String t) {
 271 		runtimeLog.error("ERROR: " + t, AutohitErrorCodes.CODE_COMPILE_ERROR);
 272 		errors++;
 273 	}
 274 
 275 	/**
 276 	 *  Posts a debug message to the runtime log.  
 277 	 * @param t the debug message
 278 	 */
 279 	public void runtimeDebug(String t) {
 280 		runtimeLog.debug(t, AutohitErrorCodes.CODE_INFORMATIONAL_OK);
 281 	}
 282 
 283 	/**
 284 	 * Get error count.  
 285 	 * @return number of errors
 286 	 */
 287 	public int numberErrors() {
 288 		return errors;
 289 	}
 290 
 291 	/**
 292 	 * Get warning count.  
 293 	 * @return number of warnings
 294 	 */
 295 	public int numberWarnings() {
 296 		return warnings;
 297 	}
 298 
 299 	/**
 300 	 *  This sets the runtime log.  The runtime log will be used during
 301 	 *  compilation.  The system (myLog) is used during overhead functions.
 302 	 *  These can be the same log.  This method lets you point to a 
 303 	 *  different log.   This will clear the error and warning counts.
 304 	 * @param cl a log injector
 305 	 */
 306 	public void setRuntimeLog(AutohitLogInjectorWrapper cl) {
 307 		runtimeLog = cl;
 308 		warnings = 0;
 309 		errors = 0;
 310 	}
 311 
 312 	/**
 313 	 *  This sets the runtime log back to be the same as the system log.
 314 	 */
 315 	public void resetRuntimeLog() {
 316 		runtimeLog = myLog;
 317 	}
 318 
 319 	/**
 320 	 *  Abstract build method.  Override with a method that builds the object code
 321 	 *  from the XML parse tree.
 322 	 *
 323 	 *  @param xd   A parsed XML document.
 324 	 *  
 325 	 *  @return Object reference to the object code.
 326 	 */
 327 	public abstract Object build(Document xd);
 328 
 329 }