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:
020: package net.sf.clirr.ant;
021:
022: import java.io.File;
023: import java.io.IOException;
024: import java.util.Iterator;
025: import java.util.LinkedList;
026: import java.util.List;
027:
028: import net.sf.clirr.core.Checker;
029: import net.sf.clirr.core.CheckerException;
030: import net.sf.clirr.core.ClassFilter;
031: import net.sf.clirr.core.ClassSelector;
032: import net.sf.clirr.core.PlainDiffListener;
033: import net.sf.clirr.core.XmlDiffListener;
034: import net.sf.clirr.core.internal.ClassLoaderUtil;
035: import net.sf.clirr.core.internal.bcel.BcelTypeArrayBuilder;
036: import net.sf.clirr.core.spi.JavaType;
037:
038: import org.apache.bcel.classfile.JavaClass;
039: import org.apache.tools.ant.BuildException;
040: import org.apache.tools.ant.DirectoryScanner;
041: import org.apache.tools.ant.Project;
042: import org.apache.tools.ant.Task;
043: import org.apache.tools.ant.types.FileSet;
044: import org.apache.tools.ant.types.Path;
045: import org.apache.tools.ant.types.PatternSet;
046:
047: /**
048: * Implements the Clirr ant task.
049: * @author lkuehne
050: */
051: public final class AntTask extends Task {
052: private static final String FORMATTER_TYPE_PLAIN = "plain";
053: private static final String FORMATTER_TYPE_XML = "xml";
054:
055: /**
056: * Output formater.
057: */
058: public static final class Formatter {
059: private String type = null;
060: private String outFile = null;
061:
062: public String getOutFile() {
063: return outFile;
064: }
065:
066: public void setOutFile(String outFile) {
067: this .outFile = outFile;
068: }
069:
070: public String getType() {
071: return type;
072: }
073:
074: public void setType(String type) {
075: String lowerCase = type.toLowerCase();
076: if (!lowerCase.equals(FORMATTER_TYPE_XML)
077: && !lowerCase.equals(FORMATTER_TYPE_PLAIN)) {
078: throw new BuildException(
079: "Illegal formatter type, only plain and xml are supported");
080: }
081:
082: this .type = type;
083: }
084: }
085:
086: /**
087: * Class Filter that returns the logical "and" of two underlying class filters.
088: */
089: private static class CompoundClassFilter implements ClassFilter {
090: private final ClassFilter patternSetFilter;
091: private final ClassFilter scopeSelector;
092:
093: public CompoundClassFilter(ClassFilter patternSetFilter,
094: ClassFilter scopeSelector) {
095: this .patternSetFilter = patternSetFilter;
096: this .scopeSelector = scopeSelector;
097: }
098:
099: public boolean isSelected(JavaClass clazz) {
100: return patternSetFilter.isSelected(clazz)
101: && scopeSelector.isSelected(clazz);
102: }
103: }
104:
105: private FileSet origFiles = null;
106: private FileSet newFiles = null;
107: private Path newClassPath = null;
108: private Path origClassPath = null;
109:
110: private boolean failOnBinError = true;
111: private boolean failOnBinWarning = false;
112: private boolean failOnSrcError = true;
113: private boolean failOnSrcWarning = false;
114: private List formatters = new LinkedList();
115: private List patternSets = new LinkedList();
116:
117: public Path createNewClassPath() {
118: if (newClassPath == null) {
119: newClassPath = new Path(getProject());
120: }
121: return newClassPath.createPath();
122: }
123:
124: public void setNewClassPath(Path path) {
125: if (newClassPath == null) {
126: newClassPath = path;
127: } else {
128: newClassPath.append(path);
129: }
130: }
131:
132: public Path createOrigClassPath() {
133: if (origClassPath == null) {
134: origClassPath = new Path(getProject());
135: }
136: return origClassPath.createPath();
137: }
138:
139: public void setOrigClassPath(Path path) {
140: if (origClassPath == null) {
141: origClassPath = path;
142: } else {
143: origClassPath.append(path);
144: }
145: }
146:
147: public void addOrigFiles(FileSet origFiles) {
148: if (this .origFiles != null) {
149: throw new BuildException();
150: }
151: this .origFiles = origFiles;
152: }
153:
154: public void addNewFiles(FileSet newFiles) {
155: if (this .newFiles != null) {
156: throw new BuildException();
157: }
158: this .newFiles = newFiles;
159: }
160:
161: public void setFailOnBinError(boolean failOnBinError) {
162: this .failOnBinError = failOnBinError;
163: }
164:
165: public void setFailOnBinWarning(boolean failOnBinWarning) {
166: this .failOnBinWarning = failOnBinWarning;
167: }
168:
169: public void setFailOnSrcError(boolean failOnSrcError) {
170: this .failOnSrcError = failOnSrcError;
171: }
172:
173: public void setFailOnSrcWarning(boolean failOnSrcWarning) {
174: this .failOnSrcWarning = failOnSrcWarning;
175: }
176:
177: public void addFormatter(Formatter formatter) {
178: formatters.add(formatter);
179: }
180:
181: public void addApiClasses(PatternSet set) {
182: patternSets.add(set);
183: }
184:
185: public void execute() {
186: log("Running Clirr, built from tag $Name: RELEASE_CLIRR_0_6 $",
187: Project.MSG_VERBOSE);
188:
189: if (origFiles == null || newFiles == null) {
190: throw new BuildException(
191: "Missing nested filesets origFiles and newFiles.",
192: getLocation());
193: }
194:
195: if (newClassPath == null) {
196: newClassPath = new Path(getProject());
197: }
198:
199: if (origClassPath == null) {
200: origClassPath = new Path(getProject());
201: }
202:
203: final File[] origJars = scanFileSet(origFiles);
204: final File[] newJars = scanFileSet(newFiles);
205:
206: if (origJars.length == 0) {
207: throw new BuildException(
208: "No files in nested fileset origFiles - nothing to check!"
209: + " Please check your fileset specification.");
210: }
211:
212: if (newJars.length == 0) {
213: throw new BuildException(
214: "No files in nested fileset newFiles - nothing to check!"
215: + " Please check your fileset specification.");
216: }
217:
218: final ClassLoader origThirdPartyLoader = createClasspathLoader(origClassPath);
219: final ClassLoader newThirdPartyLoader = createClasspathLoader(newClassPath);
220:
221: final Checker checker = new Checker();
222: final ChangeCounter counter = new ChangeCounter();
223:
224: boolean formattersWriteToStdOut = false;
225:
226: for (Iterator it = formatters.iterator(); it.hasNext();) {
227: Formatter formatter = (Formatter) it.next();
228: final String type = formatter.getType();
229: final String outFile = formatter.getOutFile();
230:
231: formattersWriteToStdOut = formattersWriteToStdOut
232: || outFile == null;
233:
234: try {
235: if (FORMATTER_TYPE_PLAIN.equals(type)) {
236: checker.addDiffListener(new PlainDiffListener(
237: outFile));
238: } else if (FORMATTER_TYPE_XML.equals(type)) {
239: checker
240: .addDiffListener(new XmlDiffListener(
241: outFile));
242: }
243: } catch (IOException ex) {
244: log("unable to initialize formatter: "
245: + ex.getMessage(), Project.MSG_WARN);
246: }
247: }
248:
249: if (!formattersWriteToStdOut) {
250: checker.addDiffListener(new AntLogger(this ));
251: }
252:
253: checker.addDiffListener(counter);
254: try {
255: ClassFilter classSelector = buildClassFilter();
256: final JavaType[] origClasses = BcelTypeArrayBuilder
257: .createClassSet(origJars, origThirdPartyLoader,
258: classSelector);
259:
260: final JavaType[] newClasses = BcelTypeArrayBuilder
261: .createClassSet(newJars, newThirdPartyLoader,
262: classSelector);
263:
264: checker.reportDiffs(origClasses, newClasses);
265: } catch (CheckerException ex) {
266: throw new BuildException(ex.getMessage());
267: }
268:
269: if ((counter.getBinWarnings() > 0 && failOnBinWarning)
270: || (counter.getBinErrors() > 0 && failOnBinError)) {
271: throw new BuildException(
272: "detected binary incompatible API changes");
273: }
274:
275: if ((counter.getSrcWarnings() > 0 && failOnSrcWarning)
276: || (counter.getSrcErrors() > 0 && failOnSrcError)) {
277: throw new BuildException(
278: "detected source incompatible API changes");
279: }
280: }
281:
282: private ClassFilter buildClassFilter() {
283: final PatternSetFilter patternSetFilter = new PatternSetFilter(
284: getProject(), patternSets);
285: final ClassFilter scopeSelector = new ClassSelector(
286: ClassSelector.MODE_UNLESS);
287: return new CompoundClassFilter(patternSetFilter, scopeSelector);
288: }
289:
290: private ClassLoader createClasspathLoader(Path classpath) {
291: final String[] cpEntries = classpath.list();
292: return ClassLoaderUtil.createClassLoader(cpEntries);
293: }
294:
295: private File[] scanFileSet(FileSet fs) {
296: Project prj = getProject();
297: DirectoryScanner scanner = fs.getDirectoryScanner(prj);
298: scanner.scan();
299: File basedir = scanner.getBasedir();
300: String[] fileNames = scanner.getIncludedFiles();
301: File[] ret = new File[fileNames.length];
302: for (int i = 0; i < fileNames.length; i++) {
303: String fileName = fileNames[i];
304: ret[i] = new File(basedir, fileName);
305: }
306: return ret;
307: }
308:
309: }
|