package knorxx.framework.generator.dependency;
import com.google.common.base.Charsets;
import com.google.common.base.Optional;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import knorxx.framework.generator.JavaFile;
import knorxx.framework.generator.JavaFileOnClasspath;
import knorxx.framework.generator.util.JavaIdentifierUtils;
import static knorxx.framework.generator.util.JavaIdentifierUtils.getPackageName;
import static knorxx.framework.generator.util.JavaIdentifierUtils.isValidClassName;
import net.sourceforge.pmd.lang.ParserOptions;
import net.sourceforge.pmd.lang.java.Java18Parser;
import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTName;
import net.sourceforge.pmd.lang.java.ast.ASTPackageDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorAdapter;
/**
*
* @author sj
*/
public class JavaSourceDependencyCollector extends DependencyCollector {
public JavaSourceDependencyCollector() {
}
public JavaSourceDependencyCollector(DependencyCollector nextCollector) {
super(nextCollector);
}
@Override
public Set<String> collect(JavaFileOnClasspath<?> javaFile, ClassLoader classLoader) {
throw new UnsupportedOperationException("Can't work on Java files without source!");
}
@Override
protected Set<String> collectInternal(JavaFile<?> javaFile, final ClassLoader classLoader) {
final Set<String> result = new TreeSet<>();
Optional<InputStream> sourceInputStream = javaFile.getSourceInputStream();
if (sourceInputStream.isPresent()) {
try (InputStream input = sourceInputStream.get()) {
ASTCompilationUnit compilationUnit = (ASTCompilationUnit) new Java18Parser(
new ParserOptions()).parse("", new InputStreamReader(input, Charsets.UTF_8));
new JavaParserVisitorAdapter() {
private String packageName;
private Set<String> importedClassNames = new HashSet<>();
private List<String> asteriskPackages = new ArrayList<>();
@Override
public Object visit(ASTPackageDeclaration node, Object data) {
packageName = node.getPackageNameImage();
asteriskPackages.add(packageName);
return super.visit(node, data);
}
@Override
public Object visit(ASTImportDeclaration node, Object data) {
String className = node.getImportedName();
boolean isStatic = node.isStatic();
boolean isAsterisk = node.isImportOnDemand();
if (isStatic) {
if (isAsterisk) {
importedClassNames.add(className);
result.add(className);
} else {
// actually only removing the static part
String classNameWithoutStaticPart = getPackageName(className);
importedClassNames.add(classNameWithoutStaticPart);
result.add(classNameWithoutStaticPart);
}
} else if(isAsterisk) {
if (!asteriskPackages.contains(className)) {
asteriskPackages.add(className);
}
} else if (isValidClassName(className)) {
importedClassNames.add(className);
}
return super.visit(node, data);
}
@Override
public Object visit(ASTPrimaryPrefix node, Object data) {
ASTName name = node.getFirstChildOfType(ASTName.class);
if(name != null) {
String fieldAccess = name.getImage();
if (fieldAccess.contains(".")) {
String fieldName = JavaIdentifierUtils.getJavaClassSimpleName(fieldAccess);
if (JavaIdentifierUtils.isValidConstantName(fieldName)) {
String className = JavaIdentifierUtils.getPackageName(fieldAccess);
if (className.contains(".")) {
if (isValidClassName(className)) {
result.add(className);
}
} else {
String simpleClassName = className;
boolean resolved = false;
if (isValidClassName(simpleClassName)) {
// 1. try to find the class in the list of imports
for (String importedClassName : importedClassNames) {
if (importedClassName.endsWith("." + simpleClassName)) {
result.add(importedClassName);
resolved = true;
break;
}
}
if (!resolved) {
for (String asteriskPackage : asteriskPackages) {
// 2. check if it is a class in the same package
try {
String classNameWithCurrentPackage = asteriskPackage + "." + simpleClassName;
Class.forName(classNameWithCurrentPackage, false, classLoader);
result.add(classNameWithCurrentPackage);
break;
} catch (ClassNotFoundException ex) {
// just do nothing... we are simply not able to resolve the class name... :-(
}
}
}
}
}
}
}
}
return super.visit(node, data);
}
}.visit(compilationUnit, null);
} catch (Exception ex) {
throw new IllegalStateException("Can't parse and analyze the Java source in '" + javaFile + "'!", ex);
}
}
return result;
}
}