package fitnesse.slim; import java.io.IOException; import java.io.OutputStream; import java.io.Writer; import java.security.CodeSource; import java.util.HashMap; import java.util.Map; import static util.FileUtil.CHARENCODING; public class StackTraceEnricher { private Map<String, ClassMetaInformation> elementInformation; public StackTraceEnricher() { this.elementInformation = new HashMap<>(); } public void printStackTrace(Throwable throwable) { try { printStackTrace(throwable, System.err); } catch (IOException ioe) { // Ignore. } } public void printStackTrace(Throwable throwable, OutputStream stream) throws IOException { stream.write(getStackTraceAsString(throwable).getBytes(CHARENCODING)); stream.flush(); } public void printStackTrace(Throwable throwable, Writer writer) throws IOException { writer.write(getStackTraceAsString(throwable)); writer.flush(); } public String getStackTraceAsString(Throwable throwable) { StringBuilder sb = new StringBuilder(); Throwable t = throwable; if (throwable.getStackTrace() == null || throwable.getStackTrace().length == 0) { t = throwable.fillInStackTrace(); } for (StackTraceElement ste : t.getStackTrace()) { sb.append("\n\tat ").append(ste.toString()); ClassMetaInformation cmi = getMetaInformation(ste); sb.append(" ["); sb.append(cmi.getLocation()); if (!cmi.getVersion().equals(ClassMetaInformation.UNKNOWN)) { sb.append(":").append(cmi.getVersion()); } sb.append("]"); } if (throwable.getCause() != null) { Throwable cause = throwable.getCause(); sb.append("\nCaused by: ").append(cause.getClass().getName()).append(": ").append(cause.getMessage()); sb.append(getStackTraceAsString(cause)); } return sb.toString(); } public String getVersion(Class<?> clazz) { return getMetaInformation(clazz).getVersion(); } public String getVersion(StackTraceElement element) { return getMetaInformation(element).getVersion(); } public String getLocation(Class<?> clazz) { return getMetaInformation(clazz).getLocation(); } public String getLocation(StackTraceElement element) { return getMetaInformation(element).getLocation(); } private ClassMetaInformation getMetaInformation(Class<?> clazz) { ClassMetaInformation information; if (elementInformation.containsKey(clazz.getName())) { information = elementInformation.get(clazz.getName()); } else { information = new ClassMetaInformation(clazz); elementInformation.put(clazz.getName(), information); } return information; } private ClassMetaInformation getMetaInformation(StackTraceElement element) { ClassMetaInformation information; if (elementInformation.containsKey(element.getClassName())) { information = elementInformation.get(element.getClassName()); } else { information = new ClassMetaInformation(element); elementInformation.put(element.getClassName(), information); } return information; } private static class ClassMetaInformation { private static final String UNKNOWN = "n/a"; private static final String[] PACKAGE_PREFIXES_RTJAR = {"java.", "javax.", "sun.", "sunw.", "javafx.", "com.sun."}; private String version = UNKNOWN; private String location = UNKNOWN; private String className = UNKNOWN; private String file = UNKNOWN; public ClassMetaInformation(Class<?> clazz) { analyse(clazz.getName()); } public ClassMetaInformation(StackTraceElement ste) { file = ste.getFileName(); analyse(ste.getClassName()); } private void analyse(String className) { try { Class<?> elementClass = loadClass(className, ClassLoader.getSystemClassLoader()); analyse(elementClass); } catch (ClassNotFoundException cnfe) { this.className = className; } } private void analyse(Class clazz) { className = clazz.getName(); version = getVersion(clazz); location = getLocation(clazz); } public String getVersion() { return version; } public String getLocation() { return location; } private static Class<?> loadClass(String className, ClassLoader classLoader) throws ClassNotFoundException { if (className == null || className.isEmpty()) { throw new ClassNotFoundException("Unable to load a class with an empty or null name."); } Class<?> resolvedClass = null; if (classLoader != null) { try { resolvedClass = classLoader.loadClass(className); } catch (ClassNotFoundException e) { // Ignore, try to resolve the class via other class loaders. } } if (resolvedClass == null) { ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); if (contextClassLoader != null) { resolvedClass = contextClassLoader.loadClass(className); } else { resolvedClass = StackTraceEnricher.class.getClassLoader().loadClass(className); } } return resolvedClass; } private static String getLocation(Class<?> clazz) { String location = UNKNOWN; if (clazz != null) { try { CodeSource codeSource = clazz.getProtectionDomain().getCodeSource(); if (codeSource != null) { String fullLocation = codeSource.getLocation().toString(); if (isDirectory(fullLocation)) { location = fullLocation; } else { location = removeParentDirectories(fullLocation); } } else { for (String rtPackage : PACKAGE_PREFIXES_RTJAR) { if (clazz.getName().startsWith(rtPackage)) { location = "rt.jar"; break; } } } } catch (Exception e) { // Ignore. } } return location; } private static String getVersion(Class<?> clazz) { String version = UNKNOWN; if (clazz != null) { try { Package pack = clazz.getPackage(); if (pack != null) { if (pack.getImplementationVersion() != null) { version = pack.getImplementationVersion(); } else if (pack.getSpecificationVersion() != null) { version = pack.getSpecificationVersion(); } } } catch (Exception e) { // Ignore. } } return version; } private static String removeParentDirectories(String path) { String parsedPath = path; if (path.contains("/")) { parsedPath = removeParentDirectories(path, "/"); } else if (path.contains("\\")) { parsedPath = removeParentDirectories(path, "\\"); } return parsedPath; } private static String removeParentDirectories(String path, String separator) { String parsedPath = path; if (path.contains(separator) && !path.endsWith(separator) || (path.indexOf(separator) < path.lastIndexOf (separator))) { parsedPath = parsedPath.substring(parsedPath.indexOf(separator) + 1); parsedPath = removeParentDirectories(parsedPath, separator); } return parsedPath; } private static boolean isDirectory(String path) { return path != null && (path.endsWith("/") || (path.endsWith("\\"))); } } }