001: /*
002: * FindBugs - Find bugs in Java programs
003: * Copyright (C) 2003-2005, University of Maryland
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation; either
008: * version 2.1 of the License, or (at your option) any later version.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: *
015: * You should have received a copy of the GNU Lesser General Public
016: * License along with this library; if not, write to the Free Software
017: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: */
019:
020: package edu.umd.cs.findbugs.filter;
021:
022: import java.io.BufferedInputStream;
023: import java.io.File;
024: import java.io.FileInputStream;
025: import java.io.IOException;
026: import java.io.OutputStream;
027: import java.io.Reader;
028: import java.util.IdentityHashMap;
029: import java.util.Iterator;
030:
031: import org.dom4j.Attribute;
032: import org.dom4j.Document;
033: import org.dom4j.DocumentException;
034: import org.dom4j.Element;
035: import org.dom4j.io.SAXReader;
036: import org.xml.sax.InputSource;
037: import org.xml.sax.SAXException;
038: import org.xml.sax.XMLReader;
039: import org.xml.sax.helpers.XMLReaderFactory;
040:
041: import edu.umd.cs.findbugs.BugInstance;
042: import edu.umd.cs.findbugs.SAXBugCollectionHandler;
043: import edu.umd.cs.findbugs.SystemProperties;
044: import edu.umd.cs.findbugs.util.Strings;
045: import edu.umd.cs.findbugs.util.Util;
046: import edu.umd.cs.findbugs.xml.OutputStreamXMLOutput;
047: import edu.umd.cs.findbugs.xml.XMLOutput;
048:
049: /**
050: * Filter to match a subset of BugInstances.
051: * The filter criteria are read from an XML file.
052: *
053: * @author David Hovemeyer
054: */
055:
056: public class Filter extends OrMatcher {
057: private static final boolean DEBUG = SystemProperties
058: .getBoolean("filter.debug");
059:
060: private IdentityHashMap<Matcher, Boolean> disabled = new IdentityHashMap<Matcher, Boolean>();
061:
062: /**
063: * Constructor for empty filter
064: *
065: */
066: public Filter() {
067:
068: }
069:
070: @Override
071: public int hashCode() {
072: final int prime = 31;
073: int result = super .hashCode();
074: result = prime * result
075: + ((disabled == null) ? 0 : disabled.hashCode());
076: return result;
077: }
078:
079: @Override
080: public boolean equals(Object obj) {
081: if (this == obj)
082: return true;
083: if (!super .equals(obj))
084: return false;
085: if (!(obj instanceof Filter))
086: return false;
087: final Filter other = (Filter) obj;
088: if (disabled == null) {
089: if (other.disabled != null)
090: return false;
091: } else if (!disabled.equals(other.disabled))
092: return false;
093: return true;
094: }
095:
096: public boolean isEmpty() {
097: return super .numberChildren() == 0;
098: }
099:
100: public void setEnabled(Matcher m, boolean value) {
101: if (value)
102: enable(m);
103: else
104: disable(m);
105: }
106:
107: public void disable(Matcher m) {
108: disabled.put(m, true);
109: }
110:
111: public boolean isEnabled(Matcher m) {
112: return !disabled.containsKey(m);
113: }
114:
115: public void enable(Matcher m) {
116: disabled.remove(m);
117: }
118:
119: public static Filter parseFilter(String fileName)
120: throws IOException {
121: return new Filter(fileName);
122: }
123:
124: /**
125: * Constructor.
126: *
127: * @param fileName name of the filter file
128: * @throws IOException
129: * @throws SAXException
130: * @throws FilterException
131: */
132: public Filter(String fileName) throws IOException {
133: try {
134: parse(fileName);
135: if (false)
136: System.out.println("Parsed: " + this );
137: } catch (SAXException e) {
138: throw new IOException(e.getMessage());
139: }
140: }
141:
142: public boolean contains(Matcher child) {
143: return children.contains(child);
144: }
145:
146: /**
147: * Add if not present, but do not enable if already present and disabled
148: * @param child
149: */
150: public void softAdd(Matcher child) {
151: super .addChild(child);
152: }
153:
154: @Override
155: public void addChild(Matcher child) {
156: super .addChild(child);
157: enable(child);
158: }
159:
160: @Override
161: public void removeChild(Matcher child) {
162: enable(child);//Remove from disabled before removing it
163: super .removeChild(child);
164: }
165:
166: @Override
167: public void clear() {
168: disabled.clear();
169: super .clear();
170: }
171:
172: @Override
173: public boolean match(BugInstance bugInstance) {
174: Iterator<Matcher> i = childIterator();
175: while (i.hasNext()) {
176: Matcher child = i.next();
177: if (isEnabled(child) && child.match(bugInstance))
178: return true;
179: }
180: return false;
181: }
182:
183: /**
184: * Parse and load the given filter file.
185: *
186: * @param fileName name of the filter file
187: * @throws IOException
188: * @throws SAXException
189: * @throws FilterException
190: */
191: private void parse(String fileName) throws IOException,
192: SAXException {
193:
194: if (true) {
195: File file = new File(fileName);
196: SAXBugCollectionHandler handler = new SAXBugCollectionHandler(
197: this , file);
198: XMLReader xr = XMLReaderFactory.createXMLReader();
199: xr.setContentHandler(handler);
200: xr.setErrorHandler(handler);
201: FileInputStream fileInputStream = new FileInputStream(file);
202: try {
203: Reader reader = Util.getReader(fileInputStream);
204: xr.parse(new InputSource(reader));
205: } finally {
206: Util.closeSilently(fileInputStream);
207: }
208: return;
209:
210: }
211: Document filterDoc = null;
212:
213: FileInputStream fileInputStream = new FileInputStream(fileName);
214:
215: try {
216: SAXReader reader = new SAXReader();
217: filterDoc = reader.read(new BufferedInputStream(
218: fileInputStream));
219: } catch (DocumentException e) {
220: throw new FilterException("Couldn't parse filter file "
221: + fileName, e);
222: }
223:
224: int count = 1;
225: // Iterate over Match elements
226: for (Object matchObj : filterDoc
227: .selectNodes("/FindBugsFilter/Match")) {
228: Element matchNode = (Element) matchObj;
229: AndMatcher matchMatcher = new AndMatcher();
230:
231: // Each match node may have either "class" or "classregex" attributes
232: Matcher classMatcher = null;
233: String classAttr = matchNode.valueOf("@class");
234: if (!classAttr.equals("")) {
235: classMatcher = new ClassMatcher(classAttr);
236: } else {
237: String classRegex = matchNode.valueOf("@classregex");
238: if (!classRegex.equals(""))
239: classMatcher = new ClassMatcher("~" + classRegex);
240: }
241: if (classMatcher != null)
242: matchMatcher.addChild(classMatcher);
243:
244: if (DEBUG)
245: System.out.println("Match node");
246:
247: // Iterate over child elements of Match node.
248: Iterator<Element> j = matchNode.elementIterator();
249: while (j.hasNext()) {
250: Element child = j.next();
251: Matcher matcher = getMatcher(child);
252: matchMatcher.addChild(matcher);
253: }
254: if (matchMatcher.numberChildren() == 0)
255: throw new FilterException("Match element #" + count
256: + " (starting at 1) is invalid in filter file "
257: + fileName);
258: // Add the Match matcher to the overall Filter
259: this .addChild(matchMatcher);
260: count++;
261: }
262: if (this .numberChildren() == 0)
263: throw new FilterException(
264: "Could not find any /FindBugsFilter/Match nodes in filter file "
265: + fileName);
266:
267: }
268:
269: /**
270: * Get a Matcher for given Element.
271: *
272: * @param element the Element
273: * @return a Matcher representing that element
274: * @throws FilterException
275: */
276: private static Matcher getMatcher(Element element)
277: throws FilterException {
278: // These will be either BugCode, Priority, Class, Method, Field, or Or elements.
279: String name = element.getName();
280: if (name.equals("BugCode")) {
281: return new BugMatcher(element.valueOf("@name"), "", "");
282: } else if (name.equals("Local")) {
283: return new LocalMatcher(element.valueOf("@name"));
284: } else if (name.equals("BugPattern")) {
285: return new BugMatcher("", element.valueOf("@name"), "");
286: } else if (name.equals("Bug")) {
287: return new BugMatcher(element.valueOf("@code"), element
288: .valueOf("@pattern"), element.valueOf("@category"));
289: } else if (name.equals("Priority")) {
290: return new PriorityMatcher(element.valueOf("@value"));
291: } else if (name.equals("Class")) {
292: Attribute nameAttr = element.attribute("name");
293:
294: if (nameAttr == null)
295: throw new FilterException(
296: "Missing name attribute in Class element");
297:
298: return new ClassMatcher(nameAttr.getValue());
299: } else if (name.equals("Package")) {
300: Attribute nameAttr = element.attribute("name");
301:
302: if (nameAttr == null)
303: throw new FilterException(
304: "Missing name attribute in Package element");
305:
306: String pName = nameAttr.getValue();
307: pName = pName.startsWith("~") ? pName : "~"
308: + Strings.replace(pName, ".", "\\.");
309: return new ClassMatcher(pName + "\\.[^.]+");
310: } else if (name.equals("Method")) {
311: Attribute nameAttr = element.attribute("name");
312: String nameValue;
313: Attribute paramsAttr = element.attribute("params");
314: Attribute returnsAttr = element.attribute("returns");
315:
316: if (nameAttr == null)
317: if (paramsAttr == null || returnsAttr == null)
318: throw new FilterException(
319: "Method element must have eiter name or params and returnss attributes");
320: else
321: nameValue = "~.*"; // any name
322: else
323: nameValue = nameAttr.getValue();
324:
325: if ((paramsAttr != null || returnsAttr != null)
326: && (paramsAttr == null || returnsAttr == null))
327: throw new FilterException(
328: "Method element must have both params and returns attributes if either is used");
329:
330: if (paramsAttr == null)
331: return new MethodMatcher(nameValue);
332: else
333: return new MethodMatcher(nameValue, paramsAttr
334: .getValue(), returnsAttr.getValue());
335: } else if (name.equals("Field")) {
336: Attribute nameAttr = element.attribute("name");
337: String nameValue;
338: Attribute typeAttr = element.attribute("type");
339:
340: if (nameAttr == null)
341: if (typeAttr == null)
342: throw new FilterException(
343: "Field element must have either name or type attribute");
344: else
345: nameValue = "~.*"; // any name
346: else
347: nameValue = nameAttr.getValue();
348:
349: if (typeAttr == null)
350: return new FieldMatcher(nameValue);
351: else
352: return new FieldMatcher(nameValue, typeAttr.getValue());
353: } else if (name.equals("Or")) {
354: OrMatcher orMatcher = new OrMatcher();
355: Iterator<Element> i = element.elementIterator();
356: while (i.hasNext()) {
357: orMatcher.addChild(getMatcher(i.next()));
358: }
359: return orMatcher;
360: } else
361: throw new FilterException("Unknown element: " + name);
362: }
363:
364: public static void main(String[] argv) {
365: try {
366: if (argv.length != 1) {
367: System.err.println("Usage: " + Filter.class.getName()
368: + " <filename>");
369: System.exit(1);
370: }
371:
372: Filter filter = new Filter(argv[0]);
373: filter.writeAsXML(System.out);
374: } catch (Exception e) {
375: e.printStackTrace();
376: System.exit(1);
377: }
378: }
379:
380: public void writeAsXML(OutputStream out) throws IOException {
381:
382: XMLOutput xmlOutput = new OutputStreamXMLOutput(out);
383:
384: xmlOutput.beginDocument();
385: xmlOutput.openTag("FindBugsFilter");
386: writeBodyAsXML(xmlOutput);
387: xmlOutput.closeTag("FindBugsFilter");
388: xmlOutput.finish();
389: }
390:
391: public void writeEnabledMatchersAsXML(OutputStream out)
392: throws IOException {
393:
394: XMLOutput xmlOutput = new OutputStreamXMLOutput(out);
395:
396: xmlOutput.beginDocument();
397: xmlOutput.openTag("FindBugsFilter");
398: Iterator<Matcher> i = childIterator();
399: while (i.hasNext()) {
400: Matcher child = (Matcher) i.next();
401: if (!disabled.containsKey(child))
402: child.writeXML(xmlOutput, false);
403: }
404: ;
405: xmlOutput.closeTag("FindBugsFilter");
406: xmlOutput.finish();
407: }
408:
409: public void writeBodyAsXML(XMLOutput xmlOutput) throws IOException {
410: Iterator<Matcher> i = childIterator();
411: while (i.hasNext()) {
412: Matcher child = (Matcher) i.next();
413: child.writeXML(xmlOutput, disabled.containsKey(child));
414: }
415: }
416:
417: }
418:
419: // vim:ts=4
|