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: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.openide.util;
042:
043: import java.io.IOException;
044: import java.io.PrintStream;
045: import java.io.PrintWriter;
046: import java.io.StringWriter;
047: import java.util.ArrayList;
048: import java.util.Enumeration;
049: import java.util.List;
050: import java.util.Map;
051: import java.util.ResourceBundle;
052: import java.util.WeakHashMap;
053: import java.util.concurrent.Callable;
054: import java.util.logging.Level;
055: import java.util.logging.LogRecord;
056: import java.util.logging.Logger;
057:
058: /** Useful utility and methods to work with exceptions as
059: * described in detail in the <a href="@TOP@/org/openide/util/doc-files/logging.html">NetBeans logging guide</a>.
060: * Allows to annotate exceptions with messages, extract such messages
061: * and provides a common utility method to report an exception.
062: *
063: *
064: * @since 7.2
065: */
066: public final class Exceptions extends Object {
067: private Exceptions() {
068: }
069:
070: private static final String LOC_MSG_PLACEHOLDER = "msg"; // NOI18N
071:
072: /** Attaches additional message to given exception. This message will
073: * be visible when one does <code>e.printStackTrace()</code>.
074: *
075: * @param e exception to annotate
076: * @param msg the message to add to the exception
077: * @return the exception <code>e</code>
078: */
079: public static <E extends Throwable> E attachMessage(E e, String msg) {
080: AnnException ae = AnnException.findOrCreate(e, true);
081: LogRecord rec = new LogRecord(Level.ALL, msg);
082: ae.addRecord(rec);
083: return e;
084: }
085:
086: /** Attaches additional localized message to given exception. This message
087: * can be extracted later by using {@link #findLocalizedMessage}.
088: *
089: * @param e exception to annotate
090: * @param localizedMessage the localized message to add to the exception
091: * @return the exception <code>e</code>
092: */
093: public static <E extends Throwable> E attachLocalizedMessage(E e,
094: final String localizedMessage) {
095: AnnException ae = AnnException.findOrCreate(e, true);
096: LogRecord rec = new LogRecord(Level.ALL, LOC_MSG_PLACEHOLDER);
097: ResourceBundle rb = new ResourceBundle() {
098: public Object handleGetObject(String key) {
099: if (LOC_MSG_PLACEHOLDER.equals(key)) {
100: return localizedMessage;
101: } else {
102: return null;
103: }
104: }
105:
106: public Enumeration<String> getKeys() {
107: return Enumerations.singleton(LOC_MSG_PLACEHOLDER);
108: }
109: };
110: rec.setResourceBundle(rb);
111: ae.addRecord(rec);
112: return e;
113: }
114:
115: /** Extracts previously attached localized message for a given throwable.
116: * Complements {@link #attachLocalizedMessage}.
117: *
118: * @param t the exception to search for a message in
119: * @return localized message attached to provided exception or <code>null</code>
120: * if no such message has been attached
121: */
122: public static String findLocalizedMessage(Throwable t) {
123: while (t != null) {
124: String msg;
125: AnnException extra = AnnException.extras.get(t);
126: if (extra != null) {
127: msg = extractLocalizedMessage(extra);
128: } else {
129: msg = extractLocalizedMessage(t);
130: }
131:
132: if (msg != null) {
133: return msg;
134: }
135:
136: t = t.getCause();
137: }
138: return null;
139: }
140:
141: private static String extractLocalizedMessage(final Throwable t) {
142: String msg = null;
143: if (t instanceof Callable) {
144: Object res = null;
145: try {
146: res = ((Callable) t).call();
147: } catch (Exception ex) {
148: Logger.global.log(Level.WARNING, null, t);
149: }
150: if (res instanceof LogRecord[]) {
151: for (LogRecord r : (LogRecord[]) res) {
152: ResourceBundle b = r.getResourceBundle();
153: if (b != null) {
154: msg = b.getString(r.getMessage());
155: break;
156: }
157: }
158: }
159: }
160: return msg;
161: }
162:
163: /** Notifies an exception with a severe level. Such exception is going
164: * to be printed to log file and possibly also notified to alarm the
165: * user somehow.
166: *
167: * @param t the exception to notify
168: */
169: public static void printStackTrace(Throwable t) {
170: AnnException extra = AnnException.extras.get(t);
171: if (extra != null) {
172: assert t == extra.getCause();
173: t = extra;
174: }
175: Logger.global.log(OwnLevel.UNKNOWN, null, t);
176: }
177:
178: /** An exception that has a log record associated with itself, so
179: * the NbErrorManager can extract info about the annotation.
180: */
181: private static final class AnnException extends Exception implements
182: Callable<LogRecord[]> {
183: private List<LogRecord> records;
184:
185: private AnnException() {
186: super ();
187: }
188:
189: private AnnException(String msg) {
190: super (msg);
191: }
192:
193: @Override
194: public String getMessage() {
195: StringBuilder sb = new StringBuilder();
196: String sep = "";
197: for (LogRecord r : records) {
198: String m = r.getMessage();
199: if (m != null && !m.equals(LOC_MSG_PLACEHOLDER)) {
200: sb.append(sep);
201: sb.append(m);
202: sep = "\n";
203: }
204: }
205: return sb.toString();
206: }
207:
208: /** additional mapping from throwables that refuse initCause call */
209: private static Map<Throwable, AnnException> extras = new WeakHashMap<Throwable, AnnException>();
210:
211: static AnnException findOrCreate(Throwable t, boolean create) {
212: if (t instanceof AnnException) {
213: return (AnnException) t;
214: }
215: if (t.getCause() == null) {
216: if (create) {
217: try {
218: t.initCause(new AnnException());
219: } catch (IllegalStateException x) {
220: AnnException ann = extras.get(t);
221: if (ann == null) {
222: ann = new AnnException(t.getMessage());
223: ann.initCause(t);
224: Logger
225: .getLogger(
226: Exceptions.class.getName())
227: .log(
228: Level.FINE,
229: "getCause was null yet initCause failed for "
230: + t, x);
231: extras.put(t, ann);
232: }
233: return ann;
234: }
235: }
236: return (AnnException) t.getCause();
237: }
238: return findOrCreate(t.getCause(), create);
239: }
240:
241: public synchronized void addRecord(LogRecord rec) {
242: if (records == null) {
243: records = new ArrayList<LogRecord>();
244: }
245: records.add(rec);
246: }
247:
248: public LogRecord[] call() {
249: List<LogRecord> r = records;
250: LogRecord[] empty = new LogRecord[0];
251: return r == null ? empty : r.toArray(empty);
252: }
253:
254: @Override
255: public void printStackTrace(PrintStream s) {
256: super .printStackTrace(s);
257: logRecords(s);
258: }
259:
260: @Override
261: public void printStackTrace(PrintWriter s) {
262: super .printStackTrace(s);
263: logRecords(s);
264: }
265:
266: @Override
267: public Throwable fillInStackTrace() {
268: return this ;
269: }
270:
271: @Override
272: public String toString() {
273: return getMessage();
274: }
275:
276: private void logRecords(Appendable a) {
277: List<LogRecord> r = records;
278: if (r == null) {
279: return;
280: }
281: try {
282:
283: for (LogRecord log : r) {
284: if (log.getMessage() != null) {
285: a.append(log.getMessage()).append("\n");
286: ;
287: }
288: if (log.getThrown() != null) {
289: StringWriter w = new StringWriter();
290: log.getThrown().printStackTrace(
291: new PrintWriter(w));
292: a.append(w.toString()).append("\n");
293: }
294: }
295: } catch (IOException ex) {
296: ex.printStackTrace();
297: }
298: }
299: } // end AnnException
300:
301: private static final class OwnLevel extends Level {
302: public static final Level UNKNOWN = new OwnLevel("SEVERE",
303: Level.SEVERE.intValue() + 1); // NOI18N
304:
305: private OwnLevel(String s, int i) {
306: super (s, i);
307: }
308: } // end of OwnLevel
309: }
|