package org.codehaus.mojo.repositorytools.validation;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.apache.bcel.classfile.ConstantClass;
import org.apache.bcel.classfile.ConstantPool;
import org.apache.bcel.classfile.DescendingVisitor;
import org.apache.bcel.classfile.EmptyVisitor;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.util.ClassPath;
import org.apache.bcel.util.SyntheticRepository;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.project.artifact.InvalidDependencyVersionException;
import org.codehaus.mojo.repositorytools.components.RepositoryToolsException;
import org.codehaus.plexus.util.StringUtils;
/**
* @plexus.component role="org.codehaus.mojo.repositorytools.validation.ArtifactValidator"
* role-hint="imports"
* @author tom
*
*/
public class ImportsValidator extends AbstractValidator
{
private static final String CLASS_SUFFIX = ".class";
/**
* @plexus.requirement
*/
private ArtifactResolver resolver;
/**
* @plexus.requirement
*/
private ArtifactMetadataSource source;
public List validateArtifact(Artifact artifact,
List remoteRepositories,
ArtifactRepository localRepository) throws RepositoryToolsException
{
List messages = new ArrayList();
if (!artifact.getArtifactHandler().getLanguage().equals("java"))
{
messages.add(new ValidationMessage(ValidationMessage.INFO,
"Not a java artifact"));
}
SyntheticRepository bcelRepo;
String[] classes;
try
{
ClassPath classpath = getClassPath(artifact, remoteRepositories,
localRepository);
bcelRepo = SyntheticRepository.getInstance(classpath);
classes = findClasses(artifact.getFile());
}
catch (Exception e1)
{
throw new RepositoryToolsException("Error setting up classpath");
}
Set imported = new HashSet();
for (int i = 0; i < classes.length; i++) {
String klass = classes[i];
JavaClass jc;
try
{
jc = bcelRepo.loadClass(klass);
bcelRepo.storeClass(jc);
Set s = getImportedClasses(jc);
imported.addAll(s);
}
catch (ClassNotFoundException e)
{
// this should not happen !
messages.add(new ValidationMessage(ValidationMessage.ERROR,
"Class " + klass + "could not be loaded."));
}
}
for (Iterator iterator = imported.iterator(); iterator.hasNext();) {
String i = (String) iterator.next();
JavaClass classFile;
try
{
classFile = bcelRepo.loadClass(i);
bcelRepo.storeClass(classFile);
}
catch (ClassNotFoundException e)
{
messages.add(new ValidationMessage(ValidationMessage.WARNING,
"Class " + i + " not found in any dependency."));
}
}
// sort the messages so we get a nicer view on the packages
Collections.sort(messages, new Comparator()
{
public int compare(Object o1, Object o2)
{
return ((ValidationMessage) o1).getMessage().compareTo(((ValidationMessage) o2).getMessage());
}
});
return messages;
}
public ClassPath getClassPath(Artifact artifact,
List remoteRepositories,
ArtifactRepository localRepository)
throws ProjectBuildingException, ArtifactResolutionException,
ArtifactNotFoundException, InvalidDependencyVersionException
{
MavenProject pomProject = createProject(artifact, remoteRepositories,
localRepository);
resolver.resolve(artifact, remoteRepositories, localRepository);
ArtifactFilter filter = new ScopeArtifactFilter(Artifact.SCOPE_COMPILE);
Set artifacts = pomProject.createArtifacts(artifactFactory, null,
filter);
resolver.resolveTransitively(artifacts,
artifact, localRepository, remoteRepositories, source, filter);
StringBuffer result = new StringBuffer();
// TODO portable way to get the boot classpath
result.append(System.getProperty("sun.boot.class.path"));
result.append(File.pathSeparatorChar);
for (Iterator iterator = artifacts.iterator(); iterator.hasNext();) {
Artifact a = (Artifact) iterator.next();
result.append(a.getFile());
result.append(File.pathSeparatorChar);
}
result.append(artifact.getFile());
return new ClassPath(result.toString());
}
private static String[] findClasses(File jarFile) throws IOException
{
List result = new ArrayList();
JarFile jar = new JarFile(jarFile);
Enumeration entries = jar.entries();
while (entries.hasMoreElements())
{
JarEntry je = (JarEntry) entries.nextElement();
String name = je.getName();
if (name.endsWith(CLASS_SUFFIX))
{
name = name.substring(0, name.length() - CLASS_SUFFIX.length());
name = name.replace('/', '.');
result.add(name);
}
}
return (String[]) result.toArray(new String[result.size()]);
}
private Set getImportedClasses(JavaClass jc)
{
final Set imports = new HashSet();
final ConstantPool pool = jc.getConstantPool();
new DescendingVisitor(jc, new EmptyVisitor()
{
public void visitConstantClass(ConstantClass cc)
{
String s = pool.constantToString(cc);
s = s.replace('/', '.');
if (s.startsWith("["))
{
while (s.startsWith("["))
{
s = s.substring(1);
}
s = s.substring(1); // remove [L
}
if (s != null && !s.startsWith("java."))
{
imports.add(s);
}
}
}).visit();
return imports;
}
public String getDescription()
{
return "Validation of imports";
}
public boolean canValidate(Artifact artifact)
{
String classifier = artifact.getClassifier();
if (!StringUtils.isEmpty(classifier) && classifier.indexOf("sources") >= 0) {
return false;
}
return artifact.getArtifactHandler().getLanguage().equals("java")
&& !artifact.getType().equals("pom");
}
}