/* * The MIT License * * Copyright (c) 2010-2011, CloudBees, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package hudson.console; import hudson.Extension; import hudson.MarkupText; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Placed on the beginning of the exception stack trace produced by Hudson, which in turn produces hyperlinked stack trace. * * <p> * Exceptions in the user code (like junit etc) should be handled differently. This is only for exceptions * that occur inside Hudson. * * @author Kohsuke Kawaguchi * @since 1.349 */ public class HudsonExceptionNote extends ConsoleNote<Object> { @Override public ConsoleAnnotator annotate(Object context, MarkupText text, int charPos) { // An exception stack trace looks like this: // org.acme.FooBarException: message // <TAB>at org.acme.Foo.method(Foo.java:123) // Caused by: java.lang.ClassNotFoundException: String line = text.getText(); int end = line.indexOf(':',charPos); if (end<0) { if (CLASSNAME.matcher(line.substring(charPos)).matches()) end = line.length(); else return null; // unexpected format. abort. } text.addHyperlinkLowKey(charPos,end,annotateClassName(line.substring(charPos,end))); return new ConsoleAnnotator() { public ConsoleAnnotator annotate(Object context, MarkupText text) { String line = text.getText(); Matcher m = STACK_TRACE_ELEMENT.matcher(line); if (m.find()) {// allow the match to happen in the middle of a line to cope with prefix. Ant and Maven put them, among many other tools. text.addHyperlinkLowKey(m.start()+4,m.end(),annotateMethodName(m.group(1),m.group(2),m.group(3),Integer.parseInt(m.group(4)))); return this; } int idx = line.indexOf(CAUSED_BY); if (idx>=0) { int s = idx + CAUSED_BY.length(); int e = line.indexOf(':', s); if (e<0) e = line.length(); text.addHyperlinkLowKey(s,e,annotateClassName(line.substring(s,e))); return this; } if (AND_MORE.matcher(line).matches()) return this; // looks like we are done with the stack trace return null; } }; } // TODO; separate out the annotations and mark up private String annotateMethodName(String className, String methodName, String sourceFileName, int lineNumber) { return "http://stacktrace.hudson-ci.org/search/?query="+className+'.'+methodName+"&entity=method"; } private String annotateClassName(String className) { return "http://stacktrace.hudson-ci.org/search?query="+className; } @Extension public static final class DescriptorImpl extends ConsoleAnnotationDescriptor { @Override public String getDisplayName() { return "Exception Stack Trace"; } } /** * Regular expression that represents a valid class name. */ private static final String CLASSNAME_PATTERN = "[\\p{L}0-9$_.]+"; private static final Pattern CLASSNAME = Pattern.compile(CLASSNAME_PATTERN+"\r?\n?"); /** * Matches to the line like "\tat org.acme.Foo.method(File.java:123)" * and captures class name, method name, source file name, and line number separately. */ private static final Pattern STACK_TRACE_ELEMENT = Pattern.compile("\tat ("+CLASSNAME_PATTERN+")\\.([\\p{L}0-9$_<>]+)\\((\\S+):([0-9]+)\\)"); private static final String CAUSED_BY = "Caused by: "; private static final Pattern AND_MORE = Pattern.compile("\t... [0-9]+ more\n"); }