001: //////////////////////////////////////////////////////////////////////////////
002: // Clirr: compares two versions of a java library for binary compatibility
003: // Copyright (C) 2003 - 2005 Lars Kühne
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: package net.sf.clirr.core;
020:
021: import org.apache.bcel.classfile.JavaClass;
022:
023: import java.util.ArrayList;
024: import java.util.Iterator;
025:
026: /**
027: * Given a JavaClass object, determines whether or not it is "selected",
028: * based on its class or package. This is used to select subsets of the
029: * classes available in a classpath for comparison or testing purposes.
030: *
031: * @author Simon Kitching
032: */
033: public final class ClassSelector implements ClassFilter {
034: /** Class for implementing an enumeration. */
035: public static final class Mode {
036: private Mode() {
037: }
038: }
039:
040: /** positive selection. */
041: public static final Mode MODE_IF = new Mode();
042: /** negative selection. */
043: public static final Mode MODE_UNLESS = new Mode();
044:
045: private Mode mode;
046:
047: private ArrayList packages = new ArrayList();
048: private ArrayList packageTrees = new ArrayList();
049: private ArrayList classes = new ArrayList();
050:
051: /**
052: * Create a selector.
053: * <p>
054: * When mode is MODE_IF then a class is "selected" if-and-only-if
055: * the class matches one of the criteria defined via the addXXX methods.
056: * In other words, the criteria specify which classes are included
057: * (selected) in the resulting class set.
058: * <p>
059: * When mode is MODE_UNLESS, then a class is "selected" unless the class
060: * matches one of the criteria defined via the addXXX methods. In other
061: * words, the criteria specify which classes are excluded from the
062: * resulting class set.
063: */
064: public ClassSelector(Mode mode) {
065: this .mode = mode;
066: }
067:
068: /**
069: * Matches any class which is in the named package.
070: */
071: public void addPackage(String packageName) {
072: packages.add(packageName);
073: }
074:
075: /**
076: * Matches any class which is in the named package or any subpackage of it.
077: */
078: public void addPackageTree(String packageName) {
079: packages.add(packageName);
080: }
081:
082: /**
083: * Matches the class with exactly this name, plus any of its inner classes.
084: */
085: public void addClass(String classname) {
086: classes.add(classname);
087: }
088:
089: /**
090: * Return true if this class is one selected by the criteria stored
091: * in this object.
092: */
093: public boolean isSelected(JavaClass clazz) {
094: if (isAnonymousInnerClass(clazz)) {
095: return false;
096: }
097:
098: boolean matches = matchesCriteria(clazz);
099: if (mode == MODE_IF) {
100: return matches;
101: } else // mode == MODE_UNLESS
102: {
103: return !matches;
104: }
105: }
106:
107: /**
108: * Return true if this class is an anonymous inner class.
109: * Not even developers working on a package would be interested
110: * in API changes in these classes...
111: */
112: private boolean isAnonymousInnerClass(JavaClass clazz) {
113: String name = clazz.getClassName();
114: int dollarPos = name.indexOf('$');
115: if (dollarPos == -1) {
116: return false;
117: }
118:
119: for (int i = dollarPos + 1; i < name.length(); ++i) {
120: if (!Character.isDigit(name.charAt(i))) {
121: return false;
122: }
123: }
124:
125: // ok, we have a class name which contains a dollar sign, and
126: // every subsequent character is a digit.
127: return true;
128: }
129:
130: /**
131: * Return true if this class matches one of the criteria stored
132: * in this object.
133: */
134: private boolean matchesCriteria(JavaClass clazz) {
135: String packageName = clazz.getPackageName();
136: if (packages.contains(packageName)) {
137: return true;
138: }
139:
140: for (Iterator i = packageTrees.iterator(); i.hasNext();) {
141: String entry = (String) i.next();
142: if (packageName.startsWith(entry)) {
143: if (packageName.length() == entry.length()) {
144: // they are exactly equal
145: return true;
146: }
147:
148: if (packageName.charAt(entry.length()) == '.') {
149: return true;
150: }
151:
152: // else packagename is like "com.acmegadgets" and entryname
153: // is like "com.acme", which is not a match, so keep looking.
154: }
155: }
156:
157: String className = clazz.getClassName();
158: for (Iterator i = classes.iterator(); i.hasNext();) {
159: String entry = (String) i.next();
160:
161: if (className.startsWith(entry)) {
162: if (className.length() == entry.length()) {
163: // they are exactly equal
164: return true;
165: }
166:
167: if (className.charAt(entry.length()) == '$') {
168: // this is an inner class of the named class
169: return true;
170: }
171: }
172: }
173:
174: return false;
175: }
176: }
|