1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package net.sf.clirr.ant;
21
22 import java.io.File;
23 import java.io.IOException;
24 import java.util.Iterator;
25 import java.util.LinkedList;
26 import java.util.List;
27
28 import net.sf.clirr.core.Checker;
29 import net.sf.clirr.core.CheckerException;
30 import net.sf.clirr.core.ClassFilter;
31 import net.sf.clirr.core.ClassSelector;
32 import net.sf.clirr.core.PlainDiffListener;
33 import net.sf.clirr.core.XmlDiffListener;
34 import net.sf.clirr.core.internal.ClassLoaderUtil;
35 import net.sf.clirr.core.internal.bcel.BcelTypeArrayBuilder;
36 import net.sf.clirr.core.spi.JavaType;
37
38 import org.apache.bcel.classfile.JavaClass;
39 import org.apache.tools.ant.BuildException;
40 import org.apache.tools.ant.DirectoryScanner;
41 import org.apache.tools.ant.Project;
42 import org.apache.tools.ant.Task;
43 import org.apache.tools.ant.types.FileSet;
44 import org.apache.tools.ant.types.Path;
45 import org.apache.tools.ant.types.PatternSet;
46
47
48 /***
49 * Implements the Clirr ant task.
50 * @author lkuehne
51 */
52 public final class AntTask extends Task
53 {
54 private static final String FORMATTER_TYPE_PLAIN = "plain";
55 private static final String FORMATTER_TYPE_XML = "xml";
56
57 /***
58 * Output formater.
59 */
60 public static final class Formatter
61 {
62 private String type = null;
63 private String outFile = null;
64
65 public String getOutFile()
66 {
67 return outFile;
68 }
69
70 public void setOutFile(String outFile)
71 {
72 this.outFile = outFile;
73 }
74
75 public String getType()
76 {
77 return type;
78 }
79
80 public void setType(String type)
81 {
82 String lowerCase = type.toLowerCase();
83 if (!lowerCase.equals(FORMATTER_TYPE_XML)
84 && !lowerCase.equals(FORMATTER_TYPE_PLAIN))
85 {
86 throw new BuildException(
87 "Illegal formatter type, only plain and xml are supported");
88 }
89
90 this.type = type;
91 }
92 }
93
94 /***
95 * Class Filter that returns the logical "and" of two underlying class filters.
96 */
97 private static class CompoundClassFilter implements ClassFilter
98 {
99 private final ClassFilter patternSetFilter;
100 private final ClassFilter scopeSelector;
101
102 public CompoundClassFilter(ClassFilter patternSetFilter, ClassFilter scopeSelector)
103 {
104 this.patternSetFilter = patternSetFilter;
105 this.scopeSelector = scopeSelector;
106 }
107
108 public boolean isSelected(JavaClass clazz)
109 {
110 return patternSetFilter.isSelected(clazz) && scopeSelector.isSelected(clazz);
111 }
112 }
113
114 private FileSet origFiles = null;
115 private FileSet newFiles = null;
116 private Path newClassPath = null;
117 private Path origClassPath = null;
118
119 private boolean failOnBinError = true;
120 private boolean failOnBinWarning = false;
121 private boolean failOnSrcError = true;
122 private boolean failOnSrcWarning = false;
123 private List formatters = new LinkedList();
124 private List patternSets = new LinkedList();
125
126
127 public Path createNewClassPath()
128 {
129 if (newClassPath == null)
130 {
131 newClassPath = new Path(getProject());
132 }
133 return newClassPath.createPath();
134 }
135
136 public void setNewClassPath(Path path)
137 {
138 if (newClassPath == null)
139 {
140 newClassPath = path;
141 }
142 else
143 {
144 newClassPath.append(path);
145 }
146 }
147
148 public Path createOrigClassPath()
149 {
150 if (origClassPath == null)
151 {
152 origClassPath = new Path(getProject());
153 }
154 return origClassPath.createPath();
155 }
156
157 public void setOrigClassPath(Path path)
158 {
159 if (origClassPath == null)
160 {
161 origClassPath = path;
162 }
163 else
164 {
165 origClassPath.append(path);
166 }
167 }
168
169 public void addOrigFiles(FileSet origFiles)
170 {
171 if (this.origFiles != null)
172 {
173 throw new BuildException();
174 }
175 this.origFiles = origFiles;
176 }
177
178 public void addNewFiles(FileSet newFiles)
179 {
180 if (this.newFiles != null)
181 {
182 throw new BuildException();
183 }
184 this.newFiles = newFiles;
185 }
186
187 public void setFailOnBinError(boolean failOnBinError)
188 {
189 this.failOnBinError = failOnBinError;
190 }
191
192 public void setFailOnBinWarning(boolean failOnBinWarning)
193 {
194 this.failOnBinWarning = failOnBinWarning;
195 }
196
197 public void setFailOnSrcError(boolean failOnSrcError)
198 {
199 this.failOnSrcError = failOnSrcError;
200 }
201
202 public void setFailOnSrcWarning(boolean failOnSrcWarning)
203 {
204 this.failOnSrcWarning = failOnSrcWarning;
205 }
206
207 public void addFormatter(Formatter formatter)
208 {
209 formatters.add(formatter);
210 }
211
212 public void addApiClasses(PatternSet set)
213 {
214 patternSets.add(set);
215 }
216
217 public void execute()
218 {
219 log("Running Clirr, built from tag $Name: RELEASE_CLIRR_0_6 $", Project.MSG_VERBOSE);
220
221 if (origFiles == null || newFiles == null)
222 {
223 throw new BuildException(
224 "Missing nested filesets origFiles and newFiles.", getLocation());
225 }
226
227 if (newClassPath == null)
228 {
229 newClassPath = new Path(getProject());
230 }
231
232 if (origClassPath == null)
233 {
234 origClassPath = new Path(getProject());
235 }
236
237 final File[] origJars = scanFileSet(origFiles);
238 final File[] newJars = scanFileSet(newFiles);
239
240 if (origJars.length == 0)
241 {
242 throw new BuildException(
243 "No files in nested fileset origFiles - nothing to check!"
244 + " Please check your fileset specification.");
245 }
246
247 if (newJars.length == 0)
248 {
249 throw new BuildException(
250 "No files in nested fileset newFiles - nothing to check!"
251 + " Please check your fileset specification.");
252 }
253
254 final ClassLoader origThirdPartyLoader = createClasspathLoader(origClassPath);
255 final ClassLoader newThirdPartyLoader = createClasspathLoader(newClassPath);
256
257 final Checker checker = new Checker();
258 final ChangeCounter counter = new ChangeCounter();
259
260 boolean formattersWriteToStdOut = false;
261
262 for (Iterator it = formatters.iterator(); it.hasNext();)
263 {
264 Formatter formatter = (Formatter) it.next();
265 final String type = formatter.getType();
266 final String outFile = formatter.getOutFile();
267
268 formattersWriteToStdOut = formattersWriteToStdOut || outFile == null;
269
270 try
271 {
272 if (FORMATTER_TYPE_PLAIN.equals(type))
273 {
274 checker.addDiffListener(new PlainDiffListener(outFile));
275 }
276 else if (FORMATTER_TYPE_XML.equals(type))
277 {
278 checker.addDiffListener(new XmlDiffListener(outFile));
279 }
280 }
281 catch (IOException ex)
282 {
283 log("unable to initialize formatter: " + ex.getMessage(),
284 Project.MSG_WARN);
285 }
286 }
287
288 if (!formattersWriteToStdOut)
289 {
290 checker.addDiffListener(new AntLogger(this));
291 }
292
293 checker.addDiffListener(counter);
294 try
295 {
296 ClassFilter classSelector = buildClassFilter();
297 final JavaType[] origClasses =
298 BcelTypeArrayBuilder.createClassSet(origJars, origThirdPartyLoader, classSelector);
299
300 final JavaType[] newClasses =
301 BcelTypeArrayBuilder.createClassSet(newJars, newThirdPartyLoader, classSelector);
302
303 checker.reportDiffs(origClasses, newClasses);
304 }
305 catch (CheckerException ex)
306 {
307 throw new BuildException(ex.getMessage());
308 }
309
310 if ((counter.getBinWarnings() > 0 && failOnBinWarning)
311 || (counter.getBinErrors() > 0 && failOnBinError))
312 {
313 throw new BuildException("detected binary incompatible API changes");
314 }
315
316 if ((counter.getSrcWarnings() > 0 && failOnSrcWarning)
317 || (counter.getSrcErrors() > 0 && failOnSrcError))
318 {
319 throw new BuildException("detected source incompatible API changes");
320 }
321 }
322
323 private ClassFilter buildClassFilter()
324 {
325 final PatternSetFilter patternSetFilter = new PatternSetFilter(getProject(), patternSets);
326 final ClassFilter scopeSelector = new ClassSelector(ClassSelector.MODE_UNLESS);
327 return new CompoundClassFilter(patternSetFilter, scopeSelector);
328 }
329
330
331 private ClassLoader createClasspathLoader(Path classpath)
332 {
333 final String[] cpEntries = classpath.list();
334 return ClassLoaderUtil.createClassLoader(cpEntries);
335 }
336
337 private File[] scanFileSet(FileSet fs)
338 {
339 Project prj = getProject();
340 DirectoryScanner scanner = fs.getDirectoryScanner(prj);
341 scanner.scan();
342 File basedir = scanner.getBasedir();
343 String[] fileNames = scanner.getIncludedFiles();
344 File[] ret = new File[fileNames.length];
345 for (int i = 0; i < fileNames.length; i++)
346 {
347 String fileName = fileNames[i];
348 ret[i] = new File(basedir, fileName);
349 }
350 return ret;
351 }
352
353 }