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}