1. /*
  2. * Copyright 2001-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package org.apache.tools.ant.taskdefs.optional.sitraka;
  18. import java.io.File;
  19. import java.io.FileInputStream;
  20. import java.util.Enumeration;
  21. import java.util.Hashtable;
  22. import java.util.NoSuchElementException;
  23. import java.util.Vector;
  24. import javax.xml.parsers.DocumentBuilder;
  25. import javax.xml.parsers.DocumentBuilderFactory;
  26. import org.apache.tools.ant.Project;
  27. import org.apache.tools.ant.Task;
  28. import org.apache.tools.ant.taskdefs.optional.sitraka.bytecode.ClassFile;
  29. import org.apache.tools.ant.taskdefs.optional.sitraka.bytecode.ClassPathLoader;
  30. import org.apache.tools.ant.taskdefs.optional.sitraka.bytecode.MethodInfo;
  31. import org.apache.tools.ant.taskdefs.optional.sitraka.bytecode.Utils;
  32. import org.w3c.dom.Document;
  33. import org.w3c.dom.Element;
  34. import org.w3c.dom.Node;
  35. import org.w3c.dom.NodeList;
  36. import org.xml.sax.InputSource;
  37. /**
  38. * Little hack to process XML report from JProbe. It will fix
  39. * some reporting errors from JProbe 3.0 and makes use of a reference
  40. * classpath to add classes/methods that were not reported by JProbe
  41. * as being used (ie loaded)
  42. *
  43. */
  44. public class XMLReport {
  45. /** task caller, can be null, used for logging purpose */
  46. private Task task;
  47. /** the XML file to process just from CovReport */
  48. private File file;
  49. /** jprobe home path. It is used to get the DTD */
  50. private File jprobeHome;
  51. /** parsed document */
  52. private Document report;
  53. /**
  54. * mapping of class names to <code>ClassFile</code>s from the reference
  55. * classpath. It is used to filter the JProbe report.
  56. */
  57. private Hashtable classFiles;
  58. /** mapping package name / package node for faster access */
  59. private Hashtable pkgMap;
  60. /** mapping classname / class node for faster access */
  61. private Hashtable classMap;
  62. /** method filters */
  63. private ReportFilters filters;
  64. /** create a new XML report, logging will be on stdout */
  65. public XMLReport(File file) {
  66. this(null, file);
  67. }
  68. /** create a new XML report, logging done on the task */
  69. public XMLReport(Task task, File file) {
  70. this.file = file;
  71. this.task = task;
  72. }
  73. /** set the JProbe home path. Used to get the DTD */
  74. public void setJProbehome(File home) {
  75. jprobeHome = home;
  76. }
  77. /** set the */
  78. public void setReportFilters(ReportFilters filters) {
  79. this.filters = filters;
  80. }
  81. /** create node maps so that we can access node faster by their name */
  82. protected void createNodeMaps() {
  83. pkgMap = new Hashtable();
  84. classMap = new Hashtable();
  85. // create a map index of all packages by their name
  86. // @todo can be done faster by direct access.
  87. NodeList packages = report.getElementsByTagName("package");
  88. final int pkglen = packages.getLength();
  89. log("Indexing " + pkglen + " packages");
  90. for (int i = pkglen - 1; i > -1; i--) {
  91. Element pkg = (Element) packages.item(i);
  92. String pkgname = pkg.getAttribute("name");
  93. int nbclasses = 0;
  94. // create a map index of all classes by their fully
  95. // qualified name.
  96. NodeList classes = pkg.getElementsByTagName("class");
  97. final int classlen = classes.getLength();
  98. log("Indexing " + classlen + " classes in package " + pkgname);
  99. for (int j = classlen - 1; j > -1; j--) {
  100. Element clazz = (Element) classes.item(j);
  101. String classname = clazz.getAttribute("name");
  102. if (pkgname != null && pkgname.length() != 0) {
  103. classname = pkgname + "." + classname;
  104. }
  105. int nbmethods = 0;
  106. NodeList methods = clazz.getElementsByTagName("method");
  107. final int methodlen = methods.getLength();
  108. for (int k = methodlen - 1; k > -1; k--) {
  109. Element meth = (Element) methods.item(k);
  110. StringBuffer methodname = new StringBuffer(meth.getAttribute("name"));
  111. methodname.delete(methodname.toString().indexOf("("),
  112. methodname.toString().length());
  113. String signature = classname + "." + methodname + "()";
  114. if (filters.accept(signature)) {
  115. log("kept method:" + signature);
  116. nbmethods++;
  117. } else {
  118. clazz.removeChild(meth);
  119. }
  120. }
  121. // if we don't keep any method, we don't keep the class
  122. if (nbmethods != 0 && classFiles.containsKey(classname)) {
  123. log("Adding class '" + classname + "'");
  124. classMap.put(classname, clazz);
  125. nbclasses++;
  126. } else {
  127. pkg.removeChild(clazz);
  128. }
  129. }
  130. if (nbclasses != 0) {
  131. log("Adding package '" + pkgname + "'");
  132. pkgMap.put(pkgname, pkg);
  133. } else {
  134. pkg.getParentNode().removeChild(pkg);
  135. }
  136. }
  137. log("Indexed " + classMap.size() + " classes in " + pkgMap.size() + " packages");
  138. }
  139. /** create the whole new document */
  140. public Document createDocument(String[] classPath) throws Exception {
  141. // Iterate over the classpath to identify reference classes
  142. classFiles = new Hashtable();
  143. ClassPathLoader cpl = new ClassPathLoader(classPath);
  144. Enumeration e = cpl.loaders();
  145. while (e.hasMoreElements()) {
  146. ClassPathLoader.FileLoader fl = (ClassPathLoader.FileLoader) e.nextElement();
  147. ClassFile[] classes = fl.getClasses();
  148. log("Processing " + classes.length + " classes in " + fl.getFile());
  149. // process all classes
  150. for (int i = 0; i < classes.length; i++) {
  151. classFiles.put(classes[i].getFullName(), classes[i]);
  152. }
  153. }
  154. // Load the JProbe coverage XML report
  155. DocumentBuilder dbuilder = newBuilder();
  156. InputSource is = new InputSource(new FileInputStream(file));
  157. if (jprobeHome != null) {
  158. File dtdDir = new File(jprobeHome, "dtd");
  159. is.setSystemId("file:///" + dtdDir.getAbsolutePath() + "/");
  160. }
  161. report = dbuilder.parse(is);
  162. report.normalize();
  163. // create maps for faster node access (also filters out unwanted nodes)
  164. createNodeMaps();
  165. // Make sure each class from the reference path ends up in the report
  166. Enumeration classes = classFiles.elements();
  167. while (classes.hasMoreElements()) {
  168. ClassFile cf = (ClassFile) classes.nextElement();
  169. serializeClass(cf);
  170. }
  171. // update the document with the stats
  172. update();
  173. return report;
  174. }
  175. /**
  176. * JProbe does not put the java.lang prefix for classes
  177. * in this package, so used this nice method so that
  178. * I have the same signature for methods
  179. */
  180. protected String getMethodSignature(MethodInfo method) {
  181. StringBuffer buf = new StringBuffer(method.getName());
  182. buf.append("(");
  183. String[] params = method.getParametersType();
  184. for (int i = 0; i < params.length; i++) {
  185. String type = params[i];
  186. int pos = type.lastIndexOf('.');
  187. if (pos != -1) {
  188. String pkg = type.substring(0, pos);
  189. if ("java.lang".equals(pkg)) {
  190. params[i] = type.substring(pos + 1);
  191. }
  192. }
  193. buf.append(params[i]);
  194. if (i != params.length - 1) {
  195. buf.append(", ");
  196. }
  197. }
  198. buf.append(")");
  199. return buf.toString();
  200. }
  201. /**
  202. * Convert to a CovReport-like signature - <classname>.<method>().
  203. */
  204. protected String getMethodSignature(ClassFile clazz, MethodInfo method) {
  205. StringBuffer buf = new StringBuffer(clazz.getFullName());
  206. buf.append(".");
  207. buf.append(method.getName());
  208. buf.append("()");
  209. return buf.toString();
  210. }
  211. /**
  212. * Do additional work on an element to remove abstract methods that
  213. * are reported by JProbe 3.0
  214. */
  215. protected void removeAbstractMethods(ClassFile classFile, Element classNode) {
  216. MethodInfo[] methods = classFile.getMethods();
  217. Hashtable methodNodeList = getMethods(classNode);
  218. // assert xmlMethods.size() == methods.length()
  219. final int size = methods.length;
  220. for (int i = 0; i < size; i++) {
  221. MethodInfo method = methods[i];
  222. String methodSig = getMethodSignature(method);
  223. Element methodNode = (Element) methodNodeList.get(methodSig);
  224. if (methodNode != null
  225. && Utils.isAbstract(method.getAccessFlags())) {
  226. log("\tRemoving abstract method " + methodSig);
  227. classNode.removeChild(methodNode);
  228. }
  229. }
  230. }
  231. /** create an empty method element with its cov.data values */
  232. protected Element createMethodElement(MethodInfo method) {
  233. String methodsig = getMethodSignature(method);
  234. Element methodElem = report.createElement("method");
  235. methodElem.setAttribute("name", methodsig);
  236. // create the method cov.data element
  237. Element methodData = report.createElement("cov.data");
  238. methodElem.appendChild(methodData);
  239. methodData.setAttribute("calls", "0");
  240. methodData.setAttribute("hit_lines", "0");
  241. methodData.setAttribute("total_lines", String.valueOf(method.getNumberOfLines()));
  242. return methodElem;
  243. }
  244. /** create an empty package element with its default cov.data (0) */
  245. protected Element createPackageElement(String pkgname) {
  246. Element pkgElem = report.createElement("package");
  247. pkgElem.setAttribute("name", pkgname);
  248. // create the package cov.data element / default
  249. // must be updated at the end of the whole process
  250. Element pkgData = report.createElement("cov.data");
  251. pkgElem.appendChild(pkgData);
  252. pkgData.setAttribute("calls", "0");
  253. pkgData.setAttribute("hit_methods", "0");
  254. pkgData.setAttribute("total_methods", "0");
  255. pkgData.setAttribute("hit_lines", "0");
  256. pkgData.setAttribute("total_lines", "0");
  257. return pkgElem;
  258. }
  259. /** create an empty class element with its default cov.data (0) */
  260. protected Element createClassElement(ClassFile classFile) {
  261. // create the class element
  262. Element classElem = report.createElement("class");
  263. classElem.setAttribute("name", classFile.getName());
  264. // source file possibly does not exist in the bytecode
  265. if (null != classFile.getSourceFile()) {
  266. classElem.setAttribute("source", classFile.getSourceFile());
  267. }
  268. // create the cov.data elem
  269. Element classData = report.createElement("cov.data");
  270. classElem.appendChild(classData);
  271. // create the class cov.data element
  272. classData.setAttribute("calls", "0");
  273. classData.setAttribute("hit_methods", "0");
  274. classData.setAttribute("total_methods", "0");
  275. classData.setAttribute("hit_lines", "0");
  276. classData.setAttribute("total_lines", "0");
  277. return classElem;
  278. }
  279. /** serialize a classfile into XML */
  280. protected void serializeClass(ClassFile classFile) {
  281. // the class already is reported so ignore it
  282. String fullclassname = classFile.getFullName();
  283. log("Looking for '" + fullclassname + "'");
  284. Element clazz = (Element) classMap.get(fullclassname);
  285. // ignore classes that are already reported, all the information is
  286. // already there.
  287. if (clazz != null) {
  288. log("Ignoring " + fullclassname);
  289. removeAbstractMethods(classFile, clazz);
  290. return;
  291. }
  292. // ignore interfaces files, there is no code in there to cover.
  293. if (Utils.isInterface(classFile.getAccess())) {
  294. return;
  295. }
  296. Vector methods = getFilteredMethods(classFile);
  297. // no need to process, there are no methods to add for this class.
  298. if (methods.size() == 0) {
  299. return;
  300. }
  301. String pkgname = classFile.getPackage();
  302. // System.out.println("Looking for package " + pkgname);
  303. Element pkgElem = (Element) pkgMap.get(pkgname);
  304. if (pkgElem == null) {
  305. pkgElem = createPackageElement(pkgname);
  306. report.getDocumentElement().appendChild(pkgElem);
  307. pkgMap.put(pkgname, pkgElem); // add the pkg to the map
  308. }
  309. // this is a brand new class, so we have to create a new node
  310. // create the class element
  311. Element classElem = createClassElement(classFile);
  312. pkgElem.appendChild(classElem);
  313. int total_lines = 0;
  314. int total_methods = 0;
  315. final int count = methods.size();
  316. for (int i = 0; i < count; i++) {
  317. // create the method element
  318. MethodInfo method = (MethodInfo) methods.elementAt(i);
  319. if (Utils.isAbstract(method.getAccessFlags())) {
  320. continue; // no need to report abstract methods
  321. }
  322. Element methodElem = createMethodElement(method);
  323. classElem.appendChild(methodElem);
  324. total_lines += method.getNumberOfLines();
  325. total_methods++;
  326. }
  327. // create the class cov.data element
  328. Element classData = getCovDataChild(classElem);
  329. classData.setAttribute("total_methods", String.valueOf(total_methods));
  330. classData.setAttribute("total_lines", String.valueOf(total_lines));
  331. // add itself to the node map
  332. classMap.put(fullclassname, classElem);
  333. }
  334. protected Vector getFilteredMethods(ClassFile classFile) {
  335. MethodInfo[] methodlist = classFile.getMethods();
  336. Vector methods = new Vector(methodlist.length);
  337. for (int i = 0; i < methodlist.length; i++) {
  338. MethodInfo method = methodlist[i];
  339. String signature = getMethodSignature(classFile, method);
  340. if (filters.accept(signature)) {
  341. methods.addElement(method);
  342. log("keeping " + signature);
  343. } else {
  344. // log("discarding " + signature);
  345. }
  346. }
  347. return methods;
  348. }
  349. /** update the count of the XML, that is accumulate the stats on
  350. * methods, classes and package so that the numbers are valid
  351. * according to the info that was appended to the XML.
  352. */
  353. protected void update() {
  354. int calls = 0;
  355. int hit_methods = 0;
  356. int total_methods = 0;
  357. int hit_lines = 0;
  358. int total_lines = 0;
  359. // use the map for access, all nodes should be there
  360. Enumeration e = pkgMap.elements();
  361. while (e.hasMoreElements()) {
  362. Element pkgElem = (Element) e.nextElement();
  363. String pkgname = pkgElem.getAttribute("name");
  364. Element[] classes = getClasses(pkgElem);
  365. int pkg_calls = 0;
  366. int pkg_hit_methods = 0;
  367. int pkg_total_methods = 0;
  368. int pkg_hit_lines = 0;
  369. int pkg_total_lines = 0;
  370. //System.out.println("Processing package '" + pkgname + "': "
  371. // + classes.length + " classes");
  372. for (int j = 0; j < classes.length; j++) {
  373. Element clazz = classes[j];
  374. String classname = clazz.getAttribute("name");
  375. if (pkgname != null && pkgname.length() != 0) {
  376. classname = pkgname + "." + classname;
  377. }
  378. // there's only cov.data as a child so bet on it
  379. Element covdata = getCovDataChild(clazz);
  380. try {
  381. pkg_calls += Integer.parseInt(covdata.getAttribute("calls"));
  382. pkg_hit_methods += Integer.parseInt(covdata.getAttribute("hit_methods"));
  383. pkg_total_methods += Integer.parseInt(covdata.getAttribute("total_methods"));
  384. pkg_hit_lines += Integer.parseInt(covdata.getAttribute("hit_lines"));
  385. pkg_total_lines += Integer.parseInt(covdata.getAttribute("total_lines"));
  386. } catch (NumberFormatException ex) {
  387. log("Error parsing '" + classname + "' (" + j + "/"
  388. + classes.length + ") in package '" + pkgname + "'");
  389. throw ex;
  390. }
  391. }
  392. Element covdata = getCovDataChild(pkgElem);
  393. covdata.setAttribute("calls", String.valueOf(pkg_calls));
  394. covdata.setAttribute("hit_methods", String.valueOf(pkg_hit_methods));
  395. covdata.setAttribute("total_methods", String.valueOf(pkg_total_methods));
  396. covdata.setAttribute("hit_lines", String.valueOf(pkg_hit_lines));
  397. covdata.setAttribute("total_lines", String.valueOf(pkg_total_lines));
  398. calls += pkg_calls;
  399. hit_methods += pkg_hit_methods;
  400. total_methods += pkg_total_methods;
  401. hit_lines += pkg_hit_lines;
  402. total_lines += pkg_total_lines;
  403. }
  404. Element covdata = getCovDataChild(report.getDocumentElement());
  405. covdata.setAttribute("calls", String.valueOf(calls));
  406. covdata.setAttribute("hit_methods", String.valueOf(hit_methods));
  407. covdata.setAttribute("total_methods", String.valueOf(total_methods));
  408. covdata.setAttribute("hit_lines", String.valueOf(hit_lines));
  409. covdata.setAttribute("total_lines", String.valueOf(total_lines));
  410. }
  411. protected Element getCovDataChild(Element parent) {
  412. NodeList children = parent.getChildNodes();
  413. int len = children.getLength();
  414. for (int i = 0; i < len; i++) {
  415. Node child = children.item(i);
  416. if (child.getNodeType() == Node.ELEMENT_NODE) {
  417. Element elem = (Element) child;
  418. if ("cov.data".equals(elem.getNodeName())) {
  419. return elem;
  420. }
  421. }
  422. }
  423. throw new NoSuchElementException("Could not find 'cov.data' "
  424. + "element in parent '" + parent.getNodeName() + "'");
  425. }
  426. protected Hashtable getMethods(Element clazz) {
  427. Hashtable map = new Hashtable();
  428. NodeList children = clazz.getChildNodes();
  429. int len = children.getLength();
  430. for (int i = 0; i < len; i++) {
  431. Node child = children.item(i);
  432. if (child.getNodeType() == Node.ELEMENT_NODE) {
  433. Element elem = (Element) child;
  434. if ("method".equals(elem.getNodeName())) {
  435. String name = elem.getAttribute("name");
  436. map.put(name, elem);
  437. }
  438. }
  439. }
  440. return map;
  441. }
  442. protected Element[] getClasses(Element pkg) {
  443. Vector v = new Vector();
  444. NodeList children = pkg.getChildNodes();
  445. int len = children.getLength();
  446. for (int i = 0; i < len; i++) {
  447. Node child = children.item(i);
  448. if (child.getNodeType() == Node.ELEMENT_NODE) {
  449. Element elem = (Element) child;
  450. if ("class".equals(elem.getNodeName())) {
  451. v.addElement(elem);
  452. }
  453. }
  454. }
  455. Element[] elems = new Element[v.size()];
  456. v.copyInto(elems);
  457. return elems;
  458. }
  459. protected Element[] getPackages(Element snapshot) {
  460. Vector v = new Vector();
  461. NodeList children = snapshot.getChildNodes();
  462. int len = children.getLength();
  463. for (int i = 0; i < len; i++) {
  464. Node child = children.item(i);
  465. if (child.getNodeType() == Node.ELEMENT_NODE) {
  466. Element elem = (Element) child;
  467. if ("package".equals(elem.getNodeName())) {
  468. v.addElement(elem);
  469. }
  470. }
  471. }
  472. Element[] elems = new Element[v.size()];
  473. v.copyInto(elems);
  474. return elems;
  475. }
  476. private static DocumentBuilder newBuilder() {
  477. try {
  478. DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
  479. factory.setIgnoringComments(true);
  480. factory.setValidating(false);
  481. return factory.newDocumentBuilder();
  482. } catch (Exception e) {
  483. throw new ExceptionInInitializerError(e);
  484. }
  485. }
  486. public void log(String message) {
  487. if (task == null) {
  488. //System.out.println(message);
  489. } else {
  490. task.log(message, Project.MSG_DEBUG);
  491. }
  492. }
  493. }