001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
027: */
028:
029: package org.netbeans.lib.uihandler;
030:
031: import java.io.ByteArrayInputStream;
032: import java.io.IOException;
033: import java.io.InputStream;
034: import java.io.OutputStream;
035: import java.io.PushbackInputStream;
036: import java.io.SequenceInputStream;
037: import java.util.ArrayList;
038: import java.util.Collections;
039: import java.util.EnumMap;
040: import java.util.Enumeration;
041: import java.util.LinkedList;
042: import java.util.List;
043: import java.util.Map;
044: import java.util.MissingResourceException;
045: import java.util.Queue;
046: import java.util.ResourceBundle;
047: import java.util.logging.Formatter;
048: import java.util.logging.Handler;
049: import java.util.logging.Level;
050: import java.util.logging.LogRecord;
051: import java.util.logging.Logger;
052: import java.util.zip.GZIPInputStream;
053: import javax.xml.parsers.ParserConfigurationException;
054: import javax.xml.parsers.SAXParser;
055: import javax.xml.parsers.SAXParserFactory;
056: import org.openide.util.NbBundle;
057: import org.xml.sax.Attributes;
058: import org.xml.sax.Locator;
059: import org.xml.sax.SAXException;
060: import org.xml.sax.SAXParseException;
061: import org.xml.sax.helpers.DefaultHandler;
062:
063: /** Can persist and read log records from streams.
064: *
065: * @author Jaroslav Tulach
066: */
067: public final class LogRecords {
068: private LogRecords() {
069: }
070:
071: private static final Logger LOG = Logger.getLogger(LogRecords.class
072: .getName());
073:
074: private static final Formatter FORMATTER = new LogFormatter();
075:
076: /** Inspects the log record and decorates its content.
077: * @param r the log record
078: * @param d callback to be called with inspected values
079: */
080: public static void decorate(LogRecord r, Decorable d) {
081: Decorations.decorate(r, d);
082: }
083:
084: public static void write(OutputStream os, LogRecord rec)
085: throws IOException {
086: String formated = FORMATTER.format(rec);
087: byte[] arr = formated.getBytes("utf-8");
088: os.write(arr);
089: }
090:
091: public static void scan(InputStream is, Handler h)
092: throws IOException {
093: PushbackInputStream wrap = new PushbackInputStream(is, 32);
094: byte[] arr = new byte[5];
095: int len = wrap.read(arr);
096: if (len == -1) {
097: return;
098: }
099: wrap.unread(arr, 0, len);
100: if (arr[0] == 0x1f && arr[1] == -117) {
101: wrap = new PushbackInputStream(new GZIPInputStream(wrap),
102: 32);
103: len = wrap.read(arr);
104: if (len == -1) {
105: return;
106: }
107: wrap.unread(arr, 0, len);
108: }
109:
110: if (arr[0] == '<' && arr[1] == '?' && arr[2] == 'x'
111: && arr[3] == 'm' && arr[4] == 'l') {
112: is = wrap;
113: } else {
114: ByteArrayInputStream header = new ByteArrayInputStream(
115: "<?xml version='1.0' encoding='UTF-8'?><uigestures version='1.0'>"
116: .getBytes());
117: ByteArrayInputStream footer = new ByteArrayInputStream(
118: "</uigestures>".getBytes());
119: is = new SequenceInputStream(new SequenceInputStream(
120: header, wrap), footer);
121: }
122:
123: SAXParserFactory f = SAXParserFactory.newInstance();
124: f.setValidating(false);
125: SAXParser p;
126: try {
127: f
128: .setFeature(
129: "http://apache.org/xml/features/continue-after-fatal-error",
130: true); // NOI18N
131: p = f.newSAXParser();
132: } catch (ParserConfigurationException ex) {
133: LOG.log(Level.SEVERE, null, ex);
134: throw (IOException) new IOException(ex.getMessage())
135: .initCause(ex);
136: } catch (SAXException ex) {
137: LOG.log(Level.SEVERE, null, ex);
138: throw (IOException) new IOException(ex.getMessage())
139: .initCause(ex);
140: }
141:
142: Parser parser = new Parser(h);
143: try {
144: p.parse(is, parser);
145: } catch (SAXException ex) {
146: LOG.log(Level.WARNING, null, ex);
147: throw (IOException) new IOException(ex.getMessage())
148: .initCause(ex);
149: } catch (InternalError error) {
150: LOG.log(Level.WARNING, "INPUT FILE CORRUPTION", error);
151: } catch (IOException ex) {
152: throw ex;
153: } catch (RuntimeException ex) {
154: LOG.log(Level.WARNING, "INPUT FILE CORRUPTION", ex);
155: }
156: }
157:
158: static Level parseLevel(String lev) {
159: return "USER".equals(lev) ? Level.SEVERE : Level.parse(lev);
160: }
161:
162: private static final class Parser extends DefaultHandler {
163: private Handler callback;
164:
165: private static enum Elem {
166: UIGESTURES, RECORD, DATE, MILLIS, SEQUENCE, LEVEL, THREAD, MESSAGE, KEY, PARAM, FRAME, CLASS, METHOD, LOGGER, EXCEPTION, LINE, CATALOG, MORE, FILE;
167:
168: public String parse(Map<Elem, String> values) {
169: String v = values.get(this );
170: return v;
171: }
172: }
173:
174: private Map<Elem, String> values = new EnumMap<Elem, String>(
175: Elem.class);
176: private Elem current;
177: private FakeException currentEx;
178: private Queue<FakeException> exceptions;
179: private List<String> params;
180: private StringBuilder chars = new StringBuilder();
181: private int fatalErrors;
182:
183: public Parser(Handler c) {
184: this .callback = c;
185: }
186:
187: public void setDocumentLocator(Locator locator) {
188: }
189:
190: public void startDocument() throws SAXException {
191: }
192:
193: public void endDocument() throws SAXException {
194: callback.flush();
195: }
196:
197: public void startPrefixMapping(String prefix, String uri)
198: throws SAXException {
199: }
200:
201: public void endPrefixMapping(String prefix) throws SAXException {
202: }
203:
204: public void startElement(String uri, String localName,
205: String qName, Attributes atts) throws SAXException {
206: if (LOG.isLoggable(Level.FINEST)) {
207: LOG.log(Level.FINEST,
208: "uri: {0} localName: {1} qName: {2} atts: {3}",
209: new Object[] { uri, localName, qName, atts });
210: }
211:
212: try {
213: current = Elem.valueOf(qName.toUpperCase());
214: if (current == Elem.EXCEPTION) {
215: currentEx = new FakeException(
216: new EnumMap<Elem, String>(values));
217: }
218: } catch (IllegalArgumentException ex) {
219: LOG.log(Level.FINE, "Uknown tag " + qName, ex);
220: current = null;
221: }
222: chars = new StringBuilder();
223: }
224:
225: public void endElement(String uri, String localName,
226: String qName) throws SAXException {
227: if (current != null) {
228: String v = chars.toString();
229: values.put(current, v);
230: if (current == Elem.PARAM) {
231: if (params == null) {
232: params = new ArrayList<String>();
233: }
234: params.add(v);
235: if (params.size() > 1500) {
236: LOG
237: .severe("Too long params when reading a record. Deleting few. Msg: "
238: + Elem.MESSAGE.parse(values)); // NOI18N
239: for (String p : params) {
240: LOG.fine(p);
241: }
242: params.clear();
243: }
244: }
245: }
246: current = null;
247: chars = new StringBuilder();
248:
249: if (currentEx != null && currentEx.values != null) {
250: if ("frame".equals(qName)) { // NOI18N
251: String line = Elem.LINE.parse(values);
252: StackTraceElement elem = new StackTraceElement(
253: Elem.CLASS.parse(values), Elem.METHOD
254: .parse(values), Elem.FILE
255: .parse(values), line == null ? -1
256: : Integer.parseInt(line));
257: currentEx.trace.add(elem);
258: values.remove(Elem.CLASS);
259: values.remove(Elem.METHOD);
260: values.remove(Elem.LINE);
261: }
262: if ("exception".equals(qName)) {
263: currentEx.message = values.get(Elem.MESSAGE);
264: String more = values.get(Elem.MORE);
265: if (more != null)
266: currentEx.more = Integer.parseInt(more);
267: if (exceptions == null) {
268: exceptions = new LinkedList<FakeException>();
269: }
270: exceptions.add(currentEx);
271: values = currentEx.values;
272: currentEx = null;
273: }
274: return;
275: }
276:
277: if ("record".equals(qName)) { // NOI18N
278: String millis = Elem.MILLIS.parse(values);
279: String seq = Elem.SEQUENCE.parse(values);
280: String lev = Elem.LEVEL.parse(values);
281: String thread = Elem.THREAD.parse(values);
282: String msg = Elem.MESSAGE.parse(values);
283: String key = Elem.KEY.parse(values);
284: String catalog = Elem.CATALOG.parse(values);
285:
286: if (lev != null) {
287: LogRecord r = new LogRecord(parseLevel(lev),
288: key != null && catalog != null ? key : msg);
289: try {
290: r.setThreadID(Integer.parseInt(thread));
291: } catch (NumberFormatException ex) {
292: LOG.log(Level.WARNING, ex.getMessage(), ex);
293: }
294: r.setSequenceNumber(Long.parseLong(seq));
295: r.setMillis(Long.parseLong(millis));
296: r.setResourceBundleName(key);
297: if (catalog != null && key != null) {
298: r.setResourceBundleName(catalog);
299: if (!"<null>".equals(catalog)) { // NOI18N
300: try {
301: ResourceBundle b = NbBundle
302: .getBundle(catalog);
303: b.getObject(key);
304: // ok, the key is there
305: r.setResourceBundle(b);
306: } catch (MissingResourceException e) {
307: LOG
308: .log(
309: Level.CONFIG,
310: "Cannot find resource bundle {0} for key {1}",
311: new Object[] { catalog,
312: key });
313: r.setResourceBundle(new FakeBundle(key,
314: msg));
315: }
316: } else {
317: LOG
318: .log(
319: Level.CONFIG,
320: "Cannot find resource bundle <null> for key {1}",
321: key);
322: }
323: }
324: if (params != null) {
325: r.setParameters(params.toArray());
326: }
327: if (exceptions != null) {
328: r.setThrown(createThrown(null));
329: // exceptions = null; should be empty after poll
330: }
331:
332: callback.publish(r);
333: }
334:
335: currentEx = null;
336: params = null;
337: values.clear();
338: }
339:
340: }
341:
342: /** set first element of exceptions as a result of this calling and
343: * recursively fill it's cause
344: */
345: private FakeException createThrown(FakeException last) {
346: if (exceptions.size() == 0) {
347: return null;
348: }
349: FakeException result = exceptions.poll();
350: if ((result != null) && (result.getMore() != 0)) {
351: assert last != null : "IF MORE IS NOT 0, LAST MUST BE SET NOT NULL";
352: StackTraceElement[] trace = last.getStackTrace();
353: for (int i = trace.length - result.getMore(); i < trace.length; i++) {
354: result.trace.add(trace[i]);// fill the rest of stacktrace
355: }
356: }
357: FakeException cause = createThrown(result);
358: result.initCause(cause);
359: return result;
360: }
361:
362: public void characters(char[] ch, int start, int length)
363: throws SAXException {
364: chars.append(ch, start, length);
365: }
366:
367: public void ignorableWhitespace(char[] ch, int start, int length)
368: throws SAXException {
369: }
370:
371: public void processingInstruction(String target, String data)
372: throws SAXException {
373: }
374:
375: public void skippedEntity(String name) throws SAXException {
376: }
377:
378: public void fatalError(SAXParseException e) throws SAXException {
379: if (fatalErrors++ > 1000) {
380: throw e;
381: }
382: }
383:
384: }
385:
386: private static final class FakeBundle extends ResourceBundle {
387: private String key;
388: private String value;
389:
390: public FakeBundle(String key, String value) {
391: this .key = key;
392: this .value = value;
393: }
394:
395: protected Object handleGetObject(String arg0) {
396: if (key.equals(arg0)) {
397: return value;
398: } else {
399: return null;
400: }
401: }
402:
403: public Enumeration<String> getKeys() {
404: return Collections.enumeration(Collections.singleton(key));
405: }
406: } // end of FakeBundle
407:
408: private static final class FakeException extends Exception {
409: final List<StackTraceElement> trace = new ArrayList<StackTraceElement>();
410: Map<Parser.Elem, String> values;
411: String message;
412: int more;
413:
414: public FakeException(Map<Parser.Elem, String> values) {
415: this .values = values;
416: more = 0;
417: }
418:
419: public StackTraceElement[] getStackTrace() {
420: return trace.toArray(new StackTraceElement[0]);
421: }
422:
423: public String getMessage() {
424: return message;
425: }
426:
427: public int getMore() {
428: return more;
429: }
430:
431: /**
432: * org.netbeans.lib.uihandler.LogRecords$FakeException: NullPointerException ...
433: * is not the best message - it's better to suppress FakeException
434: */
435: public String toString() {
436: return message;
437: }
438:
439: } // end of FakeException
440: }
|