/*
* 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.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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", "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("/srv/haven/errors");
File outdir = new File("/srv/www/haven/errors");
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();
}
}
}
}