//Tested with BCEL-5.1
//http://jakarta.apache.org/builds/jakarta-bcel/release/v5.1/
package com.puppycrawl.tools.checkstyle.bcel;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.apache.bcel.Repository;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Visitor;
import org.apache.bcel.util.ClassLoaderRepository;
import com.puppycrawl.tools.checkstyle.DefaultContext;
import com.puppycrawl.tools.checkstyle.ModuleFactory;
import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
import com.puppycrawl.tools.checkstyle.api.Configuration;
import com.puppycrawl.tools.checkstyle.api.Context;
import com.puppycrawl.tools.checkstyle.api.LocalizedMessage;
import com.puppycrawl.tools.checkstyle.api.LocalizedMessages;
/**
* Checks a set of class files using BCEL
* @author Rick Giles
*/
//TODO: Refactor with AbstractFileSetCheck and TreeWalker
public class ClassFileSetCheck
extends AbstractFileSetCheck
implements IObjectSetVisitor
{
/** visitors for BCEL parse tree walk */
private final Set mTreeVisitors = new HashSet();
/** all the registered checks */
private final Set mAllChecks = new HashSet();
/** all visitors for IObjectSetVisitor visits */
private final Set mObjectSetVisitors = new HashSet();
/** class loader to resolve classes with. **/
private ClassLoader mClassLoader;
/** context of child components */
private Context mChildContext;
/** a factory for creating submodules (i.e. the Checks) */
private ModuleFactory mModuleFactory;
/** Error messages */
HashMap mMessageMap = new HashMap();
/**
* Creates a new <code>ClassFileSetCheck</code> instance.
* Initializes the acceptable file extensions.
*/
public ClassFileSetCheck()
{
setFileExtensions(new String[]{"class", "jar", "zip"});
}
/**
* Stores the class loader and makes it the Repository's class loader.
* @param aClassLoader class loader to resolve classes with.
*/
public void setClassLoader(ClassLoader aClassLoader)
{
Repository.setRepository(new ClassLoaderRepository(aClassLoader));
mClassLoader = aClassLoader;
}
/**
* Sets the module factory for creating child modules (Checks).
* @param aModuleFactory the factory
*/
public void setModuleFactory(ModuleFactory aModuleFactory)
{
mModuleFactory = aModuleFactory;
}
/**
* Instantiates, configures and registers a Check that is specified
* in the provided configuration.
* @see com.puppycrawl.tools.checkstyle.api.AutomaticBean
*/
public void setupChild(Configuration aChildConf)
throws CheckstyleException
{
// TODO: improve the error handing
final String name = aChildConf.getName();
final Object module = mModuleFactory.createModule(name);
if (!(module instanceof AbstractCheckVisitor)) {
throw new CheckstyleException(
"ClassFileSet is not allowed as a parent of " + name);
}
final AbstractCheckVisitor c = (AbstractCheckVisitor) module;
c.contextualize(mChildContext);
c.configure(aChildConf);
c.init();
registerCheck(c);
}
/** @see com.puppycrawl.tools.checkstyle.api.Configurable */
public void finishLocalSetup()
{
DefaultContext checkContext = new DefaultContext();
checkContext.add("classLoader", mClassLoader);
checkContext.add("messageMap", mMessageMap);
checkContext.add("severity", getSeverity());
mChildContext = checkContext;
}
/**
* Register a check.
* @param aCheck the check to register
*/
private void registerCheck(AbstractCheckVisitor aCheck)
{
mAllChecks.add(aCheck);
}
/**
* @see com.puppycrawl.tools.checkstyle.api.FileSetCheck
*/
public void process(File[] aFiles)
{
registerVisitors();
// get all the JavaClasses in the files
final Set javaClasses = extractJavaClasses(aFiles);
visitSet(javaClasses);
// walk each Java class parse tree
final JavaClassWalker walker = new JavaClassWalker();
walker.setVisitor(getTreeVisitor());
final Iterator it = javaClasses.iterator();
while (it.hasNext()) {
final JavaClass clazz = (JavaClass) it.next();
visitObject(clazz);
walker.walk(clazz);
leaveObject(clazz);
}
leaveSet(javaClasses);
fireErrors();
}
/**
* Gets the visitor for a parse tree walk.
* @return the visitor for a parse tree walk.
*/
private Visitor getTreeVisitor()
{
return new VisitorSet(mTreeVisitors);
}
/**
* Registers all the visitors for IObjectSetVisitor visits, and for
* tree walk visits.
*/
private void registerVisitors()
{
mObjectSetVisitors.addAll(mAllChecks);
final Iterator it = mAllChecks.iterator();
while (it.hasNext()) {
final AbstractCheckVisitor check = (AbstractCheckVisitor) it.next();
final IDeepVisitor visitor = check.getVisitor();
mObjectSetVisitors.add(visitor);
mTreeVisitors.add(visitor);
}
}
/**
* Gets the set of all visitors for all the checks.
* @return the set of all visitors for all the checks.
*/
private Set getObjectSetVisitors()
{
return mObjectSetVisitors;
}
/**
* Gets the set of all JavaClasses within a set of Files.
* @param aFiles the set of files to extract from.
* @return the set of all JavaClasses within aFiles.
*/
private Set extractJavaClasses(File[] aFiles)
{
final Set result = new HashSet();
final File[] classFiles = filter(aFiles);
// get Java classes from each filtered file
for (int i = 0; i < classFiles.length; i++) {
try {
final Set extracted = extractJavaClasses(classFiles[i]);
result.addAll(extracted);
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return result;
}
/** @see com.puppycrawl.tools.checkstyle.bcel.IObjectSetVisitor */
public void visitSet(Set aSet)
{
// register the JavaClasses in the Repository
Repository.clearCache();
Iterator it = aSet.iterator();
while (it.hasNext()) {
final JavaClass javaClass = (JavaClass) it.next();
Repository.addClass(javaClass);
}
// visit the visitors
it = getObjectSetVisitors().iterator();
while (it.hasNext()) {
final IObjectSetVisitor visitor = (IObjectSetVisitor) it.next();
visitor.visitSet(aSet);
}
}
/** @see com.puppycrawl.tools.checkstyle.bcel.IObjectSetVisitor */
public void visitObject(Object aObject)
{
final Iterator it = getObjectSetVisitors().iterator();
while (it.hasNext()) {
final IObjectSetVisitor visitor = (IObjectSetVisitor) it.next();
visitor.visitObject(aObject);
}
}
/** @see com.puppycrawl.tools.checkstyle.bcel.IObjectSetVisitor */
public void leaveObject(Object aObject)
{
final Iterator it = getObjectSetVisitors().iterator();
while (it.hasNext()) {
final IObjectSetVisitor visitor = (IObjectSetVisitor) it.next();
visitor.leaveObject(aObject);
}
}
/** @see com.puppycrawl.tools.checkstyle.bcel.IObjectSetVisitor */
public void leaveSet(Set aSet)
{
final Iterator it = getObjectSetVisitors().iterator();
while (it.hasNext()) {
final IObjectSetVisitor visitor = (IObjectSetVisitor) it.next();
visitor.leaveSet(aSet);
}
}
/**
* Extracts the JavaClasses from .class, .zip, and .jar files.
* @param aFile the file to extract from.
* @return the set of JavaClasses from aFile.
* @throws IOException if there is an error.
*/
private Set extractJavaClasses(File aFile)
throws IOException
{
final Set result = new HashSet();
final String fileName = aFile.getPath();
if (fileName.endsWith(".jar") || fileName.endsWith(".zip")) {
final ZipFile zipFile = new ZipFile(fileName);
final Enumeration entries = zipFile.entries();
while (entries.hasMoreElements()) {
final ZipEntry entry = (ZipEntry) entries.nextElement();
final String entryName = entry.getName();
if (entryName.endsWith(".class")) {
final InputStream in = zipFile.getInputStream(entry);
final JavaClass javaClass =
new ClassParser(in, entryName).parse();
result.add(javaClass);
}
}
}
else {
final JavaClass javaClass = new ClassParser(fileName).parse();
result.add(javaClass);
}
return result;
}
/**
* Notify all listeners about the errors in a file.
* Calls <code>MessageDispatcher.fireErrors()</code> with
* all logged errors and than clears errors' list.
*/
private void fireErrors()
{
Set keys = mMessageMap.keySet();
Iterator iter = keys.iterator();
while (iter.hasNext()) {
String key = (String) iter.next();
getMessageDispatcher().fireFileStarted(key);
LocalizedMessages localizedMessages = (LocalizedMessages) mMessageMap.get(key);
final LocalizedMessage[] errors = localizedMessages.getMessages();
localizedMessages.reset();
getMessageDispatcher().fireErrors(key, errors);
getMessageDispatcher().fireFileFinished(key);
}
}
}