/* * This file is part of the Haven & Hearth game client. * Copyright (C) 2009 Fredrik Tolf <fredrik@dolda2000.com>, and * Björn Johannessen <johannessen.bjorn@gmail.com> * * Redistribution and/or modification of this file is subject to the * terms of the GNU Lesser General Public License, version 3, as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * Other parts of this source tree adhere to other copying * rights. Please see the file `COPYING' in the root directory of the * source tree for details. * * A copy the GNU Lesser General Public License is distributed along * with the source tree of which this file is a part in the file * `doc/LPGL-3'. If it is missing for any reason, please see the Free * Software Foundation's website at <http://www.fsf.org/>, or write * to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, MA 02111-1307 USA */ package haven.error; import java.io.*; import java.util.*; import java.util.regex.*; import java.text.*; public class HtmlReporter { public static final DateFormat dfmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static final NumberFormat ifmt = NumberFormat.getInstance(); public static final String[] idxprops = { "java.vendor", "java.version", "os.arch", "os.name", "os.version", "gl.vendor", "thnm", "usr", }; public static final Class[] boring = { RuntimeException.class, javax.media.opengl.GLException.class, }; public static class ErrorIdentity implements Comparable<ErrorIdentity> { public String jarrev; public Throwable t; private Pattern[] ignclass = new Pattern[] { Pattern.compile("sun\\.reflect\\..*"), Pattern.compile("java\\.lang\\.reflect\\..*"), }; private int equals(String a, String b) { if((a == null) && (b == null)) return(0); if(a == null) return(-1); if(b == null) return(1); return(a.compareTo(b)); } public int stcmp(StackTraceElement a, StackTraceElement b) { int sc = equals(a.getFileName(), b.getFileName()); if(sc != 0) return(sc); sc = equals(a.getClassName(), b.getClassName()); if(sc != 0) return(sc); sc = equals(a.getMethodName(), b.getMethodName()); if(sc != 0) return(sc); if(a.getLineNumber() != b.getLineNumber()) return(a.getLineNumber() - b.getLineNumber()); return(0); } public int thcmp(Throwable a, Throwable b) { int sc = equals(a.getClass().getName(), b.getClass().getName()); if(sc != 0) return(sc); StackTraceElement[] at = a.getStackTrace(), bt = b.getStackTrace(); int ai = 0, bi = 0; while(true) { boolean ad = ai >= at.length, bd = bi >= bt.length; if(ad && bd) break; else if(ad && !bd) return(-1); else if(!ad && bd) return(1); ign: while(true) { for(Pattern ignp : ignclass) { Matcher ma = ignp.matcher(at[ai].getClassName()); if(ma.matches()) { ai++; continue ign; } Matcher mb = ignp.matcher(bt[bi].getClassName()); if(mb.matches()) { bi++; continue ign; } } break; } sc = stcmp(at[ai++], bt[bi++]); if(sc != 0) return(sc); } if((a.getCause() == null) && (b.getCause() == null)) return(0); if(a.getCause() == null) return(-1); if(b.getCause() == null) return(1); return(thcmp(a.getCause(), b.getCause())); } public ErrorIdentity(Report r) { if((jarrev = (String)r.props.get("jar.git-rev")) == null) jarrev = ""; t = r.t; } public int compareTo(ErrorIdentity o) { int sc = jarrev.compareTo(o.jarrev); if(sc != 0) return(sc); return(thcmp(t, o.t)); } public boolean equals(ErrorIdentity o) { return(compareTo(o) == 0); } } public static String htmlhead(String title) { StringBuilder buf = new StringBuilder(); buf.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); buf.append("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n"); buf.append("<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en-US\">\n"); buf.append("<head>\n"); buf.append("<title>" + title + "</title>\n"); buf.append("<link rel=\"stylesheet\" title=\"Haven error report\" type=\"text/css\" href=\"base.css\" />"); buf.append("</head>\n"); buf.append("<body>\n"); return(buf.toString()); } public static String htmltail() { StringBuilder buf = new StringBuilder(); buf.append("</body>\n"); buf.append("</html>\n"); return(buf.toString()); } public static String htmlq(String html) { if(html == null) return("(null)"); StringBuilder buf = new StringBuilder(); for(int i = 0; i < html.length(); i++) { char c = html.charAt(i); if(c == '&') buf.append("&"); else if(c == '<') buf.append("<"); else if(c == '>') buf.append(">"); else buf.append(c); } return(buf.toString()); } public static String htmlbt(StackTraceElement[] bt) { StringBuilder buf = new StringBuilder(); buf.append("<table class=\"bt\">\n"); buf.append("<tr><th>Class</th><th>Function</th><th>File</th><th>Line</th></tr>\n"); for(StackTraceElement e : bt) { List<String> classes = new LinkedList<String>(); String pkg = e.getClassName(); if(pkg != null) { if(pkg.startsWith("javax.media.opengl.") || pkg.startsWith("com.sun.opengl.")) { classes.add("pkg-jogl"); } else if(pkg.startsWith("java.") || pkg.startsWith("javax.")) { classes.add("pkg-java"); } else if(pkg.startsWith("haven.") || pkg.startsWith("dolda.")) { classes.add("own"); } } if(e.isNativeMethod()) classes.add("native"); buf.append("<tr"); if(classes.size() > 0) { buf.append(" class=\""); boolean f = true; for(String cls : classes) { if(!f) buf.append(" "); f = false; buf.append(cls); } buf.append("\""); } buf.append(">"); buf.append("<td>" + htmlq(e.getClassName()) + "</td>"); buf.append("<td>" + htmlq(e.getMethodName()) + "</td>"); buf.append("<td>" + htmlq(e.getFileName()) + "</td>"); buf.append("<td>" + htmlq(Integer.toString(e.getLineNumber())) + "</td>"); buf.append("</tr>\n"); } buf.append("<table>\n"); return(buf.toString()); } public static void makereport(OutputStream outs, Report rep) throws IOException { PrintWriter out = new PrintWriter(new OutputStreamWriter(outs, "UTF-8")); out.print(htmlhead("Error Report")); out.println("<h1>Error Report</h1>"); out.println("<p>Reported at: " + htmlq(dfmt.format(new Date(rep.time))) + "</p>"); out.println("<h2>Properties</h2>"); out.println("<table>"); out.println("<tr><th>Name</th><th>Value</th>"); SortedSet<String> keys = new TreeSet<String>(rep.props.keySet()); for(String key : keys) { Object val = rep.props.get(key); String vals; if(val instanceof Number) { vals = ifmt.format((Number)val); } else if(val instanceof Date) { vals = dfmt.format((Number)val); } else if(val == null) { vals = "(null)"; } else { vals = val.toString(); } out.print(" <tr>"); out.print("<td>" + htmlq(key) + "</td><td>" + htmlq(vals) + "</td>"); out.println(" </tr>"); } out.println("</table>"); out.println("<h2>Exception chain</h2>"); for(Throwable t = rep.t; t != null; t = t.getCause()) { out.println("<h3>" + htmlq(t.getClass().getName()) + "</h3>"); out.println("<p>" + htmlq(t.getMessage()) + "</p>"); out.print(htmlbt(t.getStackTrace())); } out.print(htmltail()); out.flush(); } public static Throwable findrootexc(Throwable t) { if(t.getCause() == null) return(t); for(Class b : boring) { if(t.getClass() == b) return(findrootexc(t.getCause())); } return(t); } public static void makeindex(OutputStream outs, Map<File, Report> reports, Map<File, Exception> failed) throws IOException { PrintWriter out = new PrintWriter(new OutputStreamWriter(outs, "UTF-8")); out.print(htmlhead("Error Index")); out.println("<h1>Error Index</h1>"); Set<String> props = new TreeSet<String>(); for(String pn : idxprops) props.add(pn); Map<ErrorIdentity, List<Map.Entry<File, Report>>> groups = new TreeMap<ErrorIdentity, List<Map.Entry<File, Report>>>(); for(Map.Entry<File, Report> rent : reports.entrySet()) { ErrorIdentity id = new ErrorIdentity(rent.getValue()); if(groups.get(id) == null) groups.put(id, new ArrayList<Map.Entry<File, Report>>()); groups.get(id).add(rent); } for(ErrorIdentity id : groups.keySet()) { out.println("<h2>" + htmlq(findrootexc(id.t).getClass().getSimpleName()) + "</h2>"); out.println("<table><tr>"); out.println(" <th>File</th>"); out.println(" <th>Time</th>"); for(String pn : props) out.println(" <th>" + htmlq(pn) + "</th>"); out.println("</tr>"); List<Map.Entry<File, Report>> reps = groups.get(id); Collections.sort(reps, new Comparator<Map.Entry<File, Report>>() { public int compare(Map.Entry<File, Report> a, Map.Entry<File, Report> b) { long at = a.getValue().time, bt = b.getValue().time; if(at > bt) return(-1); else if(at < bt) return(1); else return(0); } }); for(Map.Entry<File, Report> rent : reps) { File file = rent.getKey(); Report rep = rent.getValue(); out.println(" <tr>"); out.print(" <td>"); out.println("<a href=\"" + htmlq(file.getName()) + ".html\">"); out.print(htmlq(file.getName())); out.println("</a></td>"); out.println(" <td>" + htmlq(dfmt.format(new Date(rep.time))) + "</td>"); for(String pn : props) { out.print(" <td>"); if(rep.props.containsKey(pn)) { out.print(htmlq(rep.props.get(pn).toString())); } out.println("</td>"); } out.println(" </tr>"); } out.println("</table>"); } if(failed.size() > 0) { out.println("<h2>Unreadable reports</h2>"); out.println("<table>"); out.println("<tr><th>File</th><th>Exception</th>"); for(File file : failed.keySet()) { Exception exc = failed.get(file); out.print(" <tr>"); out.print("<td>" + htmlq(file.getName()) + "</td><td>" + htmlq(exc.getClass().getName()) + ": " + htmlq(exc.getMessage()) + "</td>"); out.println(" </tr>"); } out.println("</table>"); } out.print(htmltail()); out.flush(); } public static void main(String[] args) throws Exception { File indir = new File(args[0]); File outdir = new File(args[1]); Map<File, Report> reports = new HashMap<File, Report>(); Map<File, Exception> failed = new HashMap<File, Exception>(); for(File f : indir.listFiles()) { if(f.getName().startsWith("err")) { try { ObjectInputStream in = new ObjectInputStream(new FileInputStream(f)); try { reports.put(f, (Report)in.readObject()); } finally { in.close(); } } catch(Exception e) { failed.put(f, e); } } } OutputStream out; out = new FileOutputStream(new File(outdir, "index.html")); try { makeindex(out, reports, failed); } finally { out.close(); } for(File f : reports.keySet()) { out = new FileOutputStream(new File(outdir, f.getName() + ".html")); try { makereport(out, reports.get(f)); } finally { out.close(); } } } }