Source code for /engineering/autohit-2003/src/autohit/common/AutohitLogDrain.javaOriginal file AutohitLogDrain.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.common;
  22 
  23 import java.io.OutputStream;
  24 import java.io.OutputStreamWriter;
  25 import java.io.Writer;
  26 import java.util.Calendar;
  27 import java.util.GregorianCalendar;
  28 
  29 import autohit.common.channels.Atom;
  30 import autohit.common.channels.ChannelException;
  31 import autohit.common.channels.Drain;
  32 import autohit.common.channels.Receipt;
  33 
  34 /**
  35  * An abstract logging formatter for Autohit Tools.  It will try to log anything that
  36  * has a String in the atom.  You can route Atom.senderIDs to different
  37  * output streams.
  38  * <p>
  39  * The subclasses need to implement a setWriter() and initchain().
  40  * <p>
  41  * A pretty flag tells the formatter to keep the lines under
  42  * 80 characters, applying wrapping where possible.  TRUE means 
  43  * pretty.  The default is FALSE.  It will use the line.separator
  44  * property to put a return on the wrapped lines.
  45  * <p>
  46  * NOT STAMPED
  47  * <code>
  48  * 0123456789...............{79}
  49  * IINNNN:tttttttttt......t[EOL]
  50  *       :tttttttttt......t[EOL]
  51  * [  7  ][        69      ]
  52  * </code>
  53  * <p>
  54  * STAMPED (with default formatter) 
  55  * We will reserve 4 extra spacers that an overloading formatter can use.
  56  * <code>
  57  * 012345678901234567890123456789...............{79}
  58  * IINNNN[DD:HHMMSS]tttttttttt.................t[EOL]
  59  *                 ]tttttttttt.................t[EOL]
  60  * [ 6  ][9+3extra ][       54                  ]
  61  * [18] + [61] = 79
  62  * </code>
  63  * 
  64  * Cs0000:223600: XMLCompiler: Software Detected Fault: SAXException mad
  65                   e it out of the builder.
  66  * @author Erich P. Gatejen
  67  * @version 1.0
  68  * <i>Version History</i>
  69  * <code>EPG - Rewrite - 27Apr03</code> 
  70  * 
  71  */
  72 public abstract class AutohitLogDrain implements Drain {
  73 
  74 	private boolean prettyFlag;
  75 	private boolean stampFlag;
  76 	private String lineSep;
  77 	private Calendar cachedCalendar;
  78 	private OutputStream output;
  79 	protected Writer myWriter;
  80 	private Formatter formatter;
  81 
  82 	static final String EMPTY_MESSAGE_STRING = "No text in entry.";
  83 
  84 	/**
  85 	 * Default constructor
  86 	 * You must init() the drain before it is valid.
  87 	 */
  88 	public AutohitLogDrain() {
  89 
  90 	}
  91 
  92 	/**
  93 	 * Post an item
  94 	 * Don't care about receipts
  95 	 * @return null -- always
  96 	 */
  97 	public Receipt post(Atom a) throws ChannelException {
  98 
  99 		// TODO do I really care if AutohitLogDrain requires a senderID
 100 		if (a.senderID == null) {
 101 			throw new ChannelException(
 102 				"AutohitLogDrain required senderID",
 103 				ChannelException.CODE_CHANNEL_DRAIN_REQUIRES_ID_ERROR);
 104 		}
 105 
 106 		try {
 107 
 108 			// prequalify the atom.  Print anything that has a string.
 109 			if (!(a.thing instanceof String))
 110 				return null;
 111 
 112 			setWriter(a.senderID);
 113 
 114 			if (stampFlag == true) {
 115 				if (prettyFlag == true) {
 116 					logStampPretty((Atom) a);
 117 				} else {
 118 					logStamp((Atom) a);
 119 				}
 120 			} else {
 121 				if (prettyFlag == true) {
 122 					logNoStampPretty((Atom) a);
 123 				} else {
 124 					logNoStamp((Atom) a);
 125 				}
 126 			}
 127 
 128 		} catch (ClassCastException ce) {
 129 			throw new ChannelException(
 130 				"AutohitLogDrain failed.  Something sent a TYPE.Log that isn't a String.",
 131 				AutohitErrorCodes.CODE_VM_SOFTWARE_DETECTED_FAULT);
 132 
 133 		} catch (Exception e) {
 134 			throw new ChannelException(
 135 				"AutohitLogDrain failed drain post.  " + e.getMessage(),
 136 				ChannelException.CODE_CHANNEL_FAULT,
 137 				e);
 138 		}
 139 		// don't care about receipts
 140 		return null;
 141 	}
 142 
 143 	/**
 144 	 *  Log with no timestamp
 145 	 * @param a the atom
 146 	 */
 147 	private void logNoStamp(Atom a) {
 148 		String s = (String) a.thing;
 149 		if (s.length() == 0)
 150 			s = EMPTY_MESSAGE_STRING;
 151 
 152 		try {
 153 
 154 			if (s.charAt(0) == ':') {
 155 				myWriter.write("      :");
 156 			} else {
 157 				myWriter.write(a.senderID);
 158 				myWriter.write(numericFormatter(a.numeric));
 159 				myWriter.write(":");
 160 			}
 161 			myWriter.write(s);
 162 			myWriter.write(lineSep);
 163 			myWriter.flush();
 164 
 165 		} catch (Exception e) {
 166 			this.dump(e);
 167 		}
 168 	}
 169 
 170 	/**
 171 	 *  Log with no timestamp
 172 	 * @param a the atom
 173 	 */
 174 	private void logNoStampPretty(Atom a) {
 175 
 176 		String s = (String) a.thing;
 177 		if (s.length() == 0)
 178 			s = EMPTY_MESSAGE_STRING;
 179 
 180 		try {
 181 			formatter.prefixedWriter(
 182 				a.senderID + numericFormatter(a.numeric) + "]",
 183 				"      ]",
 184 				s,
 185 				myWriter);
 186 		} catch (Exception e) {
 187 			this.dump(e);
 188 		}
 189 
 190 	}
 191 
 192 	/**
 193 	 *  Log with a timestamp with no pretty
 194 	 * @param a the atom
 195 	 * @throws ChannelException
 196 	 */
 197 	private void logStamp(Atom a) throws ChannelException {
 198 
 199 		String s = (String) a.thing;
 200 		if (s.length() == 0)
 201 			s = EMPTY_MESSAGE_STRING;
 202 
 203 		try {
 204 			if (s.charAt(0) == ':') {
 205 				myWriter.write("                ]");
 206 			} else {
 207 				myWriter.write(a.senderID);
 208 				myWriter.write(numericFormatter(a.numeric));
 209 				myWriter.write(this.timestampFormatter(a.stamp));
 210 			}
 211 			myWriter.write(s);
 212 			myWriter.write(lineSep);
 213 			myWriter.flush();
 214 
 215 		} catch (Exception e) {
 216 			this.dump(e);
 217 		}
 218 	}
 219 
 220 	/**
 221 	 *  Log with a timestamp and pretty format
 222 	 * @param a the atom
 223 	 */
 224 	private void logStampPretty(Atom a) throws ChannelException {
 225 
 226 		String s = (String) a.thing;
 227 		if (s.length() == 0)
 228 			s = EMPTY_MESSAGE_STRING;
 229 
 230 		try {
 231 			formatter.prefixedWriter(
 232 				a.senderID
 233 					+ numericFormatter(a.numeric)
 234 					+ this.timestampFormatter(a.stamp),
 235 				"                ]",
 236 				s,
 237 				myWriter);
 238 		} catch (Exception e) {
 239 			this.dump(e);
 240 		}
 241 	}
 242 
 243 	/**
 244 	 * This formats the the numeric into a four digit string.
 245 	 * @param n numeric value
 246 	 * @return a string representation of the numeric
 247 	 */
 248 	public String numericFormatter(int n) {
 249 
 250 		StringBuffer buf = new StringBuffer();
 251 
 252 		if (n < 0) {
 253 			return "UNDR";
 254 		} else if (n < 10) {
 255 			return "000" + n;
 256 		} else if (n < 100) {
 257 			return "00" + n;
 258 		} else if (n < 1000) {
 259 			return "0" + n;
 260 		} else if (n < 10000) {
 261 			return Integer.toString(n);
 262 		}
 263 		return "OVER";
 264 	}
 265 
 266 	/**
 267 	 * This formats the timestamp for stamped entries.  You can overload 
 268 	 * this if you want a different format.
 269 	 * This one will do [DD:HHMMSS]
 270 	 * @param stamp timestamp in milliseconds.  This formatter assumes it is the unmodified system time.
 271 	 * @return a string representation of the timestamp
 272 	 */
 273 	public String timestampFormatter(long stamp) {
 274 
 275 		int t;
 276 		cachedCalendar.setTimeInMillis(stamp);
 277 		StringBuffer buf = new StringBuffer(cachedCalendar.get(Calendar.DATE));
 278 		buf.append('[');
 279 
 280 		t = cachedCalendar.get(Calendar.DAY_OF_MONTH);
 281 		if (t < 10) {
 282 			buf.append("0" + t);
 283 		} else {
 284 			buf.append(t);
 285 		}
 286 		buf.append(':');
 287 
 288 		t = cachedCalendar.get(Calendar.HOUR_OF_DAY);
 289 		if (t < 10) {
 290 			buf.append("0" + t);
 291 		} else {
 292 			buf.append(t);
 293 		}
 294 		t = cachedCalendar.get(Calendar.MINUTE);
 295 		if (t < 10) {
 296 			buf.append("0" + t);
 297 		} else {
 298 			buf.append(t);
 299 		}
 300 		t = cachedCalendar.get(Calendar.SECOND);
 301 		if (t < 10) {
 302 			buf.append("0" + t);
 303 		} else {
 304 			buf.append(t);
 305 		}
 306 		buf.append(']');
 307 		return buf.toString();
 308 	}
 309 
 310 	/**
 311 	 *  Form this string into an entry.
 312 	 * @param s string
 313 	 * @return formed string
 314 	 */
 315 	protected String form(String s) {
 316 
 317 		StringBuffer t = new StringBuffer();
 318 		int runlen = formatter.lineLength - 10;
 319 
 320 		if (s.charAt(0) == ':') {
 321 			t.append("        ");
 322 			t.append(s);
 323 		} else if (prettyFlag) {
 324 
 325 			int rover = 0;
 326 			int endspot = s.length();
 327 
 328 			try {
 329 
 330 				while (rover < formatter.lineLimit) {
 331 					if ((rover + runlen) >= endspot) {
 332 						t.append(s.substring(rover, endspot));
 333 						break;
 334 					} else {
 335 						t.append(s.substring(rover, rover + runlen));
 336 						t.append(lineSep);
 337 						rover = rover + runlen;
 338 					}
 339 				}
 340 
 341 			} catch (IndexOutOfBoundsException e) {
 342 				// ignore this one.  :^)
 343 			}
 344 
 345 		} else {
 346 			t.append("--");
 347 			t.append(" : ");
 348 			t.append(s);
 349 		}
 350 		return t.toString();
 351 	}
 352 
 353 	/**
 354 	 * This sets the pretty flag.  This will try and keep log lines
 355 	 * at under 80 characters, applying wrapping where possible.
 356 	 * TRUE means pretty.  The default is FALSE.   Not syncronized.  It 
 357 	 * will take effect on the next log write.
 358 	 * @param b
 359 	 */
 360 	public void setPrettyFlag(boolean b) {
 361 		prettyFlag = b;
 362 	}
 363 
 364 	/**
 365 	 * This sets the timestamp flag.  This will tell it to timestamp the
 366 	 * entries.  TRUE means do it.  The default is FALSE.  Not syncronized.  It 
 367 	 * will take effect on the next log write.
 368 	 * @param b
 369 	 */
 370 	public void setTimestampFlag(boolean b) {
 371 		stampFlag = b;
 372 	}
 373 
 374 	/**
 375 	 * Set the max number characters printed per line
 376 	 * @param limit the number of characters printed in a line
 377 	 */
 378 	public void setLineLimit(int limit) {
 379 		formatter.lineLimit = limit;
 380 	}
 381 
 382 	
 383 	
 384 	/**
 385 	 *  Dump
 386 	 *  @param e Exception object, not an thrown exception
 387 	 */
 388 	private void dump(Exception e) {
 389 		System.out.println("!!!!!!!!!LOGGING SYSTEM FAILED!!!!!!!!!!!");
 390 		e.printStackTrace();
 391 	}
 392 
 393 	/**
 394 	 * The subclass uses this to set the Writer.  the Writer is the
 395 	 * field myWriter.
 396 	 * @param id
 397 	 */
 398 	public abstract void setWriter(String id) throws Exception;
 399 
 400 	/**
 401 	 * The subclass uses this to discard the Writer.  It says this id isn't
 402 	 * being used anymore.
 403 	 * @param id
 404 	 */
 405 	public abstract void discardWriter(String id) throws Exception;
 406 
 407 	/**
 408 	 * The subclass should implement this to do any initialization.
 409 	 */
 410 	public abstract void initchain();
 411 
 412 	/**
 413 	 *  Call this to initialize.  Need to provide at least a default Stream.
 414 	 */
 415 	public void init(OutputStream os) throws Exception {
 416 
 417 		output = os;
 418 		myWriter = new OutputStreamWriter(output);
 419 
 420 		prettyFlag = false;
 421 		stampFlag = false;
 422 		lineSep = System.getProperty("line.separator");
 423 
 424 		cachedCalendar = new GregorianCalendar();
 425 
 426 		formatter = new Formatter();
 427 		formatter.lineLimit = AutohitProperties.LOGS_ARBITRARY_ENTRY_LIMIT_DEFAULT;
 428 		formatter.lineLength = AutohitProperties.LOGS_LINE_SIZE_DEFAULT;
 429 		
 430 		// chain the initialization - don't change this!
 431 		this.initchain();
 432 	}
 433 
 434 	/**
 435 	 *  Call this to initialize.  Need to provide at least a default Stream.
 436 	 */
 437 	public void init(OutputStream os, int linesize) throws Exception {
 438 		this.init(os);
 439 		formatter.lineLength = linesize;
 440 	}
 441 	
 442 }