001: /*
002: * FindBugs - Find bugs in Java programs
003: * Copyright (C) 2004,2005 Dave Brosius <dbrosius@qis.net>
004: * Copyright (C) 2004,2005 University of Maryland
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or (at your option) any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020:
021: /*
022: * UserPreferences.java
023: *
024: * Created on May 26, 2004, 11:55 PM
025: */
026:
027: package edu.umd.cs.findbugs.config;
028:
029: import java.io.BufferedInputStream;
030: import java.io.BufferedOutputStream;
031: import java.io.File;
032: import java.io.FileInputStream;
033: import java.io.FileOutputStream;
034: import java.io.IOException;
035: import java.io.InputStream;
036: import java.io.OutputStream;
037: import java.util.Collection;
038: import java.util.HashMap;
039: import java.util.Iterator;
040: import java.util.LinkedHashSet;
041: import java.util.LinkedList;
042: import java.util.List;
043: import java.util.Map;
044: import java.util.Properties;
045: import java.util.Set;
046: import java.util.Map.Entry;
047:
048: import edu.umd.cs.findbugs.DetectorFactory;
049: import edu.umd.cs.findbugs.DetectorFactoryCollection;
050: import edu.umd.cs.findbugs.FindBugs;
051: import edu.umd.cs.findbugs.SystemProperties;
052: import edu.umd.cs.findbugs.TigerSubstitutes;
053:
054: /**
055: * User Preferences outside of any one Project.
056: * This consists of a class to manage the findbugs.prop file found in the user.home.
057: *
058: * @author Dave Brosius
059: */
060: public class UserPreferences implements Cloneable {
061: private static final String PREF_FILE_NAME = ".Findbugs_prefs";
062:
063: private static final int MAX_RECENT_FILES = 9;
064:
065: private static final String DETECTOR_THRESHOLD_KEY = "detector_threshold";
066:
067: private static final String FILTER_SETTINGS_KEY = "filter_settings";
068:
069: private static final String FILTER_SETTINGS2_KEY = "filter_settings_neg";
070:
071: private LinkedList<String> recentProjectsList = new LinkedList<String>();
072:
073: private Map<String, Boolean> detectorEnablementMap = new HashMap<String, Boolean>();
074:
075: private ProjectFilterSettings filterSettings;
076:
077: private static UserPreferences preferencesSingleton = new UserPreferences();
078:
079: public static final String EFFORT_MIN = "min";
080:
081: public static final String EFFORT_DEFAULT = "default";
082:
083: public static final String EFFORT_MAX = "max";
084:
085: private static final String EFFORT_KEY = "effort";
086:
087: private static final String INCLUDE_FILTER_KEY = "includefilter";
088:
089: private static final String EXCLUDE_FILTER_KEY = "excludefilter";
090: private static final String EXCLUDE_BUGS_KEY = "excludebugs";
091:
092: private String effort = EFFORT_DEFAULT;
093:
094: private Collection<String> includeFilterFiles = TigerSubstitutes
095: .emptySet();
096:
097: private Collection<String> excludeFilterFiles = TigerSubstitutes
098: .emptySet();
099: private Collection<String> excludeBugsFiles = TigerSubstitutes
100: .emptySet();
101:
102: private UserPreferences() {
103: this .filterSettings = ProjectFilterSettings.createDefault();
104: }
105:
106: /**
107: * Create default UserPreferences.
108: *
109: * @return default UserPreferences
110: */
111: public static UserPreferences createDefaultUserPreferences() {
112: return new UserPreferences();
113: }
114:
115: /**
116: * Get UserPreferences singleton.
117: * This should only be used if there is a single set of user
118: * preferences to be used for all projects.
119: *
120: * @return the UserPreferences
121: */
122: public static UserPreferences getUserPreferences() {
123: return preferencesSingleton;
124: }
125:
126: /**
127: * Read persistent global UserPreferences from file in
128: * the user's home directory.
129: */
130: public void read() {
131: File prefFile = new File(SystemProperties
132: .getProperty("user.home"), PREF_FILE_NAME);
133: if (!prefFile.exists() || !prefFile.isFile())
134: return;
135: try {
136: read(new FileInputStream(prefFile));
137: } catch (IOException e) {
138: // Ignore - just use default preferences
139: }
140: }
141:
142: /**
143: * Read user preferences from given input stream.
144: * The InputStream is guaranteed to be closed by this method.
145: *
146: * @param in the InputStream
147: * @throws IOException
148: */
149: public void read(InputStream in) throws IOException {
150: BufferedInputStream prefStream = null;
151: Properties props = new Properties();
152: try {
153: prefStream = new BufferedInputStream(in);
154: props.load(prefStream);
155: } finally {
156: try {
157: if (prefStream != null)
158: prefStream.close();
159: } catch (IOException ioe) {
160: // Ignore
161: }
162: }
163:
164: if (props.size() == 0)
165: return;
166: for (int i = 0; i < MAX_RECENT_FILES; i++) {
167: String key = "recent" + i;
168: String projectName = (String) props.get(key);
169: if (projectName != null)
170: recentProjectsList.add(projectName);
171: }
172:
173: for (Map.Entry<?, ?> e : props.entrySet()) {
174:
175: String key = (String) e.getKey();
176: if (!key.startsWith("detector")
177: || key.startsWith("detector_")) {
178: // it is not a detector enablement property
179: continue;
180: }
181: String detectorState = (String) e.getValue();
182: int pipePos = detectorState.indexOf("|");
183: if (pipePos >= 0) {
184: String name = detectorState.substring(0, pipePos);
185: String enabled = detectorState.substring(pipePos + 1);
186: detectorEnablementMap.put(name, Boolean
187: .valueOf(enabled));
188: }
189: }
190:
191: if (props.get(FILTER_SETTINGS_KEY) != null) {
192: // Properties contain encoded project filter settings.
193: filterSettings = ProjectFilterSettings
194: .fromEncodedString(props
195: .getProperty(FILTER_SETTINGS_KEY));
196: } else {
197: // Properties contain only minimum warning priority threshold (probably).
198: // We will honor this threshold, and enable all bug categories.
199: String threshold = (String) props
200: .get(DETECTOR_THRESHOLD_KEY);
201: if (threshold != null) {
202: try {
203: int detectorThreshold = Integer.parseInt(threshold);
204: setUserDetectorThreshold(detectorThreshold);
205: } catch (NumberFormatException nfe) {
206: //Ok to ignore
207: }
208: }
209: }
210: if (props.get(FILTER_SETTINGS2_KEY) != null) {
211: // populate the hidden bug categories in the project filter settings
212: ProjectFilterSettings.hiddenFromEncodedString(
213: filterSettings, props
214: .getProperty(FILTER_SETTINGS2_KEY));
215: }
216: effort = props.getProperty(EFFORT_KEY, EFFORT_DEFAULT);
217: includeFilterFiles = readFilters(props, INCLUDE_FILTER_KEY);
218: excludeFilterFiles = readFilters(props, EXCLUDE_FILTER_KEY);
219: excludeBugsFiles = readFilters(props, EXCLUDE_BUGS_KEY);
220:
221: }
222:
223: /**
224: * Write persistent global UserPreferences to file
225: * in user's home directory.
226: */
227: public void write() {
228: try {
229: File prefFile = new File(SystemProperties
230: .getProperty("user.home"), PREF_FILE_NAME);
231: write(new FileOutputStream(prefFile));
232: } catch (IOException e) {
233: if (FindBugs.DEBUG)
234: e.printStackTrace(); // Ignore
235: }
236: }
237:
238: /**
239: * Write UserPreferences to given OutputStream.
240: * The OutputStream is guaranteed to be closed by this method.
241: *
242: * @param out the OutputStream
243: * @throws IOException
244: */
245: public void write(OutputStream out) throws IOException {
246:
247: Properties props = new SortedProperties();
248:
249: for (int i = 0; i < recentProjectsList.size(); i++) {
250: String projectName = recentProjectsList.get(i);
251: String key = "recent" + i;
252: props.put(key, projectName);
253: }
254:
255: Iterator<Entry<String, Boolean>> it = detectorEnablementMap
256: .entrySet().iterator();
257: while (it.hasNext()) {
258: Entry<String, Boolean> entry = it.next();
259: props.put("detector" + entry.getKey(), entry.getKey() + "|"
260: + String.valueOf(entry.getValue().booleanValue()));
261: }
262:
263: // Save ProjectFilterSettings
264: props
265: .put(FILTER_SETTINGS_KEY, filterSettings
266: .toEncodedString());
267: props.put(FILTER_SETTINGS2_KEY, filterSettings
268: .hiddenToEncodedString());
269:
270: // Backwards-compatibility: save minimum warning priority as integer.
271: // This will allow the properties file to work with older versions
272: // of FindBugs.
273: props.put(DETECTOR_THRESHOLD_KEY, String.valueOf(filterSettings
274: .getMinPriorityAsInt()));
275: props.setProperty(EFFORT_KEY, effort);
276: writeFilters(props, INCLUDE_FILTER_KEY, includeFilterFiles);
277: writeFilters(props, EXCLUDE_FILTER_KEY, excludeFilterFiles);
278: writeFilters(props, EXCLUDE_BUGS_KEY, excludeBugsFiles);
279:
280: OutputStream prefStream = null;
281: try {
282: prefStream = new BufferedOutputStream(out);
283: props.store(prefStream, "FindBugs User Preferences");
284: prefStream.flush();
285: } finally {
286: try {
287: if (prefStream != null)
288: prefStream.close();
289: } catch (IOException ioe) {
290: }
291: }
292: }
293:
294: /**
295: * Get List of recent project filenames.
296: *
297: * @return List of recent project filenames
298: */
299: public List<String> getRecentProjects() {
300: return recentProjectsList;
301: }
302:
303: /**
304: * Add given project filename to the front of the recently-used
305: * project list.
306: *
307: * @param projectName project filename
308: */
309: public void useProject(String projectName) {
310: removeProject(projectName);
311: recentProjectsList.addFirst(projectName);
312: while (recentProjectsList.size() > MAX_RECENT_FILES)
313: recentProjectsList.removeLast();
314: }
315:
316: /**
317: * Remove project filename from the recently-used project list.
318: *
319: * @param projectName project filename
320: */
321: public void removeProject(String projectName) {
322: //It should only be in list once (usually in slot 0) but check entire list...
323: Iterator<String> it = recentProjectsList.iterator();
324: while (it.hasNext()) {
325: //LinkedList, so remove() via iterator is faster than remove(index).
326: if (projectName.equals(it.next()))
327: it.remove();
328: }
329: }
330:
331: /**
332: * Set the enabled/disabled status of given Detector.
333: *
334: * @param factory the DetectorFactory for the Detector to be enabled/disabled
335: * @param enable true if the Detector should be enabled,
336: * false if it should be Disabled
337: */
338: public void enableDetector(DetectorFactory factory, boolean enable) {
339: detectorEnablementMap.put(factory.getShortName(), enable);
340: }
341:
342: /**
343: * Get the enabled/disabled status of given Detector.
344: *
345: * @param factory the DetectorFactory of the Detector
346: * @return true if the Detector is enabled, false if not
347: */
348: public boolean isDetectorEnabled(DetectorFactory factory) {
349: String detectorName = factory.getShortName();
350: Boolean enabled = detectorEnablementMap.get(detectorName);
351: if (enabled == null) {
352: // No explicit preference has been specified for this detector,
353: // so use the default enablement specified by the
354: // DetectorFactory.
355: enabled = factory.isDefaultEnabled();
356: detectorEnablementMap.put(detectorName, enabled);
357: }
358: return enabled;
359: }
360:
361: /**
362: * Enable or disable all known Detectors.
363: *
364: * @param enable true if all detectors should be enabled,
365: * false if they should all be disabled
366: */
367: public void enableAllDetectors(boolean enable) {
368: detectorEnablementMap.clear();
369:
370: DetectorFactoryCollection factoryCollection = DetectorFactoryCollection
371: .instance();
372: for (Iterator<DetectorFactory> i = factoryCollection
373: .factoryIterator(); i.hasNext();) {
374: DetectorFactory factory = i.next();
375: detectorEnablementMap.put(factory.getShortName(), enable);
376: }
377: }
378:
379: /**
380: * Set the ProjectFilterSettings.
381: *
382: * @param filterSettings the ProjectFilterSettings
383: */
384: public void setProjectFilterSettings(
385: ProjectFilterSettings filterSettings) {
386: this .filterSettings = filterSettings;
387: }
388:
389: /**
390: * Get ProjectFilterSettings.
391: *
392: * @return the ProjectFilterSettings
393: */
394: public ProjectFilterSettings getFilterSettings() {
395: return this .filterSettings;
396: }
397:
398: /**
399: * Get the detector threshold (min severity to report a warning).
400: *
401: * @return the detector threshold
402: */
403: public int getUserDetectorThreshold() {
404: return filterSettings.getMinPriorityAsInt();
405: }
406:
407: /**
408: * Set the detector threshold (min severity to report a warning).
409: *
410: * @param threshold the detector threshold
411: */
412: public void setUserDetectorThreshold(int threshold) {
413: String minPriority = ProjectFilterSettings
414: .getIntPriorityAsString(threshold);
415: filterSettings.setMinPriority(minPriority);
416: }
417:
418: /**
419: * Set the detector threshold (min severity to report a warning).
420: *
421: * @param threshold the detector threshold
422: */
423: public void setUserDetectorThreshold(String threshold) {
424: filterSettings.setMinPriority(threshold);
425: }
426:
427: @Override
428: public boolean equals(Object obj) {
429: if (obj == null || obj.getClass() != this .getClass())
430: return false;
431:
432: UserPreferences other = (UserPreferences) obj;
433:
434: return recentProjectsList.equals(other.recentProjectsList)
435: && detectorEnablementMap
436: .equals(other.detectorEnablementMap)
437: && filterSettings.equals(other.filterSettings)
438: && effort.equals(other.effort)
439: && includeFilterFiles.equals(other.includeFilterFiles)
440: && excludeFilterFiles.equals(other.excludeFilterFiles);
441: }
442:
443: @Override
444: public int hashCode() {
445: return recentProjectsList.hashCode()
446: + detectorEnablementMap.hashCode()
447: + filterSettings.hashCode() + effort.hashCode()
448: + includeFilterFiles.hashCode()
449: + excludeFilterFiles.hashCode();
450: }
451:
452: @Override
453: public Object clone() {
454: try {
455: UserPreferences dup = (UserPreferences) super .clone();
456:
457: dup.recentProjectsList = new LinkedList<String>();
458: dup.recentProjectsList.addAll(this .recentProjectsList);
459:
460: dup.detectorEnablementMap = new HashMap<String, Boolean>();
461: dup.detectorEnablementMap
462: .putAll(this .detectorEnablementMap);
463:
464: dup.filterSettings = (ProjectFilterSettings) this .filterSettings
465: .clone();
466:
467: return dup;
468: } catch (CloneNotSupportedException e) {
469: throw new AssertionError(e);
470: }
471: }
472:
473: public String getEffort() {
474: return effort;
475: }
476:
477: public void setEffort(String effort) {
478: if (!EFFORT_MIN.equals(effort)
479: && !EFFORT_DEFAULT.equals(effort)
480: && !EFFORT_MAX.equals(effort)) {
481: throw new IllegalArgumentException("Effort \"" + effort
482: + "\" is not a valid effort value.");
483: }
484: this .effort = effort;
485:
486: }
487:
488: public Collection<String> getIncludeFilterFiles() {
489: return includeFilterFiles;
490: }
491:
492: public void setIncludeFilterFiles(
493: Collection<String> includeFilterFiles) {
494: if (includeFilterFiles == null) {
495: throw new IllegalArgumentException(
496: "includeFilterFiles may not be null.");
497: }
498: this .includeFilterFiles = includeFilterFiles;
499: }
500:
501: public Collection<String> getExcludeBugsFiles() {
502: return excludeBugsFiles;
503: }
504:
505: public void setExcludeBugsFiles(Collection<String> excludeBugsFiles) {
506: if (excludeBugsFiles == null) {
507: throw new IllegalArgumentException(
508: "excludeBugsFiles may not be null.");
509: }
510: this .excludeBugsFiles = excludeBugsFiles;
511: }
512:
513: public void setExcludeFilterFiles(
514: Collection<String> excludeFilterFiles) {
515: if (excludeFilterFiles == null) {
516: throw new IllegalArgumentException(
517: "excludeFilterFiles may not be null.");
518: }
519: this .excludeFilterFiles = excludeFilterFiles;
520: }
521:
522: public Collection<String> getExcludeFilterFiles() {
523: return excludeFilterFiles;
524: }
525:
526: /**
527: * Helper method to read array of strings out of the properties file, using
528: * a Findbugs style format.
529: *
530: * @param props
531: * The properties file to read the array from.
532: * @param keyPrefix
533: * The key prefix of the array.
534: * @return The array of Strings, or an empty array if no values exist.
535: */
536: private Set<String> readFilters(Properties props, String keyPrefix) {
537: Set<String> filters = new LinkedHashSet<String>();
538: int counter = 0;
539: boolean keyFound = true;
540: while (keyFound) {
541: String property = props.getProperty(keyPrefix + counter);
542: if (property != null) {
543: filters.add(property);
544: counter++;
545: } else {
546: keyFound = false;
547: }
548: }
549:
550: return filters;
551: }
552:
553: /**
554: * Helper method to write array of strings out of the properties file, using
555: * a Findbugs style format.
556: *
557: * @param props
558: * The properties file to write the array to.
559: * @param keyPrefix
560: * The key prefix of the array.
561: * @param filters
562: * The filters array to write to the properties.
563: */
564: private void writeFilters(Properties props, String keyPrefix,
565: Collection<String> filters) {
566: int counter = 0;
567: for (String s : filters) {
568: props.setProperty(keyPrefix + counter, s);
569: counter++;
570: }
571: // remove obsolete keys from the properties file
572: boolean keyFound = true;
573: while (keyFound) {
574: String key = keyPrefix + counter;
575: String property = props.getProperty(key);
576: if (property == null) {
577: keyFound = false;
578: } else {
579: props.remove(key);
580: }
581: }
582: }
583:
584: /**
585: * Returns the effort level as an array of feature settings as expected by
586: * FindBugs.
587: *
588: * @return The array of feature settings corresponding to the current effort
589: * setting.
590: */
591: public AnalysisFeatureSetting[] getAnalysisFeatureSettings() {
592: if (effort.equals(EFFORT_DEFAULT)) {
593: return FindBugs.DEFAULT_EFFORT;
594: } else if (effort.equals(EFFORT_MIN)) {
595: return FindBugs.MIN_EFFORT;
596: }
597: return FindBugs.MAX_EFFORT;
598: }
599: }
600:
601: // vim:ts=4
|