more tests
This commit is contained in:
parent
ba53b67ccf
commit
766da3d665
22 changed files with 961 additions and 0 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
.classpath
|
||||
.project
|
||||
.settings
|
||||
*~
|
||||
target
|
||||
14
src/main/java/yooze/ClassNotFound.java
Normal file
14
src/main/java/yooze/ClassNotFound.java
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package yooze;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class ClassNotFound extends RuntimeException {
|
||||
|
||||
public ClassNotFound(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ClassNotFound(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
||||
11
src/main/java/yooze/InspectableClasspath.java
Normal file
11
src/main/java/yooze/InspectableClasspath.java
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package yooze;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javassist.ClassPath;
|
||||
|
||||
public interface InspectableClasspath extends ClassPath {
|
||||
public String getResourceName();
|
||||
|
||||
public List<String> getClasses();
|
||||
}
|
||||
11
src/main/java/yooze/Scanner.java
Normal file
11
src/main/java/yooze/Scanner.java
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package yooze;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
public interface Scanner {
|
||||
public List<InspectableClasspath> scanArchive(String archiveName) throws IOException;
|
||||
|
||||
public List<InspectableClasspath> scanArchive(File file) throws IOException;
|
||||
}
|
||||
40
src/main/java/yooze/application/GraphBuilderFactory.java
Normal file
40
src/main/java/yooze/application/GraphBuilderFactory.java
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
package yooze.application;
|
||||
|
||||
import yooze.GraphBuilder;
|
||||
import yooze.scanner.ArchiveScanner;
|
||||
import yooze.scanner.ClassesDirScanner;
|
||||
import yooze.scanner.LibScanner;
|
||||
import yooze.scanner.TgzScanner;
|
||||
import yooze.scanner.WarScanner;
|
||||
|
||||
public class GraphBuilderFactory {
|
||||
/**
|
||||
* Factory method for getting a builder that does earfiles
|
||||
*/
|
||||
public static GraphBuilder getEarBuilder() {
|
||||
return new GraphBuilder(new ArchiveScanner(new WarScanner()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method for getting a builder that does (downloaded) .tar.gz files
|
||||
*/
|
||||
public static GraphBuilder getDefaultTgzBuilder() {
|
||||
GraphBuilder tgzBuilder = new GraphBuilder(new TgzScanner());
|
||||
tgzBuilder.setPackageExcludePatterns("java.*", "sun.*", "com.sun.*");
|
||||
return tgzBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method for getting a builder that scans a lib directory (containing jars)
|
||||
*/
|
||||
public static GraphBuilder getLibDirectoryBuilder() {
|
||||
return new GraphBuilder(new LibScanner());
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method for getting a builder that scans a directory containing classes
|
||||
*/
|
||||
public static GraphBuilder getClassesDirectoryBuilder() {
|
||||
return new GraphBuilder(new ClassesDirScanner());
|
||||
}
|
||||
}
|
||||
89
src/main/java/yooze/application/Neo4jDao.java
Normal file
89
src/main/java/yooze/application/Neo4jDao.java
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
package yooze.application;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.neo4j.graphdb.Direction;
|
||||
import org.neo4j.graphdb.GraphDatabaseService;
|
||||
import org.neo4j.graphdb.Node;
|
||||
import org.neo4j.graphdb.RelationshipType;
|
||||
import org.neo4j.graphdb.ReturnableEvaluator;
|
||||
import org.neo4j.graphdb.StopEvaluator;
|
||||
import org.neo4j.graphdb.Transaction;
|
||||
import org.neo4j.graphdb.Traverser;
|
||||
import org.neo4j.kernel.EmbeddedGraphDatabase;
|
||||
|
||||
import yooze.domain.ClassModel;
|
||||
|
||||
public class Neo4jDao {
|
||||
private GraphDatabaseService graphDb;
|
||||
private Node rootNode;
|
||||
private ConcurrentHashMap<String, Node> nodes = new ConcurrentHashMap<String, Node>();
|
||||
|
||||
public enum MyRelationshipTypes implements RelationshipType {
|
||||
CONTAINS, REFERENCES
|
||||
}
|
||||
|
||||
public Neo4jDao(String database) {
|
||||
graphDb = new EmbeddedGraphDatabase(database);
|
||||
Transaction tx = graphDb.beginTx();
|
||||
try {
|
||||
rootNode = graphDb.createNode();
|
||||
rootNode.setProperty("name", "root");
|
||||
tx.success();
|
||||
} finally {
|
||||
tx.finish();
|
||||
}
|
||||
}
|
||||
|
||||
public void insertClass(ClassModel classModel) {
|
||||
Transaction tx = graphDb.beginTx();
|
||||
try {
|
||||
Node newNode = findOrCreateNode(classModel);
|
||||
|
||||
for (ClassModel ref : classModel.getReferences()) {
|
||||
Node refNode = findOrCreateNode(ref);
|
||||
newNode.createRelationshipTo(refNode,
|
||||
MyRelationshipTypes.REFERENCES);
|
||||
}
|
||||
tx.success();
|
||||
} finally {
|
||||
tx.finish();
|
||||
}
|
||||
}
|
||||
|
||||
private Node findOrCreateNode(ClassModel classModel) {
|
||||
Node newNode = findNode(classModel.getName());
|
||||
if (newNode == null) {
|
||||
Transaction tx = graphDb.beginTx();
|
||||
try {
|
||||
newNode = graphDb.createNode();
|
||||
newNode.setProperty("name", classModel.getName());
|
||||
|
||||
tx.success();
|
||||
} finally {
|
||||
tx.finish();
|
||||
}
|
||||
}
|
||||
return newNode;
|
||||
}
|
||||
|
||||
public Node findNode(String className) {
|
||||
Node node = nodes.get(className);
|
||||
if (node != null) {
|
||||
return node;
|
||||
}
|
||||
// do "tablescan". is there a better way?
|
||||
Traverser classesTraverser = rootNode.traverse(
|
||||
Traverser.Order.BREADTH_FIRST, StopEvaluator.DEPTH_ONE,
|
||||
ReturnableEvaluator.ALL_BUT_START_NODE,
|
||||
MyRelationshipTypes.CONTAINS, Direction.OUTGOING);
|
||||
for (Node nextNode : classesTraverser) {
|
||||
if (nextNode.getProperty("name").equals(className)) {
|
||||
nodes.put(className, nextNode);
|
||||
return nextNode;
|
||||
}
|
||||
}
|
||||
return null;// not found
|
||||
|
||||
}
|
||||
}
|
||||
41
src/main/java/yooze/application/Yooze.java
Normal file
41
src/main/java/yooze/application/Yooze.java
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
package yooze.application;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import yooze.GraphBuilder;
|
||||
import yooze.domain.ClassModel;
|
||||
import yooze.domain.Graph;
|
||||
|
||||
public class Yooze {
|
||||
private String neo4jDb;
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
String neoDb = args[0];
|
||||
String earfile = args[1];
|
||||
String in = args[2];
|
||||
String ex = args[2];
|
||||
String startingClassname = args[3];
|
||||
new Yooze(neoDb).createNeoGraph(earfile, in, ex, startingClassname);
|
||||
|
||||
}
|
||||
|
||||
public Yooze(String neo4jDb) {
|
||||
super();
|
||||
this.neo4jDb = neo4jDb;
|
||||
}
|
||||
|
||||
public void createNeoGraph(String archive, String packageIncludePatterns, String packageExcludePatterns,
|
||||
String startingClass) throws IOException {
|
||||
GraphBuilder libDirectoryBuilder = GraphBuilderFactory.getLibDirectoryBuilder();
|
||||
libDirectoryBuilder.setPackageExcludePatterns(packageExcludePatterns);
|
||||
libDirectoryBuilder.setPackageIncludePatterns(packageIncludePatterns);
|
||||
|
||||
Graph graph = libDirectoryBuilder.build(archive, startingClass);
|
||||
|
||||
Neo4jDao neo4jDao = new Neo4jDao(neo4jDb);
|
||||
for (ClassModel model : graph.getChildren()) {
|
||||
neo4jDao.insertClass(model);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
14
src/main/java/yooze/domain/MethodNotFound.java
Normal file
14
src/main/java/yooze/domain/MethodNotFound.java
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package yooze.domain;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class MethodNotFound extends RuntimeException {
|
||||
|
||||
public MethodNotFound(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public MethodNotFound(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
||||
49
src/main/java/yooze/output/DotPrinter.java
Normal file
49
src/main/java/yooze/output/DotPrinter.java
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package yooze.output;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
import yooze.Statistics;
|
||||
import yooze.domain.ClassModel;
|
||||
import yooze.domain.Graph;
|
||||
|
||||
/**
|
||||
* prints the graph as a graphviz dot file. Takes care of circular dependencies. Not threadsafe.
|
||||
*/
|
||||
public class DotPrinter extends GraphPrinter {
|
||||
|
||||
public DotPrinter(OutputStream out) {
|
||||
super(out);
|
||||
}
|
||||
|
||||
public void print(Graph g) {
|
||||
println("digraph \"" + g.getName() + "\" {");
|
||||
println("graph [size=100,100];");
|
||||
for (ClassModel cm : g.getChildren()) {
|
||||
printClassModel(cm);
|
||||
}
|
||||
println("}");
|
||||
}
|
||||
|
||||
private void printClassModel(ClassModel cm) {
|
||||
double boxsize = Math.sqrt(Statistics.getByteCodeSizeForClass(cm.getName()));
|
||||
|
||||
print("\"");
|
||||
print(cm.getName());
|
||||
println("\" [shape=box, height=" + boxsize / 20 + "];");
|
||||
|
||||
if (cm.getReferences() == null || cm.getReferences().size() == 0) {
|
||||
print("\"");
|
||||
print(cm.getName());
|
||||
println("\";");
|
||||
} else {
|
||||
for (ClassModel ref : cm.getReferences()) {
|
||||
String relation = String.format("\"%s\" -> \"%s\"", cm.getName(), ref.getName());
|
||||
if (shouldPrint(relation)) {
|
||||
println(relation);
|
||||
markAsPrinted(relation);
|
||||
printClassModel(ref); // recurse
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
src/main/java/yooze/output/GraphPrinter.java
Normal file
38
src/main/java/yooze/output/GraphPrinter.java
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
package yooze.output;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import yooze.domain.Graph;
|
||||
|
||||
/**
|
||||
* Facilitates subclasses to take care of circular dependencies, using shouldPrint and markAsPrinted
|
||||
*
|
||||
*/
|
||||
public abstract class GraphPrinter extends PrintStream {
|
||||
|
||||
private ArrayList<String> printedRelations;
|
||||
|
||||
public GraphPrinter(OutputStream out) {
|
||||
super(out);
|
||||
printedRelations = new ArrayList<String>();
|
||||
}
|
||||
|
||||
abstract public void print(Graph g);
|
||||
|
||||
protected boolean shouldPrint(String relation) {
|
||||
return !printedRelations.contains(relation);
|
||||
}
|
||||
|
||||
protected void markAsPrinted(String relation) {
|
||||
printedRelations.add(relation);
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
protected static class CouldNotPrint extends RuntimeException {
|
||||
public CouldNotPrint(Throwable t) {
|
||||
super(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
34
src/main/java/yooze/output/JsonPrinter.java
Normal file
34
src/main/java/yooze/output/JsonPrinter.java
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
package yooze.output;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
import yooze.domain.ClassModel;
|
||||
import yooze.domain.Graph;
|
||||
|
||||
public class JsonPrinter extends GraphPrinter {
|
||||
|
||||
public JsonPrinter(OutputStream out) {
|
||||
super(out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(Graph g) {
|
||||
print("[");
|
||||
for (ClassModel cm : g.getChildren()) {
|
||||
printClassModel(cm);
|
||||
}
|
||||
print("]");
|
||||
}
|
||||
|
||||
private void printClassModel(ClassModel cm) {
|
||||
for (ClassModel ref : cm.getReferences()) {
|
||||
String relation = String.format("{\"key\":\"%s\",\"parent\":\"%s\"},", ref.getName(), cm.getName());
|
||||
if (shouldPrint(relation)) {
|
||||
print(relation);
|
||||
markAsPrinted(relation);
|
||||
printClassModel(ref); // recurse
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
28
src/main/java/yooze/scanner/ClassesDirScanner.java
Normal file
28
src/main/java/yooze/scanner/ClassesDirScanner.java
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
package yooze.scanner;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import yooze.InspectableClasspath;
|
||||
import yooze.Scanner;
|
||||
|
||||
/**
|
||||
* reads classes as .class files from a directory
|
||||
*/
|
||||
public class ClassesDirScanner implements Scanner {
|
||||
|
||||
@Override
|
||||
public List<InspectableClasspath> scanArchive(String archiveName) throws IOException {
|
||||
return scanArchive(new File(archiveName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<InspectableClasspath> scanArchive(File file) throws IOException {
|
||||
List<InspectableClasspath> result = new ArrayList<InspectableClasspath>();
|
||||
result.add(new DirClassPath(file));
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
18
src/main/java/yooze/scanner/ClasspathAdder.java
Normal file
18
src/main/java/yooze/scanner/ClasspathAdder.java
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
package yooze.scanner;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import yooze.InspectableClasspath;
|
||||
import yooze.Util;
|
||||
|
||||
public class ClasspathAdder {
|
||||
public static void addEntriesFromWarToClasspath(JarFile warfile, List<InspectableClasspath> classpaths,
|
||||
JarEntry entry) throws IOException {
|
||||
File jarFile = Util.extractFile(warfile, entry);
|
||||
classpaths.add(new JarClassPath(new JarFile(jarFile)));
|
||||
}
|
||||
}
|
||||
90
src/main/java/yooze/scanner/DirClassPath.java
Normal file
90
src/main/java/yooze/scanner/DirClassPath.java
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
package yooze.scanner;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javassist.NotFoundException;
|
||||
import yooze.ClassByteCountingInputStream;
|
||||
import yooze.ClassNotFound;
|
||||
import yooze.InspectableClasspath;
|
||||
import yooze.Util;
|
||||
|
||||
public class DirClassPath implements InspectableClasspath {
|
||||
|
||||
private final File dir;
|
||||
|
||||
public DirClassPath(File dir) {
|
||||
super();
|
||||
this.dir = dir;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
}
|
||||
|
||||
public URL find(String className) {
|
||||
try {
|
||||
return new URL("file:///" + dir.getCanonicalPath() + "/" + Util.toClassResource(className));
|
||||
} catch (IOException e) {
|
||||
throw new ClassNotFound(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DirectoryClasspath[" + dir + "]";
|
||||
}
|
||||
|
||||
public List<String> getClasses() {
|
||||
List<String> classes = new ArrayList<String>();
|
||||
getClasses(dir, dir, classes);
|
||||
return classes;
|
||||
}
|
||||
|
||||
private void getClasses(File root, File dir, List<String> classes) {
|
||||
File[] fileList = dir.listFiles();
|
||||
for (File entry : fileList) {
|
||||
if (entry.isDirectory()) {
|
||||
// recurse deeper
|
||||
getClasses(root, entry, classes);
|
||||
} else if (isClassFile(entry)) {
|
||||
classes.add(createQualifiedClassNameFromFileLocation(root, entry));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isClassFile(File entry) {
|
||||
return entry.isFile() && entry.getName().endsWith(".class");
|
||||
}
|
||||
|
||||
private String createQualifiedClassNameFromFileLocation(File root, File classFile) {
|
||||
String absolutePath = classFile.getAbsolutePath();
|
||||
String relativePath = absolutePath.substring(root.getAbsolutePath().length() + 1);
|
||||
String packageFormat = relativePath.replaceAll("\\\\", ".").replaceAll("/", ".");
|
||||
String substring = packageFormat.substring(0, packageFormat.length() - 6);
|
||||
return substring;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResourceName() {
|
||||
return dir.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream openClassfile(String className) throws NotFoundException {
|
||||
File classFile = new File(dir, Util.toClassResource(className) + ".class");
|
||||
if (!classFile.exists()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new ClassByteCountingInputStream(className, new FileInputStream(classFile));
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
78
src/main/java/yooze/scanner/JarClassPath.java
Normal file
78
src/main/java/yooze/scanner/JarClassPath.java
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
package yooze.scanner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
import javassist.NotFoundException;
|
||||
import yooze.ClassByteCountingInputStream;
|
||||
import yooze.ClassNotFound;
|
||||
import yooze.InspectableClasspath;
|
||||
import yooze.Util;
|
||||
|
||||
public class JarClassPath implements InspectableClasspath {
|
||||
|
||||
private final JarFile jar;
|
||||
|
||||
public JarClassPath(JarFile jar) {
|
||||
super();
|
||||
this.jar = jar;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
try {
|
||||
jar.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public URL find(String className) {
|
||||
try {
|
||||
return new URL("file:///" + jar.getName() + "!" + Util.toClassResource(className));
|
||||
} catch (MalformedURLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JarClasspath[" + jar.getName() + "]";
|
||||
}
|
||||
|
||||
public List<String> getClasses() {
|
||||
List<String> classes = new ArrayList<String>();
|
||||
for (Enumeration<JarEntry> entries = jar.entries(); entries.hasMoreElements();) {
|
||||
String name = entries.nextElement().getName().replaceAll("/", ".");
|
||||
if (name.endsWith(".class")) {
|
||||
classes.add(name.substring(0, name.length() - 6));
|
||||
}
|
||||
}
|
||||
return classes;
|
||||
}
|
||||
|
||||
public String getResourceName() {
|
||||
return jar.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream openClassfile(String className) throws NotFoundException {
|
||||
ZipEntry entry = jar.getEntry(Util.toClassResource(className));
|
||||
if (entry == null) {
|
||||
throw new ClassNotFound(className);
|
||||
}
|
||||
try {
|
||||
return new ClassByteCountingInputStream(className, jar.getInputStream(entry));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
108
src/main/java/yooze/scanner/WarScanner.java
Normal file
108
src/main/java/yooze/scanner/WarScanner.java
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
package yooze.scanner;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import yooze.InspectableClasspath;
|
||||
|
||||
public class WarScanner {
|
||||
private final static Pattern classPattern = Pattern.compile("WEB-INF/classes/(.*)\\.class");
|
||||
private final static Pattern packagePattern = Pattern.compile("(.+\\/+)(.+\\.class)");
|
||||
|
||||
public List<InspectableClasspath> scanWars(List<JarFile> wars) {
|
||||
List<InspectableClasspath> classpaths = new ArrayList<InspectableClasspath>();
|
||||
for (JarFile war : wars) {
|
||||
classpaths.addAll(scanWar(war));
|
||||
}
|
||||
return classpaths;
|
||||
}
|
||||
|
||||
private List<InspectableClasspath> scanWar(JarFile warfile) {
|
||||
List<InspectableClasspath> classpaths = new ArrayList<InspectableClasspath>();
|
||||
File classesDir = createTempLocation(classpaths);
|
||||
classpaths.add(new DirClassPath(classesDir));
|
||||
for (Enumeration<JarEntry> entries = warfile.entries(); entries.hasMoreElements();) {
|
||||
JarEntry entry = (JarEntry) entries.nextElement();
|
||||
if (isArchive(entry)) {
|
||||
try {
|
||||
ClasspathAdder.addEntriesFromWarToClasspath(warfile, classpaths, entry);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
extractClass(warfile, entry, classesDir);
|
||||
}
|
||||
}
|
||||
return classpaths;
|
||||
}
|
||||
|
||||
private File createTempLocation(List<InspectableClasspath> classpaths) {
|
||||
File classesDir = new File(new File(System.getProperty("java.io.tmpdir")), "classes"
|
||||
+ System.currentTimeMillis());
|
||||
boolean dirsMade = classesDir.mkdirs();
|
||||
if (!dirsMade) {
|
||||
throw new RuntimeException("Directory " + classesDir + " could not be created");
|
||||
}
|
||||
return classesDir;
|
||||
}
|
||||
|
||||
private boolean isArchive(JarEntry entry) {
|
||||
String name = entry.getName();
|
||||
return name.startsWith("WEB-INF/lib") && name.endsWith(".jar");
|
||||
}
|
||||
|
||||
private File extractClass(JarFile warfile, ZipEntry entry, File classesDir) {
|
||||
Matcher matcher = classPattern.matcher(entry.getName());
|
||||
if (matcher.matches()) {
|
||||
String className = matcher.group(1) + ".class";
|
||||
Matcher matcher2 = packagePattern.matcher(className);
|
||||
if (matcher2.matches()) {
|
||||
String packageName = matcher2.group(1);
|
||||
File classDir = createUnarchivedPackageDicectory(classesDir, packageName);
|
||||
String simpleClassName = matcher2.group(2);
|
||||
File classFile = new File(classDir, simpleClassName);
|
||||
return createUnarchivedClassfile(warfile, entry, classFile);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private File createUnarchivedClassfile(JarFile warfile, ZipEntry entry, File classFile) {
|
||||
try {
|
||||
FileOutputStream out = new FileOutputStream(classFile);
|
||||
IOUtils.copy(warfile.getInputStream(entry), out);
|
||||
return classFile;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private File createUnarchivedPackageDicectory(File classesDir, String packageName) {
|
||||
File classDir = new File(classesDir, packageName);
|
||||
if (!classDir.exists()) {
|
||||
boolean packageDirsMade = classDir.mkdirs();
|
||||
if (!packageDirsMade) {
|
||||
throw new DirectoryCouldNotBeCreated(classDir);
|
||||
}
|
||||
}
|
||||
return classDir;
|
||||
}
|
||||
|
||||
private static class DirectoryCouldNotBeCreated extends RuntimeException {
|
||||
public DirectoryCouldNotBeCreated(File classDir) {
|
||||
super(classDir.toString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
86
src/main/resources/go.js
Normal file
86
src/main/resources/go.js
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
function init(){
|
||||
var $ = go.GraphObject.make; // for conciseness in defining templates
|
||||
var diagram = $(go.Diagram, "myDiagram"); //
|
||||
|
||||
|
||||
// Define a node template showing class names.
|
||||
// Double-clicking opens up the documentation for that class.
|
||||
diagram.nodeTemplate =
|
||||
$(go.Node, "Auto",
|
||||
{ doubleClick: nodeDoubleClick }, // this function is defined below
|
||||
{ toolTip:
|
||||
$(go.Adornment, "Auto",
|
||||
$(go.Shape, { fill: "lightyellow" }),
|
||||
$(go.TextBlock, "double-click\nfor documentation",
|
||||
{ margin: 5 })
|
||||
)
|
||||
},
|
||||
$(go.Shape, { fill: "darkslategray", stroke: null }),
|
||||
$(go.TextBlock,
|
||||
{ font: "bold 13px Helvetica, bold Arial, sans-serif",
|
||||
stroke: "white", margin: 3 },
|
||||
new go.Binding("text", "key")));
|
||||
|
||||
// Define a trivial link template with no arrowhead
|
||||
diagram.linkTemplate =
|
||||
$(go.Link, // the whole link panel
|
||||
{ selectable: false },
|
||||
$(go.Shape)); // the link shape, with the default black stroke
|
||||
|
||||
// Collect all of the data for the model of the class hierarchy
|
||||
var nodeDataArray = [];
|
||||
|
||||
// Iterate over all of the classes in "go"
|
||||
for (k in go) {
|
||||
var cls = go[k];
|
||||
if (!cls) continue;
|
||||
var proto = cls.prototype;
|
||||
if (!proto) continue;
|
||||
proto.constructor.className = k; // remember name
|
||||
// find base class constructor
|
||||
var base = Object.getPrototypeOf(cls.prototype).constructor;
|
||||
if (base === Object) { // "root" node?
|
||||
nodeDataArray.push({ key: k });
|
||||
} else {
|
||||
// add a node for this class and a tree-parent reference to the base class name
|
||||
nodeDataArray.push({ key: k, parent: base.className });
|
||||
}
|
||||
}
|
||||
|
||||
// Create the model for the hierarchy diagram
|
||||
diagram.model = new go.TreeModel(nodeDataArray);
|
||||
|
||||
// Now collect all node data that are singletons
|
||||
var singlesArray = []; // for classes that don't inherit from another class
|
||||
var it = diagram.nodes;
|
||||
while (it.next()) {
|
||||
var node = it.value;
|
||||
if (node.linksConnected.count === 0) {
|
||||
singlesArray.push(node.data);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the unconnected class nodes from the main Diagram
|
||||
for (var i = 0; i < singlesArray.length; i++) {
|
||||
diagram.model.removeNodeData(singlesArray[i]);
|
||||
}
|
||||
|
||||
// Now lay out the diagram as a tree;
|
||||
// separate trees are arranged vertically above each other.
|
||||
diagram.layout = $(go.TreeLayout, { nodeSpacing: 3 });
|
||||
|
||||
// Display the unconnected classes in a separate Diagram
|
||||
var singletons =
|
||||
$(go.Diagram, "mySingletons",
|
||||
{ nodeTemplate: diagram.nodeTemplate, // Share the node template with the main Diagram
|
||||
layout:
|
||||
$(go.GridLayout,
|
||||
{ wrappingColumn: 1, // Put the unconnected nodes in a column
|
||||
spacing: new go.Size(3, 3) }),
|
||||
model: new go.Model(singlesArray) }); // Use a separate model
|
||||
}
|
||||
|
||||
// When a Node is double clicked, open the documentation for the corresponding class in a new window
|
||||
function nodeDoubleClick(event, node) {
|
||||
window.open("../api/symbols/" + node.data.key + ".html");
|
||||
}
|
||||
72
src/test/java/yooze/output/DotPrinterTest.java
Normal file
72
src/test/java/yooze/output/DotPrinterTest.java
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
package yooze.output;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import yooze.ClassCache;
|
||||
import yooze.GraphBuilder;
|
||||
import yooze.application.GraphBuilderFactory;
|
||||
import yooze.domain.Graph;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(locations = "classpath:applicationContext-test.xml")
|
||||
public class DotPrinterTest {
|
||||
|
||||
@Before
|
||||
public void clearClassCache() {
|
||||
ClassCache.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dotPrinting() throws IOException {
|
||||
GraphBuilder directoryBuilder = GraphBuilderFactory.getClassesDirectoryBuilder();
|
||||
directoryBuilder.setPackageExcludePatterns(".*?Class4");
|
||||
directoryBuilder.setPackageIncludePatterns(".*?.Class.");
|
||||
Graph graph = directoryBuilder.build("target/test-classes", "yooze.Class1");
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(500);
|
||||
DotPrinter d = new DotPrinter(bytes);
|
||||
d.print(graph);
|
||||
String dotText = new String(bytes.toByteArray());
|
||||
d.close();
|
||||
String[] expectedDotTextLines = { "digraph \"test-classes\" {", "graph [size=100,100];",
|
||||
"\"yooze.Class1\" [shape=box, height=0.0];", "\"yooze.Class1\" -> \"yooze.Class2\"",
|
||||
"\"yooze.Class2\" [shape=box, height=0.0];", "\"yooze.Class2\" -> \"yooze.Class3\"",
|
||||
"\"yooze.Class3\" [shape=box, height=0.0];", "\"yooze.Class3\" -> \"yooze.Class1\"",
|
||||
"\"yooze.Class1\" [shape=box, height=0.0];", "\"yooze.Class2\" [shape=box, height=0.0];",
|
||||
"\"yooze.Class3\" [shape=box, height=0.0];", "}" };
|
||||
System.out.println(dotText);
|
||||
expectTextContainsLines(dotText, expectedDotTextLines);
|
||||
}
|
||||
|
||||
private void expectTextContainsLines(String dotText, String[] expectedDotTextLines) {
|
||||
for (String line : expectedDotTextLines) {
|
||||
Assert.assertTrue("Not found:" + line, dotText.contains(line));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noReference() throws IOException {
|
||||
GraphBuilder directoryBuilder = GraphBuilderFactory.getClassesDirectoryBuilder();
|
||||
directoryBuilder.setPackageExcludePatterns("");
|
||||
directoryBuilder.setPackageIncludePatterns("yooze.Class4");
|
||||
Graph graph = directoryBuilder.build("target/test-classes", "yooze.Class4");
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(1000);
|
||||
DotPrinter d = new DotPrinter(bytes);
|
||||
d.print(graph);
|
||||
String dotText = new String(bytes.toByteArray());
|
||||
System.out.println(dotText);
|
||||
String[] expectedDotTextLines = { "digraph \"test-classes\" {", "graph [size=100,100];",
|
||||
"\"yooze.Class4\" [shape=box, height=0.0];", "\"yooze.Class4\";", "}" };
|
||||
d.close();
|
||||
expectTextContainsLines(dotText, expectedDotTextLines);
|
||||
}
|
||||
}
|
||||
38
src/test/java/yooze/output/JsonPrinterTest.java
Normal file
38
src/test/java/yooze/output/JsonPrinterTest.java
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
package yooze.output;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import yooze.ClassCache;
|
||||
import yooze.GraphBuilder;
|
||||
import yooze.application.GraphBuilderFactory;
|
||||
import yooze.domain.Graph;
|
||||
|
||||
public class JsonPrinterTest {
|
||||
@Before
|
||||
public void clearClassCache() {
|
||||
ClassCache.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jsonShouldBeWellFormed() throws IOException {
|
||||
GraphBuilder directoryBuilder = GraphBuilderFactory.getClassesDirectoryBuilder();
|
||||
directoryBuilder.setPackageExcludePatterns(".*?Class4");
|
||||
directoryBuilder.setPackageIncludePatterns(".*?.Class.");
|
||||
Graph graph = directoryBuilder.build("target/test-classes", "yooze.Class1");
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(500);
|
||||
GraphPrinter d = new JsonPrinter(bytes);
|
||||
d.print(graph);
|
||||
String json = new String(bytes.toByteArray());
|
||||
d.close();
|
||||
Assert.assertEquals(
|
||||
"[{\"key\":\"yooze.Class2\",\"parent\":\"yooze.Class1\"},{\"key\":\"yooze.Class3\",\"parent\":\"yooze.Class2\"},{\"key\":\"yooze.Class1\""
|
||||
+ ",\"parent\":\"yooze.Class3\"},]", json);
|
||||
}
|
||||
}
|
||||
30
src/test/java/yooze/scanner/ArchiveScannerTest.java
Normal file
30
src/test/java/yooze/scanner/ArchiveScannerTest.java
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
package yooze.scanner;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import yooze.InspectableClasspath;
|
||||
|
||||
public class ArchiveScannerTest {
|
||||
@InjectMocks
|
||||
private ArchiveScanner archiveScanner;
|
||||
|
||||
@Mock
|
||||
private WarScanner warScanner;
|
||||
|
||||
@Before
|
||||
public void createArchiveScanner() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nothing() {
|
||||
List<InspectableClasspath> scanArchive = archiveScanner.scanArchive(".");
|
||||
|
||||
}
|
||||
}
|
||||
48
src/test/java/yooze/scanner/EarScannerTest.java
Normal file
48
src/test/java/yooze/scanner/EarScannerTest.java
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package yooze.scanner;
|
||||
|
||||
import static org.hamcrest.core.Is.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import yooze.Config;
|
||||
import yooze.InspectableClasspath;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(locations = "classpath:applicationContext-test.xml")
|
||||
public class EarScannerTest {
|
||||
|
||||
@Autowired
|
||||
private Config config;
|
||||
|
||||
@Test
|
||||
public void scanner() throws IOException {
|
||||
List<InspectableClasspath> classpaths = new ArchiveScanner(new WarScanner()).scanArchive(config.getEarFile());
|
||||
for (InspectableClasspath path : classpaths) {
|
||||
if (path instanceof DirClassPath) {
|
||||
List<String> classes = ((InspectableClasspath) path).getClasses();
|
||||
assertThat(classes.size(), is(49));
|
||||
}
|
||||
if (path instanceof JarClassPath) {
|
||||
String name = ((InspectableClasspath) path).getResourceName();
|
||||
if (name.contains("standard")) {
|
||||
List<String> classes = ((InspectableClasspath) path).getClasses();
|
||||
assertTrue(classes.contains("org.apache.taglibs.standard.tag.common.sql.DataSourceUtil"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setConfig(Config config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
}
|
||||
19
src/test/java/yooze/scanner/LibScannerTest.java
Normal file
19
src/test/java/yooze/scanner/LibScannerTest.java
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package yooze.scanner;
|
||||
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import yooze.InspectableClasspath;
|
||||
|
||||
public class LibScannerTest {
|
||||
@Test
|
||||
public void directoryWithoutJars_isNotAClasspathEntry() throws IOException {
|
||||
LibScanner libScanner = new LibScanner();
|
||||
List<InspectableClasspath> classpathList = libScanner.scanArchive("src");
|
||||
assertTrue("This should be empty", classpathList.isEmpty());
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue