1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.log4j;
18
19 import org.apache.log4j.spi.ThrowableRenderer;
20
21 import java.io.File;
22 import java.lang.reflect.Method;
23 import java.net.URL;
24 import java.security.CodeSource;
25 import java.util.HashMap;
26 import java.util.Map;
27
28 /**
29 * Enhanced implementation of ThrowableRenderer. Uses Throwable.getStackTrace
30 * if running on JDK 1.4 or later and delegates to DefaultThrowableRenderer.render
31 * on earlier virtual machines.
32 *
33 * @since 1.2.16
34 */
35 public final class EnhancedThrowableRenderer implements ThrowableRenderer {
36 /**
37 * Throwable.getStackTrace() method.
38 */
39 private Method getStackTraceMethod;
40 /**
41 * StackTraceElement.getClassName() method.
42 */
43 private Method getClassNameMethod;
44
45
46 /**
47 * Construct new instance.
48 */
49 public EnhancedThrowableRenderer() {
50 try {
51 Class[] noArgs = null;
52 getStackTraceMethod = Throwable.class.getMethod("getStackTrace", noArgs);
53 Class ste = Class.forName("java.lang.StackTraceElement");
54 getClassNameMethod = ste.getMethod("getClassName", noArgs);
55 } catch(Exception ex) {
56 }
57 }
58
59 /**
60 * {@inheritDoc}
61 */
62 public String[] doRender(final Throwable throwable) {
63 if (getStackTraceMethod != null) {
64 try {
65 Object[] noArgs = null;
66 Object[] elements = (Object[]) getStackTraceMethod.invoke(throwable, noArgs);
67 String[] lines = new String[elements.length + 1];
68 lines[0] = throwable.toString();
69 Map classMap = new HashMap();
70 for(int i = 0; i < elements.length; i++) {
71 lines[i+1] = formatElement(elements[i], classMap);
72 }
73 return lines;
74 } catch(Exception ex) {
75 }
76 }
77 return DefaultThrowableRenderer.render(throwable);
78 }
79
80 /**
81 * Format one element from stack trace.
82 * @param element element, may not be null.
83 * @param classMap map of class name to location.
84 * @return string representation of element.
85 */
86 private String formatElement(final Object element, final Map classMap) {
87 StringBuffer buf = new StringBuffer("\tat ");
88 buf.append(element);
89 try {
90 String className = getClassNameMethod.invoke(element, (Object[]) null).toString();
91 Object classDetails = classMap.get(className);
92 if (classDetails != null) {
93 buf.append(classDetails);
94 } else {
95 Class cls = findClass(className);
96 int detailStart = buf.length();
97 buf.append('[');
98 try {
99 CodeSource source = cls.getProtectionDomain().getCodeSource();
100 if (source != null) {
101 URL locationURL = source.getLocation();
102 if (locationURL != null) {
103 //
104 // if a file: URL
105 //
106 if ("file".equals(locationURL.getProtocol())) {
107 String path = locationURL.getPath();
108 if (path != null) {
109 //
110 // find the last file separator character
111 //
112 int lastSlash = path.lastIndexOf('/');
113 int lastBack = path.lastIndexOf(File.separatorChar);
114 if (lastBack > lastSlash) {
115 lastSlash = lastBack;
116 }
117 //
118 // if no separator or ends with separator (a directory)
119 // then output the URL, otherwise just the file name.
120 //
121 if (lastSlash <= 0 || lastSlash == path.length() - 1) {
122 buf.append(locationURL);
123 } else {
124 buf.append(path.substring(lastSlash + 1));
125 }
126 }
127 } else {
128 buf.append(locationURL);
129 }
130 }
131 }
132 } catch(SecurityException ex) {
133 }
134 buf.append(':');
135 Package pkg = cls.getPackage();
136 if (pkg != null) {
137 String implVersion = pkg.getImplementationVersion();
138 if (implVersion != null) {
139 buf.append(implVersion);
140 }
141 }
142 buf.append(']');
143 classMap.put(className, buf.substring(detailStart));
144 }
145 } catch(Exception ex) {
146 }
147 return buf.toString();
148 }
149
150 /**
151 * Find class given class name.
152 * @param className class name, may not be null.
153 * @return class, will not be null.
154 * @throws ClassNotFoundException thrown if class can not be found.
155 */
156 private Class findClass(final String className) throws ClassNotFoundException {
157 try {
158 return Thread.currentThread().getContextClassLoader().loadClass(className);
159 } catch (ClassNotFoundException e) {
160 try {
161 return Class.forName(className);
162 } catch (ClassNotFoundException e1) {
163 return getClass().getClassLoader().loadClass(className);
164 }
165 }
166 }
167
168 }