| /* |
| * The Apache Software License, Version 1.1 |
| * |
| * Copyright (c) 2001-2002 The Apache Software Foundation. All rights |
| * reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in |
| * the documentation and/or other materials provided with the |
| * distribution. |
| * |
| * 3. The end-user documentation included with the redistribution, if |
| * any, must include the following acknowlegement: |
| * "This product includes software developed by the |
| * Apache Software Foundation (http://www.apache.org/)." |
| * Alternately, this acknowlegement may appear in the software itself, |
| * if and wherever such third-party acknowlegements normally appear. |
| * |
| * 4. The names "The Jakarta Project", "Ant", and "Apache Software |
| * Foundation" must not be used to endorse or promote products derived |
| * from this software without prior written permission. For written |
| * permission, please contact apache@apache.org. |
| * |
| * 5. Products derived from this software may not be called "Apache" |
| * nor may "Apache" appear in their names without prior written |
| * permission of the Apache Group. |
| * |
| * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR |
| * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
| * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
| * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| * ==================================================================== |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals on behalf of the Apache Software Foundation. For more |
| * information on the Apache Software Foundation, please see |
| * <http://www.apache.org/>. |
| */ |
| package org.apache.tools.ant.taskdefs.optional.sitraka; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.util.Enumeration; |
| import java.util.Hashtable; |
| import java.util.NoSuchElementException; |
| import java.util.Vector; |
| import javax.xml.parsers.DocumentBuilder; |
| import javax.xml.parsers.DocumentBuilderFactory; |
| |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| import org.xml.sax.InputSource; |
| |
| import org.apache.tools.ant.Project; |
| import org.apache.tools.ant.Task; |
| import org.apache.tools.ant.taskdefs.optional.sitraka.bytecode.ClassFile; |
| import org.apache.tools.ant.taskdefs.optional.sitraka.bytecode.ClassPathLoader; |
| import org.apache.tools.ant.taskdefs.optional.sitraka.bytecode.MethodInfo; |
| import org.apache.tools.ant.taskdefs.optional.sitraka.bytecode.Utils; |
| |
| /** |
| * Little hack to process XML report from JProbe. It will fix |
| * some reporting errors from JProbe 3.0 and makes use of a reference |
| * classpath to add classes/methods that were not reported by JProbe |
| * as being used (ie loaded) |
| * |
| * @author <a href="sbailliez@imediation.com">Stephane Bailliez</a> |
| */ |
| public class XMLReport { |
| /** task caller, can be null, used for logging purpose */ |
| private Task task; |
| |
| /** the XML file to process just from CovReport */ |
| private File file; |
| |
| /** jprobe home path. It is used to get the DTD */ |
| private File jprobeHome; |
| |
| /** parsed document */ |
| private Document report; |
| |
| /** mapping of class names to <code>ClassFile</code>s from the reference classpath. It is used to filter the JProbe report. */ |
| private Hashtable classFiles; |
| |
| /** mapping package name / package node for faster access */ |
| private Hashtable pkgMap; |
| |
| /** mapping classname / class node for faster access */ |
| private Hashtable classMap; |
| |
| /** method filters */ |
| private ReportFilters filters; |
| |
| /** create a new XML report, logging will be on stdout */ |
| public XMLReport(File file) { |
| this(null, file); |
| } |
| |
| /** create a new XML report, logging done on the task */ |
| public XMLReport(Task task, File file) { |
| this.file = file; |
| this.task = task; |
| } |
| |
| /** set the JProbe home path. Used to get the DTD */ |
| public void setJProbehome(File home) { |
| jprobeHome = home; |
| } |
| |
| /** set the */ |
| public void setReportFilters(ReportFilters filters) { |
| this.filters = filters; |
| } |
| |
| |
| /** create node maps so that we can access node faster by their name */ |
| protected void createNodeMaps() { |
| pkgMap = new Hashtable(); |
| classMap = new Hashtable(); |
| // create a map index of all packages by their name |
| // @todo can be done faster by direct access. |
| NodeList packages = report.getElementsByTagName("package"); |
| final int pkglen = packages.getLength(); |
| log("Indexing " + pkglen + " packages"); |
| for (int i = pkglen - 1; i > -1; i--) { |
| Element pkg = (Element) packages.item(i); |
| String pkgname = pkg.getAttribute("name"); |
| |
| int nbclasses = 0; |
| // create a map index of all classes by their fully |
| // qualified name. |
| NodeList classes = pkg.getElementsByTagName("class"); |
| final int classlen = classes.getLength(); |
| log("Indexing " + classlen + " classes in package " + pkgname); |
| for (int j = classlen - 1; j > -1; j--) { |
| Element clazz = (Element) classes.item(j); |
| String classname = clazz.getAttribute("name"); |
| if (pkgname != null && pkgname.length() != 0) { |
| classname = pkgname + "." + classname; |
| } |
| |
| int nbmethods = 0; |
| NodeList methods = clazz.getElementsByTagName("method"); |
| final int methodlen = methods.getLength(); |
| for (int k = methodlen - 1; k > -1; k--) { |
| Element meth = (Element) methods.item(k); |
| StringBuffer methodname = new StringBuffer(meth.getAttribute("name")); |
| methodname.delete(methodname.toString().indexOf("("), methodname.toString().length()); |
| String signature = classname + "." + methodname + "()"; |
| if (filters.accept(signature)) { |
| log("kept method:" + signature); |
| nbmethods++; |
| } else { |
| clazz.removeChild(meth); |
| } |
| } |
| // if we don't keep any method, we don't keep the class |
| if (nbmethods != 0 && classFiles.containsKey(classname)) { |
| log("Adding class '" + classname + "'"); |
| classMap.put(classname, clazz); |
| nbclasses++; |
| } else { |
| pkg.removeChild(clazz); |
| } |
| } |
| if (nbclasses != 0) { |
| log("Adding package '" + pkgname + "'"); |
| pkgMap.put(pkgname, pkg); |
| } else { |
| pkg.getParentNode().removeChild(pkg); |
| } |
| } |
| log("Indexed " + classMap.size() + " classes in " + pkgMap.size() + " packages"); |
| } |
| |
| /** create the whole new document */ |
| public Document createDocument(String[] classPath) throws Exception { |
| |
| // Iterate over the classpath to identify reference classes |
| classFiles = new Hashtable(); |
| ClassPathLoader cpl = new ClassPathLoader(classPath); |
| Enumeration enum = cpl.loaders(); |
| while (enum.hasMoreElements()) { |
| ClassPathLoader.FileLoader fl = (ClassPathLoader.FileLoader) enum.nextElement(); |
| ClassFile[] classes = fl.getClasses(); |
| log("Processing " + classes.length + " classes in " + fl.getFile()); |
| // process all classes |
| for (int i = 0; i < classes.length; i++) { |
| classFiles.put(classes[i].getFullName(), classes[i]); |
| } |
| } |
| |
| // Load the JProbe coverage XML report |
| DocumentBuilder dbuilder = newBuilder(); |
| InputSource is = new InputSource(new FileInputStream(file)); |
| if (jprobeHome != null) { |
| File dtdDir = new File(jprobeHome, "dtd"); |
| is.setSystemId("file:///" + dtdDir.getAbsolutePath() + "/"); |
| } |
| report = dbuilder.parse(is); |
| report.normalize(); |
| |
| // create maps for faster node access (also filters out unwanted nodes) |
| createNodeMaps(); |
| |
| // Make sure each class from the reference path ends up in the report |
| Enumeration classes = classFiles.elements(); |
| while (classes.hasMoreElements()) { |
| ClassFile cf = (ClassFile) classes.nextElement(); |
| serializeClass(cf); |
| } |
| // update the document with the stats |
| update(); |
| return report; |
| } |
| |
| /** |
| * JProbe does not put the java.lang prefix for classes |
| * in this package, so used this nice method so that |
| * I have the same signature for methods |
| */ |
| protected String getMethodSignature(MethodInfo method) { |
| StringBuffer buf = new StringBuffer(method.getName()); |
| buf.append("("); |
| String[] params = method.getParametersType(); |
| for (int i = 0; i < params.length; i++) { |
| String type = params[i]; |
| int pos = type.lastIndexOf('.'); |
| if (pos != -1) { |
| String pkg = type.substring(0, pos); |
| if ("java.lang".equals(pkg)) { |
| params[i] = type.substring(pos + 1); |
| } |
| } |
| buf.append(params[i]); |
| if (i != params.length - 1) { |
| buf.append(", "); |
| } |
| } |
| buf.append(")"); |
| return buf.toString(); |
| } |
| |
| /** |
| * Convert to a CovReport-like signature - <classname>.<method>(). |
| */ |
| protected String getMethodSignature(ClassFile clazz, MethodInfo method) { |
| StringBuffer buf = new StringBuffer(clazz.getFullName()); |
| buf.append("."); |
| buf.append(method.getName()); |
| buf.append("()"); |
| return buf.toString(); |
| } |
| |
| /** |
| * Do additional work on an element to remove abstract methods that |
| * are reported by JProbe 3.0 |
| */ |
| protected void removeAbstractMethods(ClassFile classFile, Element classNode) { |
| MethodInfo[] methods = classFile.getMethods(); |
| Hashtable methodNodeList = getMethods(classNode); |
| // assert xmlMethods.size() == methods.length() |
| final int size = methods.length; |
| for (int i = 0; i < size; i++) { |
| MethodInfo method = methods[i]; |
| String methodSig = getMethodSignature(method); |
| Element methodNode = (Element) methodNodeList.get(methodSig); |
| if (methodNode != null && |
| Utils.isAbstract(method.getAccessFlags())) { |
| log("\tRemoving abstract method " + methodSig); |
| classNode.removeChild(methodNode); |
| } |
| } |
| } |
| |
| /** create an empty method element with its cov.data values */ |
| protected Element createMethodElement(MethodInfo method) { |
| String methodsig = getMethodSignature(method); |
| Element methodElem = report.createElement("method"); |
| methodElem.setAttribute("name", methodsig); |
| // create the method cov.data element |
| Element methodData = report.createElement("cov.data"); |
| methodElem.appendChild(methodData); |
| methodData.setAttribute("calls", "0"); |
| methodData.setAttribute("hit_lines", "0"); |
| methodData.setAttribute("total_lines", String.valueOf(method.getNumberOfLines())); |
| return methodElem; |
| } |
| |
| /** create an empty package element with its default cov.data (0) */ |
| protected Element createPackageElement(String pkgname) { |
| Element pkgElem = report.createElement("package"); |
| pkgElem.setAttribute("name", pkgname); |
| // create the package cov.data element / default |
| // must be updated at the end of the whole process |
| Element pkgData = report.createElement("cov.data"); |
| pkgElem.appendChild(pkgData); |
| pkgData.setAttribute("calls", "0"); |
| pkgData.setAttribute("hit_methods", "0"); |
| pkgData.setAttribute("total_methods", "0"); |
| pkgData.setAttribute("hit_lines", "0"); |
| pkgData.setAttribute("total_lines", "0"); |
| return pkgElem; |
| } |
| |
| /** create an empty class element with its default cov.data (0) */ |
| protected Element createClassElement(ClassFile classFile) { |
| // create the class element |
| Element classElem = report.createElement("class"); |
| classElem.setAttribute("name", classFile.getName()); |
| // source file possibly does not exist in the bytecode |
| if (null != classFile.getSourceFile()) { |
| classElem.setAttribute("source", classFile.getSourceFile()); |
| } |
| // create the cov.data elem |
| Element classData = report.createElement("cov.data"); |
| classElem.appendChild(classData); |
| // create the class cov.data element |
| classData.setAttribute("calls", "0"); |
| classData.setAttribute("hit_methods", "0"); |
| classData.setAttribute("total_methods", "0"); |
| classData.setAttribute("hit_lines", "0"); |
| classData.setAttribute("total_lines", "0"); |
| return classElem; |
| } |
| |
| /** serialize a classfile into XML */ |
| protected void serializeClass(ClassFile classFile) { |
| // the class already is reported so ignore it |
| String fullclassname = classFile.getFullName(); |
| log("Looking for '" + fullclassname + "'"); |
| Element clazz = (Element) classMap.get(fullclassname); |
| |
| // ignore classes that are already reported, all the information is |
| // already there. |
| if (clazz != null) { |
| log("Ignoring " + fullclassname); |
| removeAbstractMethods(classFile, clazz); |
| return; |
| } |
| |
| // ignore interfaces files, there is no code in there to cover. |
| if (Utils.isInterface(classFile.getAccess())) { |
| return; |
| } |
| |
| Vector methods = getFilteredMethods(classFile); |
| // no need to process, there are no methods to add for this class. |
| if (methods.size() == 0) { |
| return; |
| } |
| |
| String pkgname = classFile.getPackage(); |
| // System.out.println("Looking for package " + pkgname); |
| Element pkgElem = (Element) pkgMap.get(pkgname); |
| if (pkgElem == null) { |
| pkgElem = createPackageElement(pkgname); |
| report.getDocumentElement().appendChild(pkgElem); |
| pkgMap.put(pkgname, pkgElem); // add the pkg to the map |
| } |
| // this is a brand new class, so we have to create a new node |
| |
| // create the class element |
| Element classElem = createClassElement(classFile); |
| pkgElem.appendChild(classElem); |
| |
| int total_lines = 0; |
| int total_methods = 0; |
| final int count = methods.size(); |
| for (int i = 0; i < count; i++) { |
| // create the method element |
| MethodInfo method = (MethodInfo) methods.elementAt(i); |
| if (Utils.isAbstract(method.getAccessFlags())) { |
| continue; // no need to report abstract methods |
| } |
| Element methodElem = createMethodElement(method); |
| classElem.appendChild(methodElem); |
| total_lines += method.getNumberOfLines(); |
| total_methods++; |
| } |
| // create the class cov.data element |
| Element classData = getCovDataChild(classElem); |
| classData.setAttribute("total_methods", String.valueOf(total_methods)); |
| classData.setAttribute("total_lines", String.valueOf(total_lines)); |
| |
| // add itself to the node map |
| classMap.put(fullclassname, classElem); |
| } |
| |
| protected Vector getFilteredMethods(ClassFile classFile) { |
| MethodInfo[] methodlist = classFile.getMethods(); |
| Vector methods = new Vector(methodlist.length); |
| for (int i = 0; i < methodlist.length; i++) { |
| MethodInfo method = methodlist[i]; |
| String signature = getMethodSignature(classFile, method); |
| if (filters.accept(signature)) { |
| methods.addElement(method); |
| log("keeping " + signature); |
| } else { |
| // log("discarding " + signature); |
| } |
| } |
| return methods; |
| } |
| |
| |
| /** update the count of the XML, that is accumulate the stats on |
| * methods, classes and package so that the numbers are valid |
| * according to the info that was appended to the XML. |
| */ |
| protected void update() { |
| int calls = 0; |
| int hit_methods = 0; |
| int total_methods = 0; |
| int hit_lines = 0; |
| int total_lines = 0; |
| |
| // use the map for access, all nodes should be there |
| Enumeration enum = pkgMap.elements(); |
| while (enum.hasMoreElements()) { |
| Element pkgElem = (Element) enum.nextElement(); |
| String pkgname = pkgElem.getAttribute("name"); |
| Element[] classes = getClasses(pkgElem); |
| int pkg_calls = 0; |
| int pkg_hit_methods = 0; |
| int pkg_total_methods = 0; |
| int pkg_hit_lines = 0; |
| int pkg_total_lines = 0; |
| //System.out.println("Processing package '" + pkgname + "': " + classes.length + " classes"); |
| for (int j = 0; j < classes.length; j++) { |
| Element clazz = classes[j]; |
| String classname = clazz.getAttribute("name"); |
| if (pkgname != null && pkgname.length() != 0) { |
| classname = pkgname + "." + classname; |
| } |
| // there's only cov.data as a child so bet on it |
| Element covdata = getCovDataChild(clazz); |
| try { |
| pkg_calls += Integer.parseInt(covdata.getAttribute("calls")); |
| pkg_hit_methods += Integer.parseInt(covdata.getAttribute("hit_methods")); |
| pkg_total_methods += Integer.parseInt(covdata.getAttribute("total_methods")); |
| pkg_hit_lines += Integer.parseInt(covdata.getAttribute("hit_lines")); |
| pkg_total_lines += Integer.parseInt(covdata.getAttribute("total_lines")); |
| } catch (NumberFormatException e) { |
| log("Error parsing '" + classname + "' (" + j + "/" + classes.length + ") in package '" + pkgname + "'"); |
| throw e; |
| } |
| } |
| Element covdata = getCovDataChild(pkgElem); |
| covdata.setAttribute("calls", String.valueOf(pkg_calls)); |
| covdata.setAttribute("hit_methods", String.valueOf(pkg_hit_methods)); |
| covdata.setAttribute("total_methods", String.valueOf(pkg_total_methods)); |
| covdata.setAttribute("hit_lines", String.valueOf(pkg_hit_lines)); |
| covdata.setAttribute("total_lines", String.valueOf(pkg_total_lines)); |
| calls += pkg_calls; |
| hit_methods += pkg_hit_methods; |
| total_methods += pkg_total_methods; |
| hit_lines += pkg_hit_lines; |
| total_lines += pkg_total_lines; |
| } |
| Element covdata = getCovDataChild(report.getDocumentElement()); |
| covdata.setAttribute("calls", String.valueOf(calls)); |
| covdata.setAttribute("hit_methods", String.valueOf(hit_methods)); |
| covdata.setAttribute("total_methods", String.valueOf(total_methods)); |
| covdata.setAttribute("hit_lines", String.valueOf(hit_lines)); |
| covdata.setAttribute("total_lines", String.valueOf(total_lines)); |
| } |
| |
| protected Element getCovDataChild(Element parent) { |
| NodeList children = parent.getChildNodes(); |
| int len = children.getLength(); |
| for (int i = 0; i < len; i++) { |
| Node child = children.item(i); |
| if (child.getNodeType() == Node.ELEMENT_NODE) { |
| Element elem = (Element) child; |
| if ("cov.data".equals(elem.getNodeName())) { |
| return elem; |
| } |
| } |
| } |
| throw new NoSuchElementException("Could not find 'cov.data' element in parent '" + parent.getNodeName() + "'"); |
| } |
| |
| protected Hashtable getMethods(Element clazz) { |
| Hashtable map = new Hashtable(); |
| NodeList children = clazz.getChildNodes(); |
| int len = children.getLength(); |
| for (int i = 0; i < len; i++) { |
| Node child = children.item(i); |
| if (child.getNodeType() == Node.ELEMENT_NODE) { |
| Element elem = (Element) child; |
| if ("method".equals(elem.getNodeName())) { |
| String name = elem.getAttribute("name"); |
| map.put(name, elem); |
| } |
| } |
| } |
| return map; |
| } |
| |
| protected Element[] getClasses(Element pkg) { |
| Vector v = new Vector(); |
| NodeList children = pkg.getChildNodes(); |
| int len = children.getLength(); |
| for (int i = 0; i < len; i++) { |
| Node child = children.item(i); |
| if (child.getNodeType() == Node.ELEMENT_NODE) { |
| Element elem = (Element) child; |
| if ("class".equals(elem.getNodeName())) { |
| v.addElement(elem); |
| } |
| } |
| } |
| Element[] elems = new Element[v.size()]; |
| v.copyInto(elems); |
| return elems; |
| |
| } |
| |
| protected Element[] getPackages(Element snapshot) { |
| Vector v = new Vector(); |
| NodeList children = snapshot.getChildNodes(); |
| int len = children.getLength(); |
| for (int i = 0; i < len; i++) { |
| Node child = children.item(i); |
| if (child.getNodeType() == Node.ELEMENT_NODE) { |
| Element elem = (Element) child; |
| if ("package".equals(elem.getNodeName())) { |
| v.addElement(elem); |
| } |
| } |
| } |
| Element[] elems = new Element[v.size()]; |
| v.copyInto(elems); |
| return elems; |
| } |
| |
| private static DocumentBuilder newBuilder() { |
| try { |
| DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); |
| factory.setIgnoringComments(true); |
| factory.setValidating(false); |
| return factory.newDocumentBuilder(); |
| } catch (Exception e) { |
| throw new ExceptionInInitializerError(e); |
| } |
| } |
| |
| public void log(String message) { |
| if (task == null) { |
| //System.out.println(message); |
| } else { |
| task.log(message, Project.MSG_DEBUG); |
| } |
| } |
| |
| } |
| |