/* * FindBugs - Find bugs in Java programs * Copyright (C) 2003,2004 University of Maryland * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package edu.umd.cs.findbugs; import java.util.Iterator; import java.util.NoSuchElementException; import edu.umd.cs.findbugs.model.ClassNameRewriter; import edu.umd.cs.findbugs.model.ClassNameRewriterUtil; import edu.umd.cs.findbugs.model.IdentityClassNameRewriter; /** * Compare bug instances by only those criteria which we would expect to * remain constant between versions. */ public class VersionInsensitiveBugComparator implements WarningComparator { private ClassNameRewriter classNameRewriter = IdentityClassNameRewriter.instance(); private boolean exactBugPatternMatch = true; private boolean comparePriorities = false; public VersionInsensitiveBugComparator() { } public void setClassNameRewriter(ClassNameRewriter classNameRewriter) { this.classNameRewriter = classNameRewriter; } public void setComparePriorities(boolean b) { comparePriorities = b; } /** * Wrapper for BugAnnotation iterators, which filters out * annotations we don't care about. */ private class FilteringAnnotationIterator implements Iterator<BugAnnotation> { private Iterator<BugAnnotation> iter; private BugAnnotation next; public FilteringAnnotationIterator(Iterator<BugAnnotation> iter) { this.iter = iter; this.next = null; } public boolean hasNext() { findNext(); return next != null; } public BugAnnotation next() { findNext(); if (next == null) throw new NoSuchElementException(); BugAnnotation result = next; next = null; return result; } public void remove() { throw new UnsupportedOperationException(); } private void findNext() { while (next == null) { if (!iter.hasNext()) break; BugAnnotation candidate = iter.next(); if (!isBoring(candidate)) { next = candidate; break; } } } } private boolean isBoring(BugAnnotation annotation) { return !(annotation instanceof LocalVariableAnnotation || annotation.isSignificant()); } private static int compareNullElements(Object a, Object b) { if (a != null) return 1; else if (b != null) return -1; else return 0; } private static String getCode(String pattern) { int sep = pattern.indexOf('_'); if (sep < 0) return ""; return pattern.substring(0, sep); } private void dump(BugInstance bug) { System.out.println(bug.getMessage()); Iterator<BugAnnotation> i = bug.annotationIterator(); while (i.hasNext()) { System.out.println(" " + i.next()); } } public int compare(BugInstance lhs, BugInstance rhs) { // Attributes of BugInstance. // Compare abbreviation // Compare class and method annotations (ignoring line numbers). // Compare field annotations. int cmp; BugPattern lhsPattern = lhs.getBugPattern(); BugPattern rhsPattern = rhs.getBugPattern(); if (lhsPattern == null || rhsPattern == null) { // One of the patterns is missing. // However, we can still accurately match by abbrev (usually) by comparing // the part of the type before the first '_' character. // This is almost always equivalent to the abbrev. String lhsCode = getCode(lhs.getType()); String rhsCode = getCode(rhs.getType()); if ((cmp = lhsCode.compareTo(rhsCode)) != 0) { return cmp; } } else { // Compare by abbrev instead of type. The specific bug type can change // (e.g., "definitely null" to "null on simple path"). Also, we often // change bug pattern types from one version of FindBugs to the next. // // Source line and field name are still matched precisely, so this shouldn't // cause loss of precision. if ((cmp = lhsPattern.getAbbrev().compareTo(rhsPattern.getAbbrev())) != 0) return cmp; if (isExactBugPatternMatch() && (cmp = lhsPattern.getType().compareTo(rhsPattern.getType())) != 0) return cmp; } if (comparePriorities) { cmp = lhs.getPriority() - rhs.getPriority(); if (cmp != 0) return cmp; } Iterator<BugAnnotation> lhsIter = new FilteringAnnotationIterator(lhs.annotationIterator()); Iterator<BugAnnotation> rhsIter = new FilteringAnnotationIterator(rhs.annotationIterator()); annotationLoop: while (lhsIter.hasNext() && rhsIter.hasNext()) { BugAnnotation lhsAnnotation = lhsIter.next(); BugAnnotation rhsAnnotation = rhsIter.next(); Class<? extends BugAnnotation> lhsClass; while (true) { // Different annotation types obviously cannot be equal, // so just compare by class name. lhsClass = lhsAnnotation.getClass(); Class<? extends BugAnnotation> rhsClass = rhsAnnotation.getClass(); if (lhsClass == rhsClass) break; if (lhsClass == LocalVariableAnnotation.class && !((LocalVariableAnnotation)lhsAnnotation).isSignificant() && lhsIter.hasNext()) lhsAnnotation = lhsIter.next(); else if (rhsClass == LocalVariableAnnotation.class && !((LocalVariableAnnotation)rhsAnnotation).isSignificant() && rhsIter.hasNext()) rhsAnnotation = rhsIter.next(); else return lhsClass.getName().compareTo(rhsClass.getName()); } if (lhsClass == ClassAnnotation.class) { // ClassAnnotations should have their class names rewritten to // handle moved and renamed classes. String lhsClassName = classNameRewriter.rewriteClassName( ((ClassAnnotation)lhsAnnotation).getClassName()); String rhsClassName = classNameRewriter.rewriteClassName( ((ClassAnnotation)rhsAnnotation).getClassName()); cmp = lhsClassName.compareTo(rhsClassName); } else if(lhsClass == MethodAnnotation.class ) { // Rewrite class names in MethodAnnotations MethodAnnotation lhsMethod = ClassNameRewriterUtil.convertMethodAnnotation( classNameRewriter, (MethodAnnotation) lhsAnnotation); MethodAnnotation rhsMethod = ClassNameRewriterUtil.convertMethodAnnotation( classNameRewriter, (MethodAnnotation) rhsAnnotation); cmp = lhsMethod.compareTo(rhsMethod); } else if(lhsClass == FieldAnnotation.class) { // Rewrite class names in FieldAnnotations FieldAnnotation lhsField = ClassNameRewriterUtil.convertFieldAnnotation( classNameRewriter, (FieldAnnotation) lhsAnnotation); FieldAnnotation rhsField = ClassNameRewriterUtil.convertFieldAnnotation( classNameRewriter, (FieldAnnotation) rhsAnnotation); cmp = lhsField.compareTo(rhsField); } else if(lhsClass == StringAnnotation.class) { String lhsString = ((StringAnnotation)lhsAnnotation).getValue(); String rhsString = ((StringAnnotation)rhsAnnotation).getValue(); cmp = lhsString.compareTo(rhsString); } else if(lhsClass == LocalVariableAnnotation.class) { String lhsName = ((LocalVariableAnnotation)lhsAnnotation).getName(); String rhsName = ((LocalVariableAnnotation)rhsAnnotation).getName(); if (lhsName.equals("?") || rhsName.equals("?")) continue; cmp = lhsName.compareTo(rhsName); } else if(lhsClass == TypeAnnotation.class) { String lhsType = ((TypeAnnotation)lhsAnnotation).getTypeDescriptor(); String rhsType = ((TypeAnnotation)rhsAnnotation).getTypeDescriptor(); lhsType = ClassNameRewriterUtil.rewriteSignature(classNameRewriter, lhsType); rhsType = ClassNameRewriterUtil.rewriteSignature(classNameRewriter, rhsType); cmp = lhsType.compareTo(rhsType); } else if(lhsClass == IntAnnotation.class) { int lhsValue = ((IntAnnotation)lhsAnnotation).getValue(); int rhsValue = ((IntAnnotation)rhsAnnotation).getValue(); cmp = lhsValue - rhsValue; } else if (isBoring(lhsAnnotation)) { throw new IllegalStateException("Impossible"); } else throw new IllegalStateException("Unknown annotation type: " +lhsClass.getName()); if (cmp != 0) return cmp; } if (interestingNext(rhsIter)) return -1; else if (interestingNext(lhsIter)) return 1; else return 0; } private boolean interestingNext(Iterator<BugAnnotation> i) { while (i.hasNext()) { BugAnnotation a = i.next(); if (isBoring(a)) continue; if (!(a instanceof LocalVariableAnnotation)) return true; if (((LocalVariableAnnotation)a).isSignificant()) return true; } return false; } /** * @param exactBugPatternMatch The exactBugPatternMatch to set. */ public void setExactBugPatternMatch(boolean exactBugPatternMatch) { this.exactBugPatternMatch = exactBugPatternMatch; } /** * @return Returns the exactBugPatternMatch. */ public boolean isExactBugPatternMatch() { return exactBugPatternMatch; } } // vim:ts=4