001: /*
002: * @(#)FailOnReportStyle.java
003: *
004: * Copyright (C) 2004 Matt Albrecht
005: * groboclown@users.sourceforge.net
006: * http://groboutils.sourceforge.net
007: *
008: * Permission is hereby granted, free of charge, to any person obtaining a
009: * copy of this software and associated documentation files (the "Software"),
010: * to deal in the Software without restriction, including without limitation
011: * the rights to use, copy, modify, merge, publish, distribute, sublicense,
012: * and/or sell copies of the Software, and to permit persons to whom the
013: * Software is furnished to do so, subject to the following conditions:
014: *
015: * The above copyright notice and this permission notice shall be included in
016: * all copies or substantial portions of the Software.
017: *
018: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
019: * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
020: * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
021: * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
022: * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
023: * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
024: * DEALINGS IN THE SOFTWARE.
025: */
026:
027: package net.sourceforge.groboutils.codecoverage.v2.ant;
028:
029: import java.text.NumberFormat;
030: import java.util.Enumeration;
031: import java.util.StringTokenizer;
032: import java.util.Vector;
033:
034: import org.apache.tools.ant.BuildException;
035: import org.apache.tools.ant.Project;
036: import org.w3c.dom.Document;
037: import org.w3c.dom.Element;
038: import org.w3c.dom.NodeList;
039:
040: /**
041: * Checks the coverage report for a subset of classes whose total
042: * coverage percentage isn't up-to-snuff.
043: *
044: * @author Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
045: * @version $Date: 2004/04/15 05:48:25 $
046: * @since March 15, 2003
047: */
048: public class FailOnReportStyle implements IReportStyle {
049: private Vector includeExcludeSet = new Vector();
050: private String failureProperty = null;
051: private double minPercent = 80.0;
052:
053: private int markCount = 0;
054: private int coverCount = 0;
055:
056: private static final NumberFormat DOUBLE_FORMATTER = NumberFormat
057: .getInstance();
058: static {
059: DOUBLE_FORMATTER.setMaximumFractionDigits(2);
060: }
061:
062: public static class IncludeAndExclude {
063: String value = null;
064: boolean module = false;
065: boolean corp = false;
066: boolean isInclude = true;
067:
068: public void setModule(String s) {
069: this .module = true;
070: this .value = s;
071: }
072:
073: public void setClass(String f) {
074: this .corp = true;
075: this .value = f;
076: }
077:
078: protected void check() throws BuildException {
079: if (this .module && this .corp) {
080: throw new BuildException(
081: "Only one of 'module' and 'class' attributes can be "
082: + "specified in an " + this .getType()
083: + " element.");
084: }
085: if (!this .module && !this .corp) {
086: throw new BuildException(
087: "One of 'module' or 'class' attributes must be "
088: + "specified in an " + this .getType()
089: + " element.");
090: }
091: }
092:
093: protected String getType() {
094: return (this .isInclude ? "include" : "exclude");
095: }
096: }
097:
098: static class ClassFilter {
099: IFilterToken parts[];
100:
101: public ClassFilter(String filter) throws BuildException {
102: if (filter == null || filter.length() <= 0
103: || filter.charAt(0) == '.'
104: || filter.indexOf("..") >= 0
105: || filter.indexOf("//") >= 0
106: || filter.indexOf("/.") >= 0
107: || filter.indexOf("./") >= 0
108: || filter.charAt(filter.length() - 1) == '.'
109: || filter.charAt(filter.length() - 1) == '/') {
110: throw new BuildException("Invalid class filter '"
111: + filter + "'");
112: }
113: if (filter.charAt(0) == '/') {
114: if (filter.length() <= 1) {
115: throw new BuildException("Invalid class filter '"
116: + filter + "'");
117: }
118: filter = filter.substring(1);
119: }
120:
121: StringTokenizer st = new StringTokenizer(filter, "./",
122: false);
123: Vector v = new Vector();
124: while (st.hasMoreTokens()) {
125: String s = st.nextToken();
126: if (s.length() <= 0) {
127: throw new BuildException(
128: "Invalid class filter: no values between delimiters.");
129: }
130: int len = s.length();
131: if (s.charAt(0) == '*') {
132: if (len == 1) {
133: v.addElement(ANY_TOKEN);
134: } else
135: //if (s.equals( "**" ))
136: if (len == 2 && s.charAt(1) == '*') {
137: v.addElement(NULL_TOKEN);
138: } else {
139: s = s.substring(1);
140: if (s.indexOf('*') >= 0) {
141: throw new BuildException(
142: "Invalid class filter: only 1 wildcard may "
143: + "be present between delimiters.");
144: }
145: v.addElement(new BeforeFilterToken(s));
146: }
147: } else if (s.charAt(len - 1) == '*') {
148: s = s.substring(0, len - 1);
149: if (s.indexOf('*') >= 0) {
150: throw new BuildException(
151: "Invalid class filter: only 1 wildcard may "
152: + "be present between delimiters.");
153: }
154: v.addElement(new EndFilterToken(s));
155: } else {
156: if (s.indexOf('*') > 0) {
157: throw new BuildException(
158: "Invalid class filter: a wildcard may "
159: + "only be present before and after a text part.");
160: }
161: v.addElement(new ExactFilterToken(s));
162: }
163: }
164: if (v.size() <= 0) {
165: throw new BuildException(
166: "Invalid class filter: no delimited elements");
167: }
168: parts = new IFilterToken[v.size()];
169: v.copyInto(parts);
170: }
171:
172: public boolean match(String c) {
173: if (c == null || c.length() <= 0) {
174: throw new IllegalArgumentException("no null args.");
175: }
176:
177: StringTokenizer st = new StringTokenizer(c, ".", false);
178: int filterPos = 0;
179: while (st.hasMoreTokens()) {
180: if (filterPos >= this .parts.length) {
181: return false;
182: }
183: String s = st.nextToken();
184:
185: // special handling for the '**' filter
186: if (this .parts[filterPos] == NULL_TOKEN) {
187: // if the rest of the filters are '**', early out
188: if (filterPos == this .parts.length - 1) {
189: return true;
190: }
191:
192: // this kind of pattern matching isn't greedy.
193: if (this .parts[filterPos + 1].match(s)) {
194: // we can advance to the filter after the
195: // one just matched
196: filterPos += 2;
197: }
198: // else the next filterPos doesn't match this token,
199: // so keep this token for the next match
200: } else {
201: // check if the next filter doesn't match, so we can
202: // early out.
203: if (!this .parts[filterPos++].match(s)) {
204: return false;
205: }
206: }
207: }
208:
209: // no more tokens, but are there more filters? if so, then
210: // the token didn't match everything
211: return (filterPos >= this .parts.length);
212: }
213: }
214:
215: // uses an anti-pattern: use a "null" token to be used for the "**"
216: // filter.
217: static interface IFilterToken {
218: public boolean match(String part);
219: }
220:
221: private static class BeforeFilterToken implements IFilterToken {
222: private String m;
223:
224: public BeforeFilterToken(String s) {
225: this .m = s;
226: }
227:
228: public boolean match(String part) {
229: return part.endsWith(this .m);
230: }
231: }
232:
233: private static class EndFilterToken implements IFilterToken {
234: private String m;
235:
236: public EndFilterToken(String s) {
237: this .m = s;
238: }
239:
240: public boolean match(String part) {
241: return part.startsWith(this .m);
242: }
243: }
244:
245: private static class ExactFilterToken implements IFilterToken {
246: private String m;
247:
248: public ExactFilterToken(String s) {
249: this .m = s;
250: }
251:
252: public boolean match(String part) {
253: return part.equals(this .m);
254: }
255: }
256:
257: private static class AnyFilterToken implements IFilterToken {
258: public boolean match(String part) {
259: return true;
260: }
261: }
262:
263: private static final IFilterToken ANY_TOKEN = new AnyFilterToken();
264: private static final IFilterToken NULL_TOKEN = new AnyFilterToken();
265:
266: public void setPercentage(double v) {
267: this .minPercent = v;
268: }
269:
270: public void setProperty(String p) {
271: this .failureProperty = p;
272: }
273:
274: public void addInclude(IncludeAndExclude iae) {
275: if (iae != null) {
276: iae.isInclude = true;
277: this .includeExcludeSet.addElement(iae);
278: }
279: }
280:
281: public void addExclude(IncludeAndExclude iae) {
282: if (iae != null) {
283: iae.isInclude = false;
284: this .includeExcludeSet.addElement(iae);
285: }
286: }
287:
288: /**
289: * Called when the task is finished generating all the reports. This
290: * may be useful for styles that join all the reports together.
291: */
292: public void reportComplete(Project project, Vector errorList)
293: throws BuildException {
294: double actual = getActualPercentage();
295: project.log("Testing for minimal coverage of "
296: + DOUBLE_FORMATTER.format(this .minPercent)
297: + ", and found an actual coverage of "
298: + DOUBLE_FORMATTER.format(actual), Project.MSG_VERBOSE);
299: if (actual < this .minPercent) {
300: // post an error
301: if (this .failureProperty != null) {
302: project.setNewProperty(this .failureProperty, Double
303: .toString(actual));
304: }
305: errorList
306: .addElement("Did not have sufficient coverage: requires "
307: + DOUBLE_FORMATTER.format(this .minPercent)
308: + ", but found "
309: + DOUBLE_FORMATTER.format(actual) + ".");
310: }
311: }
312:
313: public void generateReport(Project project, Document doc,
314: String moduleName) throws BuildException {
315: Vector inClass = new Vector();
316: Vector exClass = new Vector();
317: Vector inMod = new Vector();
318: Vector exMod = new Vector();
319: setupFilters(inClass, exClass, inMod, exMod);
320:
321: if (!"all".equalsIgnoreCase(moduleName)
322: && !matchModule(moduleName, inMod, exMod)) {
323: return;
324: }
325:
326: NodeList ccList = doc.getElementsByTagName("classcoverage");
327: for (int ccIndex = 0; ccIndex < ccList.getLength(); ++ccIndex) {
328: Element ccEl = (Element) ccList.item(ccIndex);
329:
330: NodeList coverList = ccEl.getElementsByTagName("cover");
331: for (int coverIndex = 0; coverIndex < coverList.getLength(); ++coverIndex) {
332: Element coverEl = (Element) coverList.item(coverIndex);
333: NodeList modList = coverEl
334: .getElementsByTagName("modulecover");
335: if (modList != null && modList.getLength() > 0) {
336: for (int modIndex = 0; modIndex < modList
337: .getLength(); ++modIndex) {
338: Element modEl = (Element) modList
339: .item(modIndex);
340: if (matchModule(modEl.getAttribute("measure"),
341: inMod, exMod)) {
342: cover(project, modEl);
343: }
344: }
345: } else {
346: cover(project, coverEl);
347: }
348: }
349: }
350: /*
351: <classcoverage classname="x.main" classsignature="x.main-2208445997" package="x" sourcefile="main.java">
352: <cover covered="25" display-covered="25" display-percentcovered="86.21" display-total="29" display-weightedpercent="375.01" percentcovered="86.20689655172414" total="29" weightedpercent="375.00766949585847">
353: <modulecover covered="19" display-covered="19" display-percentcovered="86.36" display-total="22" display-weightedpercent="375.66" measure="BytecodeCount" percentcovered="86.36363636363636" total="22" weightedpercent="375.66213314244806"></modulecover>
354: <modulecover covered="6" display-covered="6" display-percentcovered="85.71" display-total="7" display-weightedpercent="374.35" measure="LineCount" percentcovered="85.71428571428571" total="7" weightedpercent="374.3532058492689"></modulecover>
355: </cover>
356: */
357:
358: }
359:
360: protected void cover(Project project, Element el) {
361: String coveredS = el.getAttribute("covered");
362: String markedS = el.getAttribute("total");
363: try {
364: int cov = Integer.parseInt(coveredS);
365: int mar = Integer.parseInt(markedS);
366:
367: // if any of the values were invalid, then this shouldn't be reached
368: if (cov > 0 && mar > 0) {
369: this .coverCount += cov;
370: this .markCount += mar;
371: }
372: } catch (NumberFormatException e) {
373: project.log(
374: "Invalid covered attribute: expected a number.",
375: Project.MSG_VERBOSE);
376: }
377: }
378:
379: protected double getActualPercentage() {
380: if (this .markCount == 0) {
381: return 100.0;
382: }
383: // else
384: return ((double) this .coverCount * 100.0)
385: / (double) this .markCount;
386: }
387:
388: protected boolean matchModule(String moduleName, Vector inMod,
389: Vector exMod) {
390: // it must pass all inMod and fail all exMod
391: if (!matchModuleInSet(moduleName, inMod)) {
392: return false;
393: }
394: if (matchModuleInSet(moduleName, exMod)) {
395: return false;
396: }
397: return true;
398: }
399:
400: protected boolean matchModuleInSet(String moduleName, Vector set) {
401: Enumeration e = set.elements();
402: while (e.hasMoreElements()) {
403: String m = (String) e.nextElement();
404: if ("*".equals(m) || moduleName.equalsIgnoreCase(m)) {
405: return true;
406: }
407: }
408: return false;
409: }
410:
411: protected boolean matchClass(String className, Vector inClass,
412: Vector exClass) {
413: if (!matchClassInSet(className, inClass)) {
414: return false;
415: }
416: if (matchClassInSet(className, exClass)) {
417: return false;
418: }
419: return true;
420: }
421:
422: protected boolean matchClassInSet(String className, Vector set) {
423: Enumeration e = set.elements();
424: while (e.hasMoreElements()) {
425: ClassFilter cf = (ClassFilter) e.nextElement();
426: if (cf.match(className)) {
427: return true;
428: }
429: }
430: return false;
431: }
432:
433: protected void setupFilters(Vector includeClass,
434: Vector excludeClass, Vector includeModule,
435: Vector excludeModule) throws BuildException {
436: Enumeration e = this .includeExcludeSet.elements();
437: while (e.hasMoreElements()) {
438: IncludeAndExclude iae = (IncludeAndExclude) e.nextElement();
439: iae.check();
440: if (iae.module) {
441: if (iae.isInclude) {
442: includeModule.addElement(iae.value);
443: } else {
444: excludeModule.addElement(iae.value);
445: }
446: } else
447: // if (iae.corp)
448: {
449: if (iae.isInclude) {
450: includeClass.addElement(new ClassFilter(iae.value));
451: } else {
452: excludeClass.addElement(new ClassFilter(iae.value));
453: }
454: }
455: }
456:
457: // by default, if no includes are specified, then there's an implicit
458: // include **
459: if (includeModule.size() <= 0) {
460: includeModule.addElement("*");
461: }
462: if (includeClass.size() <= 0) {
463: includeClass.addElement(new ClassFilter("**"));
464: }
465: }
466: }
|