/*
* ServicesListerAntTask.java
*
* Created on September 17, 2006, 4:08 AM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
package com.kitfox.util;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import org.apache.tools.ant.taskdefs.DefBase;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.Resource;
import org.apache.tools.ant.types.resources.FileResource;
/**
* This ant task examines a directory hierarchy containing .class files and
* constructs lists of all classes that extend a given base class. This
* is meant to facilitate the generation of META-INF/services lists
* for jar files.
*
*
* <servicesLister>
* <examinepath>
* <pathelement location="${path-to-find-classfiles-to-be-analyzed}">
* </examinepath>
* <classpath>
* <pathelement location="${classpath-needed-to-construct-Class-objects-of-classes-in-examine-path}">
* </classpath>
* <service className="name.of.class.to.build.list.For1" toDir="location/to/save/generated.list1">
* <service className="name.of.class.to.build.list.For2" toDir="location/to/save/generated.list2">
* </servicesLister>
*
*
* @author kitfox
*/
public class ServicesListerAntTask extends DefBase
{
/**
* Contains info for a single service to generate a list for. There may be
* zero or more services tags embedded in the servicesLister task.
*
* If the toFile parameter is set to the name of a directory, the output
* file name is computed at new File(toFile, className)
*/
public class Service
{
private String className;
private File toFile;
private File toDir;
private Class targetClass;
ArrayList targets = new ArrayList();
public String getClassName()
{
return className;
}
public void setClassName(String className)
{
this.className = className;
}
public File getToFile()
{
return toFile;
}
public void setToFile(File toFile)
{
this.toFile = toFile;
}
public File getToDir()
{
return toDir;
}
public void setToDir(File toDir)
{
if (toDir.exists() && !toDir.isDirectory())
{
throw new BuildException("toDir argument must be a directory: " + toDir);
}
this.toDir = toDir;
}
public Class getTargetClass()
{
return targetClass;
}
public void setTargetClass(Class targetClass)
{
this.targetClass = targetClass;
}
private void writeTarget(String className)
{
targets.add(className);
}
private void writeToFile()
{
try
{
if (toFile != null)
{
if (toFile.exists() && toFile.isDirectory())
{
toFile = new File(toFile, className);
}
}
else
{
if (toDir == null)
{
throw new BuildException("Must specify either a toFile or a toDir parameter");
}
toFile = new File(toDir, className);
}
//Make sure parent directory exists
File parent = toFile.getParentFile();
if (!parent.exists())
{
parent.mkdirs();
}
FileWriter fout = new FileWriter(toFile);
PrintWriter pw = new PrintWriter(new BufferedWriter(fout));
pw.println("#Automatically generated services list");
pw.println("#Mark McKay - " + new Date());
for (String target: targets)
{
pw.println(target);
}
pw.close();
}
catch (IOException e)
{
throw new BuildException(e);
}
}
}
Path examinePath;
// ResourceCollection targetRes;
ArrayList services = new ArrayList();
/**
* The examine path should be a subset of the classpath and include all the
* classes that should be examined for possible inclusion in the services list.
*/
public Path createExaminePath() throws BuildException
{
if (examinePath == null)
{
examinePath = new Path(getProject());
return examinePath;
}
throw new BuildException("Only one examine path can be set");
}
/*
public void addFileSet(FileSet fileSet)
{
if (this.targetRes != null)
{
throw new BuildException("Resources to be examined set multiple times");
}
this.targetRes = fileSet;
}
*/
public Service createService()
{
Service service = new Service();
services.add(service);
return service;
}
/**
* Creates a new instance of ServicesListerAntTask
*/
public ServicesListerAntTask()
{
}
static final FilenameFilter FFILTER_CLASS = new FilenameFilter()
{
public boolean accept(File dir, String name)
{
return name.endsWith(".class");
}
};
public void scanPath(ClassLoader loader, File dir, File pathRoot)
{
String pathAbs = pathRoot.getAbsolutePath();
//System.err.println("***Abs path: " + pathAbs);
// File[] fileList = dir.listFiles(FFILTER_CLASS);
for (File file: dir.listFiles())
{
if (file.getName().endsWith(".class"))
{
String fileAbs = file.getAbsolutePath();
//System.err.println("***Abs file: " + fileAbs);
String relName = fileAbs.substring(pathAbs.length() + 1, fileAbs.length() - ".class".length());
relName = relName.replaceAll("[/\\\\]", ".");
//System.err.println("***Rel name: " + relName);
Class cls;
try
{
cls = loader.loadClass(relName);
}
catch (ClassNotFoundException ex)
{
throw new BuildException(ex);
}
if (cls == null)
{
log("Could not examine class " + file);
continue;
}
else
{
//Ignore classes that cannot be instantiated
int mod = cls.getModifiers();
if (!Modifier.isPublic(mod) || Modifier.isAbstract(mod) || Modifier.isInterface(mod))
{
continue;
}
}
for (Service service: services)
{
if (service.getTargetClass().isAssignableFrom(cls))
{
service.writeTarget(relName);
}
}
}
else if (file.isDirectory())
{
scanPath(loader, file, pathRoot);
}
}
}
public void execute() throws BuildException
{
if (examinePath == null)
{
throw new BuildException("Examine path not set - please specify path to files to check for services");
}
ClassLoader loader = createLoader();
for (Service service: services)
{
String name = service.getClassName();
Class cls;
try
{
cls = loader.loadClass(name);
}
catch (ClassNotFoundException ex)
{
throw new BuildException(String.format("Could not find %s in classpath", name), ex);
}
service.setTargetClass(cls);
}
for (Iterator it = examinePath.iterator(); it.hasNext();)
{
Resource res = (Resource)it.next();
if (!(res instanceof FileResource)) continue;
//System.err.println("*****Resource " + res.getName() + ", " + res.getClass().getName() + ", " + res.toString());
File root = ((FileResource)res).getFile();
//System.err.println("*****File " + Name() + " " + res);
//Find all .class files under the path
scanPath(loader, root, root);
}
//Write all to disk
for (Service service: services)
{
service.writeToFile();
}
}
}