[Clirr-devel] CVS: clirr/core/src/java/net/sf/clirr/core ApiDifference.java,NONE,1.1 Checker.java,NO
Status: Alpha
Brought to you by:
lkuehne
Update of /cvsroot/clirr/clirr/core/src/java/net/sf/clirr/core In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv23600/src/java/net/sf/clirr/core Added Files: ApiDifference.java Checker.java CheckerException.java ClassSelector.java DiffListener.java DiffListenerAdapter.java EventMessages.java FileDiffListener.java Message.java MessageManager.java MessageTranslator.java PlainDiffListener.java ScopeSelector.java Severity.java XmlDiffListener.java package.html Log Message: moving from root directory to core --- NEW FILE --- ////////////////////////////////////////////////////////////////////////////// // Clirr: compares two versions of a java library for binary compatibility // Copyright (C) 2003 - 2004 Lars Kühne // // 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 net.sf.clirr.core; /** * Describes an API change. * * @author Lars */ public final class ApiDifference { private static final int HASHCODE_MAGIC = 29; /** * Object representing the message text to be output (or null if * the constructor which takes a message string directly is used). */ private Message message = null; /** human readable change report. */ private String report; /** * severity of the change in terms of binary compatibility, * as determined by clirr. */ private Severity binaryCompatibilitySeverity; /** * severity of the change in terms of source compatibility, * as determined by clirr. */ private Severity sourceCompatibilitySeverity; /** The fully qualified class name that is affected by the API change. */ private String affectedClass; /** * The method that is affected, if any. * <p/> * The content is the method name plus the fully qualified * parameter types separated by comma and space and enclosed in * brackets, e.g. "doStuff(java.lang.String, int)". * <p/> * This value is <code>null</code> if no single method is * affected, i.e. if the * api change affects a field or is global * (like "class is now final"). */ private String affectedMethod; /** * The field that is affected, if any. * <p/> * The content is the field name, e.g. "someValue". * Type information for the field is not available. * <p/> * This value is <code>null</code> if no single field is * affected, i.e. if the * api change affects a method or is global * (like "class is now final"). */ private String affectedField; /** * The set of additional parameters that are available for use * when building the actual message description. These vary depending * upon the actual difference being reported. */ private String[] extraInfo; /** * Invokes the two-severity-level version of this constructor. */ public ApiDifference(Message message, Severity severity, String clazz, String method, String field, String[] args) { this(message, severity, severity, clazz, method, field, args); } /** * Create a new API difference representation. * * @param message is the key of a human readable string describing the * change that was made. * * @param binarySeverity the severity in terms of binary compatibility, * must be non-null. * * @param sourceSeverity the severity in terms of source code compatibility, * must be non-null. * * @param clazz is the fully-qualified name of the class in which the * change occurred, must be non-null. * * @param method the method signature of the method that changed, * <code>null</code> if no method was affected. * * @param field the field name where the change occured, <code>null</code> * if no field was affected. * * @param args is a set of additional change-specific strings which are * made available for the message description string to reference via * the standard {n} syntax. */ public ApiDifference(Message message, Severity binarySeverity, Severity sourceSeverity, String clazz, String method, String field, String[] args) { checkNonNull(message); checkNonNull(binarySeverity); checkNonNull(sourceSeverity); checkNonNull(clazz); this.message = message; this.binaryCompatibilitySeverity = binarySeverity; this.sourceCompatibilitySeverity = sourceSeverity; this.affectedClass = clazz; this.affectedField = field; this.affectedMethod = method; this.extraInfo = args; } /** * Trivial utility method to verify that a specific object is non-null. */ private void checkNonNull(Object o) { if (o == null) { throw new IllegalArgumentException(); } } /** * Return the message object (if any) associated with this difference. * <p> * Checks which support the "new" message API will provide ApiDifference * objects with non-null message objects. */ public Message getMessage() { return message; } /** * The Severity of the API difference in terms of binary compatibility. * ERROR means that clients will definitely break, WARNING means that * clients may break, depending on how they use the library. * See the eclipse paper for further explanation. * * @return the severity of the API difference in terms of binary compatibility. */ public Severity getBinaryCompatibilitySeverity() { return binaryCompatibilitySeverity; } /** * The Severity of the API difference in terms of source compatibility. * Sometimes this is different than {@link #getBinaryCompatibilitySeverity * binary compatibility severity}, for example adding a checked exception * to a method signature is binary compatible but not source compatible. * ERROR means that clients will definitely break, WARNING means that * clients may break, depending on how they use the library. * See the eclipse paper for further explanation. * * @return the severity of the API difference in terms of source code * compatibility. */ public Severity getSourceCompatibilitySeverity() { return sourceCompatibilitySeverity; } /** * Return the maximum of the binary and source compatibility severities. */ public Severity getMaximumSeverity() { final Severity src = getSourceCompatibilitySeverity(); final Severity bin = getBinaryCompatibilitySeverity(); return src.compareTo(bin) < 0 ? bin : src; } /** * Human readable api change description. * * @return a human readable description of this API difference. */ public String getReport(MessageTranslator translator) { if (report != null) { return report; } String desc = translator.getDesc(message); int nArgs = 0; if (extraInfo != null) { nArgs = extraInfo.length; } String[] strings = new String[nArgs + 3]; strings[0] = affectedClass; strings[1] = affectedMethod; strings[2] = affectedField; for (int i = 0; i < nArgs; ++i) { strings[i + 3] = extraInfo[i]; } return java.text.MessageFormat.format(desc, strings); } /** * The fully qualified class name of the class that has changed. * @return fully qualified class name of the class that has changed. */ public String getAffectedClass() { return affectedClass; } /** * Method signature of the method that has changed, if any. * @return method signature or <code>null</code> if no method is affected. */ public String getAffectedMethod() { return affectedMethod; } /** * Field name of the field that has changed, if any. * @return field name or <code>null</code> if no field is affected. */ public String getAffectedField() { return affectedField; } /** * {@inheritDoc} */ public String toString() { return report + " (" + binaryCompatibilitySeverity + ") - " + affectedClass + '[' + affectedField + '/' + affectedMethod + ']'; } } --- NEW FILE --- ////////////////////////////////////////////////////////////////////////////// // Clirr: compares two versions of a java library for binary compatibility // Copyright (C) 2003 - 2004 Lars Kühne // // 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 net.sf.clirr.core; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Enumeration; import java.util.zip.ZipFile; import java.util.zip.ZipEntry; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.net.MalformedURLException; import java.net.URLClassLoader; import net.sf.clirr.core.internal.checks.ClassHierarchyCheck; import net.sf.clirr.core.internal.checks.ClassScopeCheck; import net.sf.clirr.core.internal.checks.ClassModifierCheck; import net.sf.clirr.core.internal.checks.GenderChangeCheck; import net.sf.clirr.core.internal.checks.InterfaceSetCheck; import net.sf.clirr.core.internal.checks.FieldSetCheck; import net.sf.clirr.core.internal.checks.MethodSetCheck; import net.sf.clirr.core.internal.ApiDiffDispatcher; import net.sf.clirr.core.internal.ClassChangeCheck; import net.sf.clirr.core.internal.CoIterator; import net.sf.clirr.core.internal.JavaClassNameComparator; import org.apache.bcel.classfile.JavaClass; import org.apache.bcel.classfile.ClassParser; import org.apache.bcel.util.ClassSet; import org.apache.bcel.util.Repository; import org.apache.bcel.util.ClassLoaderRepository; /** * This is the main class to be used by Clirr frontends, * it implements the checking functionality of Clirr. * Frontends can create an instance of this class * and register themselves as DiffListeners, they are then * informed whenever an API change is detected by the * reportDiffs method. * * @author lkuehne */ public final class Checker implements ApiDiffDispatcher { private static final Message MSG_CLASS_ADDED = new Message(8000); private static final Message MSG_CLASS_REMOVED = new Message(8001); private List listeners = new ArrayList(); private List classChecks = new ArrayList(); private net.sf.clirr.core.ScopeSelector scopeSelector = new ScopeSelector(); /** * Package visible constructor for unit testing. */ Checker(ClassChangeCheck ccc) { classChecks.add(ccc); } /** * Creates a new Checker. */ public Checker() { classChecks.add(new ClassScopeCheck(this, scopeSelector)); classChecks.add(new GenderChangeCheck(this)); classChecks.add(new ClassModifierCheck(this)); classChecks.add(new InterfaceSetCheck(this)); classChecks.add(new ClassHierarchyCheck(this)); classChecks.add(new FieldSetCheck(this, scopeSelector)); classChecks.add(new MethodSetCheck(this, scopeSelector)); } public net.sf.clirr.core.ScopeSelector getScopeSelector() { return scopeSelector; } public void addDiffListener(net.sf.clirr.core.DiffListener listener) { listeners.add(listener); } private void fireStart() { for (Iterator it = listeners.iterator(); it.hasNext();) { net.sf.clirr.core.DiffListener diffListener = (net.sf.clirr.core.DiffListener) it.next(); diffListener.start(); } } private void fireStop() { for (Iterator it = listeners.iterator(); it.hasNext();) { net.sf.clirr.core.DiffListener diffListener = (net.sf.clirr.core.DiffListener) it.next(); diffListener.stop(); } } public void fireDiff(net.sf.clirr.core.ApiDifference diff) { for (Iterator it = listeners.iterator(); it.hasNext();) { net.sf.clirr.core.DiffListener diffListener = (net.sf.clirr.core.DiffListener) it.next(); diffListener.reportDiff(diff); } } /** * Compare the classes in the two sets of jars and report any differences * to this object's DiffListener object. If the classes in those jars reference * third party classes (e.g. as base class, implemented interface or method param), * such third party classes must be made available via the xyzThirdPartyLoader * classloaders. * * @param origJars is a set of jars containing the "original" versions of * the classes to be compared. * * @param newJars is a set of jars containing the new versions of the * classes to be compared. * * @param origThirdPartyLoader is a classloader that provides third party classes * which are referenced by origJars. * * @param newThirdPartyLoader is a classloader that provides third party classes * which are referenced by newJars. * * @param classSelector is an object which determines which classes from the * old and new jars are to be compared. This parameter may be null, in * which case all classes in the old and new jars are compared. */ public void reportDiffs(File[] origJars, File[] newJars, ClassLoader origThirdPartyLoader, ClassLoader newThirdPartyLoader, ClassSelector classSelector) throws CheckerException { if (classSelector == null) { // create a class selector that selects all classes classSelector = new ClassSelector(ClassSelector.MODE_UNLESS); } final ClassSet origClasses = createClassSet(origJars, origThirdPartyLoader, classSelector); final ClassSet newClasses = createClassSet(newJars, newThirdPartyLoader, classSelector); reportDiffs(origClasses, newClasses); } /** * Creates a set of classes to check. * * @param jarFiles a set of jar filed to scan for class files. * * @param thirdPartyClasses loads classes that are referenced * by the classes in the jarFiles * * @param classSelector is an object which determines which classes from the * old and new jars are to be compared. This parameter may be null, in * which case all classes in the old and new jars are compared. */ private static ClassSet createClassSet(File[] jarFiles, ClassLoader thirdPartyClasses, ClassSelector classSelector) throws CheckerException { if (classSelector == null) { // create a class selector that selects all classes classSelector = new ClassSelector(ClassSelector.MODE_UNLESS); } ClassLoader classLoader = createClassLoader(jarFiles, thirdPartyClasses); Repository repository = new ClassLoaderRepository(classLoader); ClassSet ret = new ClassSet(); for (int i = 0; i < jarFiles.length; i++) { File jarFile = jarFiles[i]; ZipFile zip = null; try { zip = new ZipFile(jarFile, ZipFile.OPEN_READ); } catch (IOException ex) { throw new CheckerException("Cannot open " + jarFile + " for reading", ex); } Enumeration enumEntries = zip.entries(); while (enumEntries.hasMoreElements()) { ZipEntry zipEntry = (ZipEntry) enumEntries.nextElement(); if (!zipEntry.isDirectory() && zipEntry.getName().endsWith(".class")) { JavaClass clazz = extractClass(zipEntry, zip, repository); if (classSelector.isSelected(clazz)) { ret.add(clazz); repository.storeClass(clazz); } } } } return ret; } private static JavaClass extractClass(ZipEntry zipEntry, ZipFile zip, Repository repository) throws CheckerException { String name = zipEntry.getName(); InputStream is = null; try { is = zip.getInputStream(zipEntry); ClassParser parser = new ClassParser(is, name); JavaClass clazz = parser.parse(); clazz.setRepository(repository); return clazz; } catch (IOException ex) { throw new CheckerException("Cannot read " + zipEntry.getName() + " from " + zip.getName(), ex); } finally { if (is != null) { try { is.close(); } catch (IOException ex) { throw new CheckerException("Cannot close " + zip.getName(), ex); } } } } private static ClassLoader createClassLoader(File[] jarFiles, ClassLoader thirdPartyClasses) { final URL[] jarUrls = new URL[jarFiles.length]; for (int i = 0; i < jarFiles.length; i++) { File jarFile = jarFiles[i]; try { URL url = jarFile.toURL(); jarUrls[i] = url; } catch (MalformedURLException ex) { // this should never happen final IllegalArgumentException illegalArgumentException = new IllegalArgumentException("Cannot create classloader with jar file " + jarFile); illegalArgumentException.initCause(ex); throw illegalArgumentException; } } final URLClassLoader jarsLoader = new URLClassLoader(jarUrls, thirdPartyClasses); return jarsLoader; } /** * Checks two sets of classes for api changes and reports * them to the DiffListeners. * @param compatibilityBaseline the classes that form the * compatibility baseline to check against * @param currentVersion the classes that are checked for * compatibility with compatibilityBaseline */ private void reportDiffs(ClassSet compatibilityBaseline, ClassSet currentVersion) throws CheckerException { fireStart(); runClassChecks(compatibilityBaseline, currentVersion); fireStop(); } private void runClassChecks(ClassSet compatBaseline, ClassSet currentVersion) throws CheckerException { JavaClass[] compat = compatBaseline.toArray(); JavaClass[] current = currentVersion.toArray(); CoIterator iter = new CoIterator(JavaClassNameComparator.COMPARATOR, compat, current); while (iter.hasNext()) { iter.next(); JavaClass compatBaselineClass = (JavaClass) iter.getLeft(); JavaClass currentClass = (JavaClass) iter.getRight(); if (compatBaselineClass == null) { final String className = currentClass.getClassName(); final ApiDifference diff = new ApiDifference(MSG_CLASS_ADDED, Severity.INFO, className, null, null, null); fireDiff(diff); } else if (currentClass == null) { final String className = compatBaselineClass.getClassName(); final ApiDifference diff = new ApiDifference(MSG_CLASS_REMOVED, Severity.ERROR, className, null, null, null); fireDiff(diff); } else { // class is available in both releases boolean continueTesting = true; for (Iterator it = classChecks.iterator(); it.hasNext() && continueTesting;) { ClassChangeCheck classChangeCheck = (ClassChangeCheck) it.next(); continueTesting = classChangeCheck.check(compatBaselineClass, currentClass); } } } } } --- NEW FILE --- ////////////////////////////////////////////////////////////////////////////// // Clirr: compares two versions of a java library for binary compatibility // Copyright (C) 2003 - 2004 Lars Kühne // // 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 net.sf.clirr.core; /** * An exception class representing a failure during checking of the * specified jar files. * <p> * The Clirr coding conventions use checked exceptions (such as this one) * for errors whose cause is something external to the clirr library/app. * Unchecked exceptions are used for errors that are due to bugs within * clirr code (assertion-violation type problems). */ public class CheckerException extends Exception { public CheckerException(String msg) { super(msg); } public CheckerException(String msg, Throwable t) { super(msg, t); } } --- NEW FILE --- ////////////////////////////////////////////////////////////////////////////// // Clirr: compares two versions of a java library for binary compatibility // Copyright (C) 2003 - 2004 Lars Kühne // // 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 net.sf.clirr.core; import org.apache.bcel.classfile.JavaClass; import java.util.ArrayList; import java.util.Iterator; /** * Given a JavaClass object, determines whether or not it is "selected", * based on its class or package. This is used to select subsets of the * classes available in a classpath for comparison or testing purposes. * * @author Simon Kitching */ public final class ClassSelector { /** Class for implementing an enumeration. */ public static final class Mode { private Mode() { } } /** positive selection. */ public static final Mode MODE_IF = new Mode(); /** negative selection. */ public static final Mode MODE_UNLESS = new Mode(); private Mode mode; private ArrayList packages = new ArrayList(); private ArrayList packageTrees = new ArrayList(); private ArrayList classes = new ArrayList(); /** * Create a selector. * <p> * When mode is MODE_IF then a class is "selected" if-and-only-if * the class matches one of the criteria defined via the addXXX methods. * In other words, the criteria specify which classes are included * (selected) in the resulting class set. * <p> * When mode is MODE_UNLESS, then a class is "selected" unless the class * matches one of the criteria defined via the addXXX methods. In other * words, the criteria specify which classes are excluded from the * resulting class set. */ public ClassSelector(Mode mode) { this.mode = mode; } /** * Matches any class which is in the named package. */ public void addPackage(String packageName) { packages.add(packageName); } /** * Matches any class which is in the named package or any subpackage of it. */ public void addPackageTree(String packageName) { packages.add(packageName); } /** * Matches the class with exactly this name, plus any of its inner classes. */ public void addClass(String classname) { classes.add(classname); } /** * Return true if this class is one selected by the criteria stored * in this object. */ public boolean isSelected(JavaClass clazz) { if (isAnonymousInnerClass(clazz)) { return false; } boolean matches = matchesCriteria(clazz); if (mode == MODE_IF) { return matches; } else // mode == MODE_UNLESS { return !matches; } } /** * Return true if this class is an anonymous inner class. * Not even developers working on a package would be interested * in API changes in these classes... */ private boolean isAnonymousInnerClass(JavaClass clazz) { String name = clazz.getClassName(); int dollarPos = name.indexOf('$'); if (dollarPos == -1) { return false; } for (int i = dollarPos + 1; i < name.length(); ++i) { if (!Character.isDigit(name.charAt(i))) { return false; } } // ok, we have a class name which contains a dollar sign, and // every subsequent character is a digit. return true; } /** * Return true if this class matches one of the criteria stored * in this object. */ private boolean matchesCriteria(JavaClass clazz) { String packageName = clazz.getPackageName(); if (packages.contains(packageName)) { return true; } for (Iterator i = packageTrees.iterator(); i.hasNext();) { String entry = (String) i.next(); if (packageName.startsWith(entry)) { if (packageName.length() == entry.length()) { // they are exactly equal return true; } if (packageName.charAt(entry.length()) == '.') { return true; } // else packagename is like "com.acmegadgets" and entryname // is like "com.acme", which is not a match, so keep looking. } } String className = clazz.getClassName(); for (Iterator i = classes.iterator(); i.hasNext();) { String entry = (String) i.next(); if (className.startsWith(entry)) { if (className.length() == entry.length()) { // they are exactly equal return true; } if (className.charAt(entry.length()) == '$') { // this is an inner class of the named class return true; } } } return false; } } --- NEW FILE --- ////////////////////////////////////////////////////////////////////////////// // Clirr: compares two versions of a java library for binary compatibility // Copyright (C) 2003 - 2004 Lars Kühne // // 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 net.sf.clirr.core; import net.sf.clirr.core.ApiDifference; /** * Listener for API differences. * * @author lkuehne */ public interface DiffListener { /** * Called when the listener should start listening. * This gives implementations a chance to write some header info. */ void start(); /** * Called when an API difference has been detected. * @param difference the difference that has been detected. */ void reportDiff(ApiDifference difference); /** * Called when the listener should stop listening. * This gives implementations a chance to write footer info, * close files, etc. */ void stop(); } --- NEW FILE --- ////////////////////////////////////////////////////////////////////////////// // Clirr: compares two versions of a java library for binary compatibility // Copyright (C) 2003 - 2004 Lars Kühne // // 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 net.sf.clirr.core; import net.sf.clirr.core.ApiDifference; import net.sf.clirr.core.DiffListener; /** * Provides empty implementations for all methods * in the DiffListener interface. * * @author lkuehne */ public class DiffListenerAdapter implements DiffListener { /** * Does nothing * @see net.sf.clirr.core.DiffListener#reportDiff(net.sf.clirr.core.ApiDifference) */ public void reportDiff(ApiDifference difference) { } /** * Does nothing * @see net.sf.clirr.core.DiffListener#start() */ public void start() { } /** * Does nothing * @see net.sf.clirr.core.DiffListener#stop() */ public void stop() { } } --- NEW FILE --- ////////////////////////////////////////////////////////////////////////////// // Clirr: compares two versions of a java library for binary compatibility // Copyright (C) 2003 - 2004 Lars Kühne // // 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 net.sf.clirr.core; import java.util.ResourceBundle; import java.util.Locale; import java.util.Enumeration; /** * ResourceBundle that implements the default locale by delegating to the english bundle. * This solves the bug described in * https://sourceforge.net/tracker/index.php?func=detail&aid=594469&group_id=29721&atid=397078 * without having to duplicate any resource bundles, leading to a simpler build and a smaller jar. * * @author lkuehne */ public class EventMessages extends ResourceBundle { /** * The base name of the resource bundle from which message descriptions * are read. */ public static final String RESOURCE_NAME = EventMessages.class.getName(); private ResourceBundle delegate = null; /** * Control variable used in synchronized blocks that delegate to the {@link #delegate} bundle. * The delegate bundle has "this" as it's parent. * To prevent infinite loops in the lookup process when searching for * non-existent keys we set isUsingDelegate to true to break out of the loop. */ private boolean isUsingDelegate = false; /** * Constructor. * @deprecated Typical user code never calls this directly but uses * {@link java.util.ResourceBundle#getBundle(java.lang.String)} or one of it's variants instead. */ public EventMessages() { } private ResourceBundle getDelegate() { if (delegate == null) { delegate = ResourceBundle.getBundle(RESOURCE_NAME, Locale.ENGLISH); } return delegate; } /** @see java.util.ResourceBundle#handleGetObject */ protected final synchronized Object handleGetObject(String key) { try { if (isUsingDelegate) { // the underlying bundle is delegating the call back to us // this means that the key is unknown, so we return null return null; } else { isUsingDelegate = true; return getDelegate().getObject(key); } } finally { isUsingDelegate = false; } } /** @see java.util.ResourceBundle#getKeys */ public final synchronized Enumeration getKeys() { try { if (isUsingDelegate) { // the underlying bundle is delegating the call back to us // this means that the key is unknown, so we return null return null; } else { isUsingDelegate = true; return getDelegate().getKeys(); } } finally { isUsingDelegate = false; } } /** @see java.util.ResourceBundle#getLocale */ public final Locale getLocale() { return getDelegate().getLocale(); } } --- NEW FILE --- ////////////////////////////////////////////////////////////////////////////// // Clirr: compares two versions of a java library for binary compatibility // Copyright (C) 2003 - 2004 Lars Kühne // // 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 net.sf.clirr.core; import net.sf.clirr.core.DiffListenerAdapter; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.OutputStream; import java.io.PrintStream; /** * Abstract DiffListener that writes output to some textual * output stream. That stream can either be System.out or a textfile. * * @author lkuehne */ abstract class FileDiffListener extends DiffListenerAdapter { private PrintStream outputStream; /** * Initializes the outputstream. * @param outFile the filename (System.out is used if null is provided here) * @throws java.io.FileNotFoundException if there are problems with */ FileDiffListener(String outFile) throws FileNotFoundException { if (outFile == null) { outputStream = System.out; } else { final OutputStream out = new FileOutputStream(outFile); outputStream = new PrintStream(out); } } /** * Returns the output stream so subclasses can write data. * @return the output stream */ protected final PrintStream getOutputStream() { return outputStream; } /** * Writes a footer and closes the * output stream if necessary. * * @see #writeFooter() */ public final void stop() { writeFooter(); if (outputStream != System.out) { outputStream.close(); } super.stop(); } /** * A hook to write footer info to the output stream. * This implementation does nothing, subclasses can override * this method if necessary. * * @see #stop() */ protected void writeFooter() { } } --- NEW FILE --- ////////////////////////////////////////////////////////////////////////////// // Clirr: compares two versions of a java library for binary compatibility // Copyright (C) 2003 - 2004 Lars Kühne // // 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 net.sf.clirr.core; /** * Class which manages API Difference messages, including expanding message * codes into strings and descriptions. */ public final class Message { private int id; /** * This constructor is equivalent to new Message(id, true). */ public Message(int id) { this(id, true); } /** * Create an instance of this object with the specified message id * * @param id is an integer which is used to look up the appropriate * text string for this message from a resource file. The id of a * message should be unique. * * @param register determines whether the new Message object should be * registered with the central MessageManager object. This is normally * desirable, as this allows the unit tests associated with clirr to * verify that message ids are unique and that translations exist for * all registered messages. However false can be useful in some * circumstances, eg when creating Message objects for the purposes * of unit tests. */ public Message(int id, boolean register) { this.id = id; if (register) { MessageManager.getInstance().addMessage(this); } } public int getId() { return id; } } --- NEW FILE --- ////////////////////////////////////////////////////////////////////////////// // Clirr: compares two versions of a java library for binary compatibility // Copyright (C) 2003 - 2004 Lars Kühne // // 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 net.sf.clirr.core; import net.sf.clirr.core.Message; import java.util.Iterator; import java.util.ArrayList; import java.util.Comparator; import java.util.Collection; /** * Class which manages API Difference messages, including expanding message * codes into strings and descriptions. */ public final class MessageManager { private static MessageManager instance; private ArrayList messages = new ArrayList(); /** * Utility class to sort messages by their numeric ids. */ private static class MessageComparator implements Comparator { public int compare(Object o1, Object o2) { Message m1 = (Message) o1; Message m2 = (Message) o2; return m1.getId() - m2.getId(); } } /** * This is a singleton class; to get an instance of this class, use * the getInstance method. */ private MessageManager() { } /** * Return the singleton instance of this class. */ public static MessageManager getInstance() { if (instance == null) { instance = new MessageManager(); } return instance; } /** * Add a message to the list of known messages. */ public void addMessage(Message msg) { messages.add(msg); } /** * Verify that the list of known messages contains no two objects * with the same numeric message id. This method is expected to be * called from the unit tests, so that if a developer adds a new * message and accidentally uses the message id of an existing * message object, then this will be reported as an error. * <p> * @throws java.lang.IllegalArgumentException if any duplicate id is found. */ public void checkUnique() { java.util.Collections.sort(messages, new MessageComparator()); int lastId = -1; for (Iterator i = messages.iterator(); i.hasNext();) { // check for any duplicates Message m = (Message) i.next(); int currId = m.getId(); if (currId <= lastId) { throw new IllegalArgumentException("Message id [" + currId + "] is not unique."); } } } /** * Return the complete set of registered messages. */ public Collection getMessages() { return messages; } } --- NEW FILE --- ////////////////////////////////////////////////////////////////////////////// // Clirr: compares two versions of a java library for binary compatibility // Copyright (C) 2003 - 2004 Lars Kühne // // 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 net.sf.clirr.core; import net.sf.clirr.core.EventMessages; import net.sf.clirr.core.Message; import java.util.Locale; import java.util.Iterator; import java.util.Collection; import java.util.ResourceBundle; /** * Class which is capable of translating a Message object into a localised * string. */ public final class MessageTranslator { /** * The default base name of the resource bundle from which message * descriptions are read. */ public static final String DFLT_RESOURCE_NAME = EventMessages.class.getName(); private Locale locale = Locale.getDefault(); private String resourceName = DFLT_RESOURCE_NAME; private ResourceBundle messageText; /** * This is a singleton class; to get an instance of this class, use * the getInstance method. */ public MessageTranslator() { } /** * Define the local language etc. Future calls to the getDesc method * will attempt to use a properties file which is appropriate to that * locale to look the message descriptions up in. * <p> * @param locale may be a valid Locale object, or null to indicate * that the default locale is to be used. */ public void setLocale(Locale locale) { if (locale == null) { locale = Locale.getDefault(); } this.locale = locale; this.messageText = null; } /** * Define the base name of the properties file that message * translations are to be read from. */ public void setResourceName(String resourceName) { this.resourceName = resourceName; this.messageText = null; } /** * Verify that the resource bundle for the currently set locale has * a translation string available for every message object in the provided * collection. This method is expected to be called from the unit tests, * so that if a developer adds a new message the unit tests will fail until * translations are also available for that new message. * <p> * @throws java.util.MissingResourceException if there is a registered * message for which no description is present in the current locale's * resources. */ public void checkComplete(Collection messages) { for (Iterator i = messages.iterator(); i.hasNext();) { Message m = (Message) i.next(); getDesc(m); } } /** * Given a Message object (containing a unique message id), look up * that id in the appropriate resource bundle (properties file) for * the set locale and return the text string associated with that * message id. * <p> * Message ids in the properties file should be prefixed with an 'm', * eg "m1000", "m5003". * <p> * @throws java.util.MissingResourceException if there is no entry in the * message translation resource bundle for the specified message. */ public String getDesc(Message msg) { // load resource bundle if (locale == null) { locale = Locale.getDefault(); } if (messageText == null) { messageText = ResourceBundle.getBundle(resourceName, locale); } return messageText.getString("m" + msg.getId()); } } --- NEW FILE --- ////////////////////////////////////////////////////////////////////////////// // Clirr: compares two versions of a java library for binary compatibility // Copyright (C) 2003 - 2004 Lars Kühne // // 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 net.sf.clirr.core; import net.sf.clirr.core.ApiDifference; import net.sf.clirr.core.FileDiffListener; import net.sf.clirr.core.Message; import net.sf.clirr.core.MessageTranslator; import java.io.IOException; import java.io.PrintStream; public final class PlainDiffListener extends FileDiffListener { private MessageTranslator translator = new MessageTranslator(); public PlainDiffListener(String outFile) throws IOException { super(outFile); } public void reportDiff(ApiDifference difference) { PrintStream out = getOutputStream(); out.print(difference.getMaximumSeverity().toString()); Message m = difference.getMessage(); if (m != null) { out.print(": "); out.print(m.getId()); } out.print(": "); out.print(difference.getAffectedClass()); out.print(": "); out.println(difference.getReport(translator)); } } --- NEW FILE --- ////////////////////////////////////////////////////////////////////////////// // Clirr: compares two versions of a java library for binary compatibility // Copyright (C) 2003 - 2004 Lars Kühne // // 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 net.sf.clirr.core; import org.apache.bcel.classfile.JavaClass; import org.apache.bcel.classfile.AccessFlags; import org.apache.bcel.classfile.Attribute; import org.apache.bcel.classfile.InnerClasses; import org.apache.bcel.classfile.InnerClass; import org.apache.bcel.classfile.ConstantPool; import org.apache.bcel.classfile.Constant; import org.apache.bcel.classfile.ConstantUtf8; import org.apache.bcel.util.Repository; import org.apache.bcel.Constants; /** * Selects zero or more java scope values (public, protected, package, * private). An instance of this class is used when comparing two versions * of an application to indicate what items are of interest. When the target * audience is "normal" users of the applications, only changes to items * which have public or protected scope are relevant. When the audience is * developers of the applications, then package-scope and private-scope * changes are also of interest. * * @author Simon Kitching */ public final class ScopeSelector { /** * Represents an "accessability" level for a java class, field or method. * <p> * Change of access rights from lower to higher visibility rating is a * binary-compatible change. Change of access rights from higher to * lower is a binary-incompatible change. * <p> * Public > Protected > Package > Private */ public static final class Scope { private int vis; private String desc; private String decl; private Scope(int vis, String desc, String decl) { this.vis = vis; this.desc = desc; this.decl = decl; } public boolean isMoreVisibleThan(Scope v) { return this.vis > v.vis; } public boolean isLessVisibleThan(Scope v) { return this.vis < v.vis; } public String getDesc() { return desc; } public String getDecl() { return decl; } } /** Object representing private scoped objects. */ public static final Scope SCOPE_PRIVATE = new Scope(0, "private", "private"); /** Object representing package scoped objects. */ public static final Scope SCOPE_PACKAGE = new Scope(1, "package", ""); /** Object representing protected scoped objects. */ public static final Scope SCOPE_PROTECTED = new Scope(2, "protected", "protected"); /** Object representing public scoped objects. */ public static final Scope SCOPE_PUBLIC = new Scope(3, "public", "public"); private Scope scope = SCOPE_PROTECTED; /** * Construct an instance which selects public and protected objects and * ignores package and private objects. The selectXXX methods can later * be used to adjust this default behaviour. */ public ScopeSelector() { } /** * Construct an instance which selects public and protected objects and * ignores package and private objects. The selectXXX methods can later * be used to adjust this default behaviour. */ public ScopeSelector(Scope scope) { this.scope = scope; } /** Specify which scope objects are of interest. */ public void setScope(Scope scope) { this.scope = scope; } /** * Get the scope that this object is configured with. */ public Scope getScope() { return scope; } /** * Return a string which indicates what scopes this object will consider * to be selected (ie relevant). */ public String toString() { return scope.getDesc(); } /** * Given a BCEL object, return true if this object's scope is one of the * values this object is configured to match. * <p> * Note that BCEL classes Field and Method inherit from the AccessFlags * base class and so are valid parameters to this method. * <p> * Note that despite JavaClass objects extending AccessFlags, the * methods which determine the accessability of a JavaClass fail * miserably (bad bcel design) for nested classes. Therefore this * method <i>must not</i> be passed a JavaClass object as a parameter. * If this is done, a RuntimeException will be thrown to indicate a * programmer error. * * @param object is the object whose scope is to be checked. * @return true if the object is selected. */ public boolean isSelected(AccessFlags object) { return !getScope(object).isLessVisibleThan(scope); } /** * Return true if objects of the specified scope, or more visible, * are selected by this selector. * * @param scope is the scope being checked * @return true if objects of the specified scope are selected. */ public boolean isSelected(Scope scope) { return !scope.isLessVisibleThan(this.scope); } /** * Given a BCEL object, return the string which would be used in java * source code to declare that object's scope. <p> * <p> * Note that BCEL classes Field and Method inherit from the AccessFlags * base class and so are valid parameters to this method. * <p> * Note that despite JavaClass objects extending AccessFlags, the * methods which determine the accessability of a JavaClass fail * miserably (bad bcel design) for nested classes. Therefore this * method <i>must not</i> be passed a JavaClass object as a parameter. * If this is done, a RuntimeException will be thrown to indicate a * programmer error. */ public static String getScopeDecl(AccessFlags object) { return getScope(object).getDecl(); } /** * Given an integer representing an object's access flags, return the * string which would be used in java source code to declare that object's * scope. * <p> * Note that this method gives the wrong results for JavaClass objects * which are nested classes. Use getClassScope(jclass).getDecl() instead. */ public static String getScopeDecl(int accessFlags) { return getScope(accessFlags).getDecl(); } /** * Given a BCEL object, return a string indicating whether the object is * public/protected/private/package scope. This is similar to * getScopeName, except for package-scope objects where this method * returns the string "package". * <p> * Note that BCEL classes Field and Method inherit from the AccessFlags * base class and so are valid parameters to this method. * <p> * Note that despite JavaClass objects extending AccessFlags, the * methods which determine the accessability of a JavaClass fail * miserably (bad bcel design) for nested classes. Therefore this * method <i>must not</i> be passed a JavaClass object as a parameter. * If this is done, a RuntimeException will be thrown to indicate a * programmer error. */ public static String getScopeDesc(AccessFlags object) { return getScope(object).getDesc(); } /** * Given an integer representing the object's access flags, return a string * indicating whether the object is public/protected/private/package scope. * <p> * This is similar to getScopeName, except for package-scope objects where * this method returns the string "package". * <p> * Note that this method gives the wrong results for JavaClass objects * which are nested classes. Use getClassScope(jclass).getDesc() instead. */ public static String getScopeDesc(int accessFlags) { return getScope(accessFlags).getDesc(); } /** * Get a Scope object representing the accessibility of the specified * object. * <p> * Note that BCEL classes Field and Method inherit from the AccessFlags * base class and so are valid parameters to this method. * <p> * Note that despite JavaClass objects extending AccessFlags, t... [truncated message content] |