/*
* 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();
}
}
}
}