View Javadoc

1   //////////////////////////////////////////////////////////////////////////////
2   // Clirr: compares two versions of a java library for binary compatibility
3   // Copyright (C) 2003 - 2005  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.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 }