001: /*
002: * FindBugs - Find bugs in Java programs
003: * Copyright (C) 2004, 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.config;
021:
022: import java.util.HashMap;
023: import java.util.HashSet;
024: import java.util.Iterator;
025: import java.util.Map;
026: import java.util.Set;
027: import java.util.StringTokenizer;
028: import java.util.TreeSet;
029:
030: import edu.umd.cs.findbugs.BugInstance;
031: import edu.umd.cs.findbugs.BugPattern;
032: import edu.umd.cs.findbugs.BugProperty;
033: import edu.umd.cs.findbugs.Detector;
034: import edu.umd.cs.findbugs.DetectorFactoryCollection;
035: import edu.umd.cs.findbugs.I18N;
036:
037: /**
038: * Settings for user filtering of warnings for a project.
039: * This includes selecting particular bug categories
040: * to view, as well as a minimum warning priority.
041: * Includes support for encoding these settings as a String,
042: * which can easily be stored as a persistent project property
043: * in Eclipse.
044: *
045: * @see BugInstance
046: * @author David Hovemeyer
047: */
048: public class ProjectFilterSettings implements Cloneable {
049: /** Text string for high priority. */
050: public static final String HIGH_PRIORITY = "High";
051:
052: /** Text string for medium priority. */
053: public static final String MEDIUM_PRIORITY = "Medium";
054:
055: /** Text string for low priority. */
056: public static final String LOW_PRIORITY = "Low";
057:
058: /** Text string for experimental priority. */
059: public static final String EXPERIMENTAL_PRIORITY = "Experimental";
060:
061: /** Default warning threshold priority. */
062: public static final String DEFAULT_PRIORITY = MEDIUM_PRIORITY;
063:
064: /** Map of priority level names to their numeric values. */
065: private static Map<String, Integer> priorityNameToValueMap = new HashMap<String, Integer>();
066: static {
067: priorityNameToValueMap.put(HIGH_PRIORITY,
068: (Detector.HIGH_PRIORITY));
069: priorityNameToValueMap.put(MEDIUM_PRIORITY,
070: (Detector.NORMAL_PRIORITY));
071: priorityNameToValueMap.put(LOW_PRIORITY,
072: (Detector.LOW_PRIORITY));
073: priorityNameToValueMap.put(EXPERIMENTAL_PRIORITY,
074: (Detector.EXP_PRIORITY));
075: }
076:
077: /**
078: * The character used for delimiting whole fields in filter settings encoded as strings
079: */
080: private static String FIELD_DELIMITER = "|";
081: /**
082: * The character used for delimiting list items in filter settings encoded as strings
083: */
084: private static String LISTITEM_DELIMITER = ",";
085:
086: // Fields
087: private Set<String> activeBugCategorySet; // not used for much: hiddenBugCategorySet has priority.
088: private Set<String> hiddenBugCategorySet;
089: private String minPriority;
090: private int minPriorityAsInt;
091: private boolean displayFalseWarnings;
092:
093: /**
094: * Constructor.
095: * This is not meant to be called directly; use one of the factory methods instead.
096: */
097: private ProjectFilterSettings() {
098: DetectorFactoryCollection.instance(); // ensure detectors loaded
099:
100: // initially all known bug categories are active
101: // using SortedSet to allow better revision control on saved sorted properties
102: this .activeBugCategorySet = new TreeSet<String>(I18N.instance()
103: .getBugCategories());
104: this .hiddenBugCategorySet = new HashSet<String>();
105: setMinPriority(DEFAULT_PRIORITY);
106: this .displayFalseWarnings = false;
107: }
108:
109: /**
110: * Factory method to create a default ProjectFilterSettings object.
111: * Uses the default warning priority threshold, and enables
112: * all bug categories.
113: *
114: * @return a default ProjectFilterSettings object
115: */
116: public static ProjectFilterSettings createDefault() {
117: ProjectFilterSettings result = new ProjectFilterSettings();
118:
119: // Set default priority threshold
120: result.setMinPriority(DEFAULT_PRIORITY);
121:
122: return result;
123: }
124:
125: /**
126: * Create ProjectFilterSettings from an encoded string.
127: *
128: * @param s the encoded string
129: * @return the ProjectFilterSettings
130: */
131: public static ProjectFilterSettings fromEncodedString(String s) {
132: ProjectFilterSettings result = new ProjectFilterSettings();
133:
134: if (s.length() > 0) {
135: int bar = s.indexOf(FIELD_DELIMITER);
136: String minPriority;
137: if (bar >= 0) {
138: minPriority = s.substring(0, bar);
139: s = s.substring(bar + 1);
140: } else {
141: minPriority = s;
142: s = "";
143: }
144: if (priorityNameToValueMap.get(minPriority) == null) {
145: minPriority = DEFAULT_PRIORITY;
146: }
147: result.setMinPriority(minPriority);
148: }
149:
150: if (s.length() > 0) {
151: int bar = s.indexOf(FIELD_DELIMITER);
152: String categories;
153: if (bar >= 0) {
154: categories = s.substring(0, bar);
155: s = s.substring(bar + 1);
156: } else {
157: categories = s;
158: s = "";
159: }
160: StringTokenizer t = new StringTokenizer(categories,
161: LISTITEM_DELIMITER);
162: while (t.hasMoreTokens()) {
163: String category = t.nextToken();
164: // 'result' probably already contains 'category', since
165: // it contains all known bug category keys by default.
166: // But add it to the set anyway in case it is an unknown key.
167: result.addCategory(category);
168: }
169: }
170:
171: if (s.length() > 0) {
172: int bar = s.indexOf(FIELD_DELIMITER);
173: String displayFalseWarnings;
174: if (bar >= 0) {
175: displayFalseWarnings = s.substring(0, bar);
176: s = s.substring(bar + 1);
177: } else {
178: displayFalseWarnings = s;
179: s = "";
180: }
181: result.setDisplayFalseWarnings(Boolean.valueOf(
182: displayFalseWarnings).booleanValue());
183: }
184:
185: if (s.length() > 0) {
186: // Can add other fields here...
187: assert true;
188: }
189:
190: return result;
191:
192: }
193:
194: /**
195: * set the hidden bug categories on the specifed ProjectFilterSettings
196: * from an encoded string
197: *
198: * @param result the ProjectFilterSettings from which to remove bug categories
199: * @param s the encoded string
200: * @see ProjectFilterSettings#hiddenFromEncodedString(ProjectFilterSettings, String)
201: */
202: public static void hiddenFromEncodedString(
203: ProjectFilterSettings result, String s) {
204:
205: if (s.length() > 0) {
206: int bar = s.indexOf(FIELD_DELIMITER);
207: String categories;
208: if (bar >= 0) {
209: categories = s.substring(0, bar);
210: } else {
211: categories = s;
212: }
213: StringTokenizer t = new StringTokenizer(categories,
214: LISTITEM_DELIMITER);
215: while (t.hasMoreTokens()) {
216: String category = t.nextToken();
217: result.removeCategory(category);
218: }
219: }
220:
221: }
222:
223: /**
224: * Return whether or not a warning should be displayed,
225: * according to the project filter settings.
226: *
227: * @param bugInstance the warning
228: * @return true if the warning should be displayed, false if not
229: */
230: public boolean displayWarning(BugInstance bugInstance) {
231:
232: int priority = bugInstance.getPriority();
233: if (priority > getMinPriorityAsInt()) {
234: return false;
235: }
236:
237: BugPattern bugPattern = bugInstance.getBugPattern();
238:
239: // HACK: it is conceivable that the detector plugin which generated
240: // this warning is not available any more, in which case we can't
241: // find out the category. Let the warning be visible in this case.
242: if (bugPattern != null
243: && !containsCategory(bugPattern.getCategory())) {
244: return false;
245: }
246:
247: if (!displayFalseWarnings) {
248: boolean isFalseWarning = !Boolean
249: .valueOf(
250: bugInstance.getProperty(BugProperty.IS_BUG,
251: "true")).booleanValue();
252: if (isFalseWarning) {
253: return false;
254: }
255: }
256:
257: return true;
258: }
259:
260: /**
261: * Set minimum warning priority threshold.
262: *
263: * @param minPriority the priority threshold: one of "High", "Medium", or "Low"
264: */
265: public void setMinPriority(String minPriority) {
266: this .minPriority = minPriority;
267:
268: Integer value = priorityNameToValueMap.get(minPriority);
269: if (value == null) {
270: value = priorityNameToValueMap.get(DEFAULT_PRIORITY);
271: if (value == null) {
272: throw new IllegalStateException();
273: }
274: }
275:
276: this .minPriorityAsInt = value.intValue();
277:
278: }
279:
280: /**
281: * Get the minimum warning priority threshold.
282: *
283: * @return minimum warning priority threshold: one of "High", "Medium", or "Low"
284: */
285: public String getMinPriority() {
286: return this .minPriority;
287: }
288:
289: /**
290: * Return the minimum warning priority threshold as an integer.
291: *
292: * @return the minimum warning priority threshold as an integer
293: */
294: public int getMinPriorityAsInt() {
295: return minPriorityAsInt;
296: }
297:
298: /**
299: * Add a bug category to the set of categories to be displayed.
300: *
301: * @param category the bug category: e.g., "CORRECTNESS"
302: */
303: public void addCategory(String category) {
304: this .hiddenBugCategorySet.remove(category);
305: this .activeBugCategorySet.add(category);
306: }
307:
308: /**
309: * Remove a bug category from the set of categories to be displayed.
310: *
311: * @param category the bug category: e.g., "CORRECTNESS"
312: */
313: public void removeCategory(String category) {
314: this .hiddenBugCategorySet.add(category);
315: this .activeBugCategorySet.remove(category);
316: }
317:
318: /**
319: * Clear all bug categories from the hidden list.
320: * So the effect is to enable all bug categories.
321: */
322: public void clearAllCategories() {
323: this .activeBugCategorySet.addAll(hiddenBugCategorySet);
324: this .hiddenBugCategorySet.clear();
325: }
326:
327: /**
328: * Returns false if the given category is hidden
329: * in the project filter settings.
330: *
331: * @param category the category
332: * @return false if the category is hidden, true if not
333: */
334: public boolean containsCategory(String category) {
335: // do _not_ consult the activeBugCategorySet: if not hidden return true.
336: return !hiddenBugCategorySet.contains(category);
337: }
338:
339: /**
340: * Return set of active (enabled) bug categories.
341: *
342: * Note that bug categories that are not explicity
343: * hidden will appear active even if they are not
344: * members of this set.
345: *
346: * @return the set of active categories
347: */
348: public Set<String> getActiveCategorySet() {
349: Set<String> result = new TreeSet<String>();
350: result.addAll(this .activeBugCategorySet);
351: return result;
352: }
353:
354: /**
355: * Set whether or not false warnings should be displayed.
356: *
357: * @param displayFalseWarnings true if false warnings should be displayed,
358: * false if not
359: */
360: public void setDisplayFalseWarnings(boolean displayFalseWarnings) {
361: this .displayFalseWarnings = displayFalseWarnings;
362: }
363:
364: /**
365: * Get whether or not false warnings should be displayed.
366: *
367: * @return true if false warnings should be displayed, false if not
368: */
369: public boolean displayFalseWarnings() {
370: return displayFalseWarnings;
371: }
372:
373: /**
374: * Create a string containing the encoded form of the hidden bug categories
375: *
376: * @return an encoded string
377: */
378: public String hiddenToEncodedString() {
379: StringBuffer buf = new StringBuffer();
380: // Encode hidden bug categories
381: for (Iterator<String> i = hiddenBugCategorySet.iterator(); i
382: .hasNext();) {
383: buf.append(i.next());
384: if (i.hasNext()) {
385: buf.append(LISTITEM_DELIMITER);
386: }
387: }
388: buf.append(FIELD_DELIMITER);
389:
390: return buf.toString();
391: }
392:
393: /**
394: * Create a string containing the encoded form of the ProjectFilterSettings.
395: *
396: * @return an encoded string
397: */
398: public String toEncodedString() {
399: // Priority threshold
400: StringBuffer buf = new StringBuffer();
401: buf.append(getMinPriority());
402:
403: // Encode enabled bug categories. Note that these aren't really used for much.
404: // They only come in to play when parsed by a version of FindBugs older than 1.1.
405: buf.append(FIELD_DELIMITER);
406: for (Iterator<String> i = activeBugCategorySet.iterator(); i
407: .hasNext();) {
408: buf.append(i.next());
409: if (i.hasNext()) {
410: buf.append(LISTITEM_DELIMITER);
411: }
412:
413: }
414:
415: // Whether to display false warnings
416: buf.append(FIELD_DELIMITER);
417: buf.append(displayFalseWarnings ? "true" : "false");
418:
419: return buf.toString();
420: }
421:
422: @Override
423: public String toString() {
424: return toEncodedString();
425: }
426:
427: /* (non-Javadoc)
428: * @see java.lang.Object#equals(java.lang.Object)
429: */
430: @Override
431: public boolean equals(Object obj) {
432: if (obj == null || obj.getClass() != this .getClass()) {
433: return false;
434: }
435: ProjectFilterSettings other = (ProjectFilterSettings) obj;
436:
437: if (!this .getMinPriority().equals(other.getMinPriority())) {
438: return false;
439: }
440:
441: // don't compare the activeBugCategorySet. compare the hiddenBugCategorySet only
442: if (!this .hiddenBugCategorySet
443: .equals(other.hiddenBugCategorySet)) {
444: return false;
445: }
446:
447: if (this .displayFalseWarnings != other.displayFalseWarnings) {
448: return false;
449: }
450:
451: return true;
452: }
453:
454: /* (non-Javadoc)
455: * @see java.lang.Object#clone()
456: */
457: @Override
458: public Object clone() {
459: try {
460: // Create shallow copy
461: ProjectFilterSettings clone = (ProjectFilterSettings) super
462: .clone();
463:
464: // Copy field contents
465: clone.hiddenBugCategorySet = new HashSet<String>();
466: clone.hiddenBugCategorySet
467: .addAll(this .hiddenBugCategorySet);
468: clone.activeBugCategorySet = new TreeSet<String>();
469: clone.activeBugCategorySet
470: .addAll(this .activeBugCategorySet);
471: clone.setMinPriority(this .getMinPriority());
472: clone.displayFalseWarnings = this .displayFalseWarnings;
473:
474: return clone;
475: } catch (CloneNotSupportedException e) {
476: // Should not happen!
477: throw new AssertionError(e);
478: }
479:
480: }
481:
482: /* (non-Javadoc)
483: * @see java.lang.Object#hashCode()
484: */
485: @Override
486: public int hashCode() {
487: return minPriority.hashCode() + 1009
488: * hiddenBugCategorySet.hashCode()
489: + (displayFalseWarnings ? 7919 : 0);
490: }
491:
492: /**
493: * Convert an integer warning priority threshold value to
494: * a String.
495: */
496: public static String getIntPriorityAsString(int prio) {
497: String minPriority;
498: switch (prio) {
499: case Detector.EXP_PRIORITY:
500: minPriority = ProjectFilterSettings.EXPERIMENTAL_PRIORITY;
501: break;
502: case Detector.LOW_PRIORITY:
503: minPriority = ProjectFilterSettings.LOW_PRIORITY;
504: break;
505: case Detector.NORMAL_PRIORITY:
506: minPriority = ProjectFilterSettings.MEDIUM_PRIORITY;
507: break;
508: case Detector.HIGH_PRIORITY:
509: minPriority = ProjectFilterSettings.HIGH_PRIORITY;
510: break;
511: default:
512: minPriority = ProjectFilterSettings.DEFAULT_PRIORITY;
513: break;
514: }
515: return minPriority;
516: }
517: }
518:
519: // vim:ts=4
|