001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.log4j; 018 019import org.apache.log4j.spi.ThrowableRenderer; 020 021import java.io.File; 022import java.lang.reflect.Method; 023import java.net.URL; 024import java.security.CodeSource; 025import java.util.HashMap; 026import java.util.Map; 027 028/** 029 * Enhanced implementation of ThrowableRenderer. Uses Throwable.getStackTrace 030 * if running on JDK 1.4 or later and delegates to DefaultThrowableRenderer.render 031 * on earlier virtual machines. 032 * 033 * @since 1.2.16 034 */ 035public final class EnhancedThrowableRenderer implements ThrowableRenderer { 036 /** 037 * Throwable.getStackTrace() method. 038 */ 039 private Method getStackTraceMethod; 040 /** 041 * StackTraceElement.getClassName() method. 042 */ 043 private Method getClassNameMethod; 044 045 046 /** 047 * Construct new instance. 048 */ 049 public EnhancedThrowableRenderer() { 050 try { 051 Class[] noArgs = null; 052 getStackTraceMethod = Throwable.class.getMethod("getStackTrace", noArgs); 053 Class ste = Class.forName("java.lang.StackTraceElement"); 054 getClassNameMethod = ste.getMethod("getClassName", noArgs); 055 } catch(Exception ex) { 056 } 057 } 058 059 /** 060 * {@inheritDoc} 061 */ 062 public String[] doRender(final Throwable throwable) { 063 if (getStackTraceMethod != null) { 064 try { 065 Object[] noArgs = null; 066 Object[] elements = (Object[]) getStackTraceMethod.invoke(throwable, noArgs); 067 String[] lines = new String[elements.length + 1]; 068 lines[0] = throwable.toString(); 069 Map classMap = new HashMap(); 070 for(int i = 0; i < elements.length; i++) { 071 lines[i+1] = formatElement(elements[i], classMap); 072 } 073 return lines; 074 } catch(Exception ex) { 075 } 076 } 077 return DefaultThrowableRenderer.render(throwable); 078 } 079 080 /** 081 * Format one element from stack trace. 082 * @param element element, may not be null. 083 * @param classMap map of class name to location. 084 * @return string representation of element. 085 */ 086 private String formatElement(final Object element, final Map classMap) { 087 StringBuffer buf = new StringBuffer("\tat "); 088 buf.append(element); 089 try { 090 String className = getClassNameMethod.invoke(element, (Object[]) null).toString(); 091 Object classDetails = classMap.get(className); 092 if (classDetails != null) { 093 buf.append(classDetails); 094 } else { 095 Class cls = findClass(className); 096 int detailStart = buf.length(); 097 buf.append('['); 098 try { 099 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}