View Javadoc
1 ////////////////////////////////////////////////////////////////////////////// 2 // Clirr: compares two versions of a java library for binary compatibility 3 // Copyright (C) 2003 - 2004 Lars Kühne 4 // 5 // This library is free software; you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 2.1 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library; if not, write to the Free Software 17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 ////////////////////////////////////////////////////////////////////////////// 19 20 package net.sf.clirr.checks; 21 22 import net.sf.clirr.event.ApiDifference; 23 import net.sf.clirr.event.Severity; 24 import net.sf.clirr.framework.AbstractDiffReporter; 25 import net.sf.clirr.framework.ApiDiffDispatcher; 26 import net.sf.clirr.framework.ClassChangeCheck; 27 import org.apache.bcel.classfile.JavaClass; 28 import org.apache.bcel.classfile.Method; 29 import org.apache.bcel.generic.Type; 30 31 import java.util.ArrayList; 32 import java.util.Collection; 33 import java.util.HashMap; 34 import java.util.Iterator; 35 import java.util.List; 36 import java.util.Map; 37 import java.util.Set; 38 import java.util.TreeSet; 39 40 /*** 41 * Checks the methods of a class. 42 * 43 * @author lkuehne 44 */ 45 public class MethodSetCheck 46 extends AbstractDiffReporter 47 implements ClassChangeCheck 48 { 49 /*** {@inheritDoc} */ 50 public MethodSetCheck(ApiDiffDispatcher dispatcher) 51 { 52 super(dispatcher); 53 } 54 55 public final void check(JavaClass compatBaseline, JavaClass currentVersion) 56 { 57 // Dont't report method problems when gender has changed, as 58 // really the whole API is a pile of crap then - let GenderChange check 59 // do it's job, and that's it 60 if (compatBaseline.isInterface() ^ currentVersion.isInterface()) 61 { 62 return; 63 } 64 65 // The main problem here is to figure out which old method corresponds to which new method. 66 67 // Methods that are named differently are trated as unrelated 68 // 69 // For Methods that differ only in their parameter list we build a similarity table, i.e. 70 // for new method i and old method j we have number that charaterizes how similar 71 // the method signatures are (0 means equal, higher number means more different) 72 73 Map bNameToMethod = buildNameToMethodMap(compatBaseline); 74 Map cNameToMethod = buildNameToMethodMap(currentVersion); 75 76 checkAddedOrRemoved(bNameToMethod, cNameToMethod, compatBaseline, currentVersion); 77 78 // now the key sets of the two maps are equal, 79 // we only have collections of methods that have the same name 80 81 // for each name analyse the differences 82 for (Iterator it = bNameToMethod.keySet().iterator(); it.hasNext();) 83 { 84 String name = (String) it.next(); 85 86 List baselineMethods = (List) bNameToMethod.get(name); 87 List currentMethods = (List) cNameToMethod.get(name); 88 89 while (baselineMethods.size() * currentMethods.size() > 0) 90 { 91 int[][] similarityTable = buildSimilarityTable(baselineMethods, currentMethods); 92 93 int min = Integer.MAX_VALUE; 94 int iMin = baselineMethods.size(); 95 int jMin = currentMethods.size(); 96 for (int i = 0; i < baselineMethods.size(); i++) 97 { 98 for (int j = 0; j < currentMethods.size(); j++) 99 { 100 final int tableEntry = similarityTable[i][j]; 101 if (tableEntry < min) 102 { 103 min = tableEntry; 104 iMin = i; 105 jMin = j; 106 } 107 } 108 } 109 Method iMethod = (Method) baselineMethods.remove(iMin); 110 Method jMethod = (Method) currentMethods.remove(jMin); 111 check(compatBaseline, iMethod, jMethod); 112 } 113 } 114 } 115 116 private int[][] buildSimilarityTable(List baselineMethods, List currentMethods) 117 { 118 int[][] similarityTable = new int[baselineMethods.size()][currentMethods.size()]; 119 for (int i = 0; i < baselineMethods.size(); i++) 120 { 121 for (int j = 0; j < currentMethods.size(); j++) 122 { 123 final Method iMethod = (Method) baselineMethods.get(i); 124 final Method jMethod = (Method) currentMethods.get(j); 125 similarityTable[i][j] = distance(iMethod, jMethod); 126 } 127 } 128 return similarityTable; 129 } 130 131 private int distance(Method m1, Method m2) 132 { 133 final Type[] m1Args = m1.getArgumentTypes(); 134 final Type[] m2Args = m2.getArgumentTypes(); 135 136 if (m1Args.length != m2Args.length) 137 { 138 return 1000 * Math.abs(m1Args.length - m2Args.length); 139 } 140 141 int retVal = 0; 142 for (int i = 0; i < m1Args.length; i++) 143 { 144 if (!m1Args[i].toString().equals(m2Args[i].toString())) 145 { 146 retVal += 1; 147 } 148 } 149 return retVal; 150 } 151 152 /*** 153 * Checks for added or removed methods, modifies the argument maps so their key sets are equal. 154 */ 155 private void checkAddedOrRemoved( 156 Map bNameToMethod, 157 Map cNameToMethod, 158 JavaClass compatBaseline, 159 JavaClass currentVersion) 160 { 161 // create copies to avoid concurrent modification exception 162 Set baselineNames = new TreeSet(bNameToMethod.keySet()); 163 Set currentNames = new TreeSet(cNameToMethod.keySet()); 164 165 for (Iterator it = baselineNames.iterator(); it.hasNext();) 166 { 167 String name = (String) it.next(); 168 if (!currentNames.contains(name)) 169 { 170 Collection removedMethods = (Collection) bNameToMethod.get(name); 171 for (Iterator rmIterator = removedMethods.iterator(); rmIterator.hasNext();) 172 { 173 Method method = (Method) rmIterator.next(); 174 reportMethodRemoved(compatBaseline, method); 175 } 176 bNameToMethod.remove(name); 177 } 178 } 179 180 for (Iterator it = currentNames.iterator(); it.hasNext();) 181 { 182 String name = (String) it.next(); 183 if (!baselineNames.contains(name)) 184 { 185 Collection addedMethods = (Collection) cNameToMethod.get(name); 186 for (Iterator addIterator = addedMethods.iterator(); addIterator.hasNext();) 187 { 188 Method method = (Method) addIterator.next(); 189 reportMethodAdded(currentVersion, method); 190 } 191 cNameToMethod.remove(name); 192 } 193 } 194 } 195 196 private void reportMethodRemoved(JavaClass oldClass, Method oldMethod) 197 { 198 fireDiff("Method '" 199 + getMethodId(oldClass, oldMethod) 200 + "' has been removed", 201 Severity.ERROR, oldClass, oldMethod); 202 } 203 204 private void reportMethodAdded(JavaClass newClass, Method newMethod) 205 { 206 207 final Severity severity = !newClass.isInterface() && (newClass.isFinal() || !newMethod.isAbstract()) 208 ? Severity.INFO 209 : Severity.ERROR; 210 211 fireDiff("Method '" 212 + getMethodId(newClass, newMethod) 213 + "' has been added", 214 severity, newClass, newMethod); 215 } 216 217 /*** 218 * Builds a map from a method name to a List of methods. 219 */ 220 private Map buildNameToMethodMap(JavaClass clazz) 221 { 222 Method[] methods = clazz.getMethods(); 223 Map retVal = new HashMap(); 224 for (int i = 0; i < methods.length; i++) 225 { 226 Method method = methods[i]; 227 228 if (!(method.isPublic() || method.isProtected())) 229 { 230 continue; 231 } 232 233 final String name = method.getName(); 234 List set = (List) retVal.get(name); 235 if (set == null) 236 { 237 set = new ArrayList(); 238 retVal.put(name, set); 239 } 240 set.add(method); 241 } 242 return retVal; 243 } 244 245 private void check(JavaClass compatBaseline, Method baselineMethod, Method currentMethod) 246 { 247 checkParameterTypes(compatBaseline, baselineMethod, currentMethod); 248 checkReturnType(compatBaseline, baselineMethod, currentMethod); 249 checkDeclaredExceptions(compatBaseline, baselineMethod, currentMethod); 250 } 251 252 private void checkParameterTypes(JavaClass compatBaseline, Method baselineMethod, Method currentMethod) 253 { 254 Type[] bArgs = baselineMethod.getArgumentTypes(); 255 Type[] cArgs = currentMethod.getArgumentTypes(); 256 257 if (bArgs.length != cArgs.length) 258 { 259 fireDiff("In Method '" + getMethodId(compatBaseline, baselineMethod) 260 + "' the number of arguments has changed", 261 Severity.ERROR, compatBaseline, baselineMethod); 262 return; 263 } 264 265 //System.out.println("baselineMethod = " + getMethodId(compatBaseline, baselineMethod)); 266 for (int i = 0; i < bArgs.length; i++) 267 { 268 Type bArg = bArgs[i]; 269 Type cArg = cArgs[i]; 270 271 if (bArg.toString().equals(cArg.toString())) 272 { 273 continue; 274 } 275 276 // TODO: Check assignability... 277 fireDiff("Parameter " + (i + 1) + " of '" + getMethodId(compatBaseline, baselineMethod) 278 + "' has changed it's type to " + cArg, 279 Severity.ERROR, compatBaseline, baselineMethod); 280 } 281 } 282 283 private void checkReturnType(JavaClass compatBaseline, Method baselineMethod, Method currentMethod) 284 { 285 Type bReturnType = baselineMethod.getReturnType(); 286 Type cReturnType = currentMethod.getReturnType(); 287 288 // TODO: Check assignability... 289 if (!bReturnType.toString().equals(cReturnType.toString())) 290 { 291 fireDiff("Return type of Method '" + getMethodId(compatBaseline, baselineMethod) 292 + "' has been changed to " + cReturnType, 293 Severity.ERROR, compatBaseline, baselineMethod); 294 } 295 296 297 } 298 299 private void checkDeclaredExceptions( 300 JavaClass compatBaseline, Method baselineMethod, Method currentMethod) 301 { 302 // TODO 303 } 304 305 /*** 306 * Creates a human readable String that is similar to the method signature 307 * and identifies the method within a class. 308 * @param clazz the container of the method 309 * @param method the method to identify. 310 * @return a human readable id, for example "public void print(java.lang.String)" 311 */ 312 private String getMethodId(JavaClass clazz, Method method) 313 { 314 if (!method.isPublic() && !method.isProtected()) 315 { 316 throw new IllegalArgumentException(); 317 } 318 319 StringBuffer buf = new StringBuffer(); 320 321 buf.append(method.isPublic() ? "public" : "protected"); 322 buf.append(" "); 323 324 String name = method.getName(); 325 if ("<init>".equals(name)) 326 { 327 final String className = clazz.getClassName(); 328 int idx = className.lastIndexOf('.'); 329 name = className.substring(idx + 1); 330 } 331 else 332 { 333 buf.append(method.getReturnType()); 334 buf.append(' '); 335 } 336 buf.append(name); 337 buf.append('('); 338 appendHumanReadableArgTypeList(method, buf); 339 buf.append(')'); 340 return buf.toString(); 341 } 342 343 private void appendHumanReadableArgTypeList(Method method, StringBuffer buf) 344 { 345 Type[] argTypes = method.getArgumentTypes(); 346 String argSeparator = ""; 347 for (int i = 0; i < argTypes.length; i++) 348 { 349 buf.append(argSeparator); 350 buf.append(argTypes[i].toString()); 351 argSeparator = ", "; 352 } 353 } 354 355 private void fireDiff(String report, Severity severity, JavaClass clazz, Method method) 356 { 357 final String className = clazz.getClassName(); 358 final ApiDifference diff = 359 new ApiDifference(report + " in " + className, 360 severity, className, getMethodId(clazz, method), null); 361 getApiDiffDispatcher().fireDiff(diff); 362 363 } 364 365 }

This page was automatically generated by Maven