package net.ayld.facade.dependency.resolver.impl; import java.io.IOException; import java.util.Set; import net.ayld.facade.component.ListenableComponent; import net.ayld.facade.dependency.resolver.DependencyResolver; import net.ayld.facade.event.model.ClassDependencyResolutionEndEvent; import net.ayld.facade.event.model.ClassDependencyResolutionStartEvent; import net.ayld.facade.model.ClassFile; import net.ayld.facade.model.ClassName; import org.apache.bcel.classfile.ClassParser; 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 com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; public class ManualBinaryParseClassDependencyResolver extends ListenableComponent implements DependencyResolver<ClassFile>{ private static final String BINARY_ARRAY_ID_PREFIX = "["; private static final String BINARY_TYPE_PREFIX = "L"; private static final String BINARY_ARRAY_ID_SUFFIX = ";"; private static final String ARRAY_ID_PREFIX_REGEX = "\\" + BINARY_ARRAY_ID_PREFIX + "+"; private static final String TYPE_PREFIX_REGEX = BINARY_TYPE_PREFIX; @Override public Set<ClassName> resolve(ClassFile classFile) throws IOException { eventBus.post(new ClassDependencyResolutionStartEvent("resolving: " + classFile.physicalFile().getAbsolutePath(), this.getClass())); final JavaClass javaClass = new ClassParser(classFile.toString()).parse(); final DependencyVisitor dependencyVisitor = new DependencyVisitor(javaClass); final DescendingVisitor classWalker = new DescendingVisitor(javaClass, dependencyVisitor); classWalker.visit(); eventBus.post(new ClassDependencyResolutionEndEvent("resolved: " + dependencyVisitor.getFoundDependencies(), this.getClass())); return dependencyVisitor.getFoundDependencies(); } @Override public Set<ClassName> resolve(Set<ClassFile> classFiles) throws IOException { final Set<ClassName> result = Sets.newHashSet(); for (ClassFile classFile : classFiles) { result.addAll(resolve(classFile)); } return result; } private static class DependencyVisitor extends EmptyVisitor { private final JavaClass javaClass; private Set<ClassName> foundDependencies = Sets.newHashSet(); private DependencyVisitor(JavaClass javaClass) { this.javaClass = javaClass; } @Override public void visitConstantClass(ConstantClass constantClass) { final ConstantPool cp = javaClass.getConstantPool(); String dependency = constantClass.getBytes(cp); // handle array dependencies // if this is an array dependency remove identifiers if (dependency.startsWith(BINARY_ARRAY_ID_PREFIX)) { dependency = dependency.replaceAll(ARRAY_ID_PREFIX_REGEX, ""); dependency = dependency.replaceAll(BINARY_ARRAY_ID_SUFFIX, ""); } // handle binary type notations if (dependency.startsWith(BINARY_TYPE_PREFIX)) { dependency = dependency.replaceAll(TYPE_PREFIX_REGEX, ""); } // because for some reason BCEL returns dependencies like this: // // com/something/Class // // which is odd // TODO check if there is a way to return dependencies dot delimited dependency = dependency.replaceAll("/", "."); foundDependencies.add(new ClassName(dependency)); } private Set<ClassName> getFoundDependencies() { return ImmutableSet.copyOf(foundDependencies); } } }