From 766da3d665d2fca57af0b67b4a711abe1527e22f Mon Sep 17 00:00:00 2001 From: snafu Date: Sat, 7 Dec 2013 12:50:04 +0100 Subject: [PATCH] more tests --- .gitignore | 5 + src/main/java/yooze/ClassNotFound.java | 14 +++ src/main/java/yooze/InspectableClasspath.java | 11 ++ src/main/java/yooze/Scanner.java | 11 ++ .../application/GraphBuilderFactory.java | 40 +++++++ src/main/java/yooze/application/Neo4jDao.java | 89 +++++++++++++++ src/main/java/yooze/application/Yooze.java | 41 +++++++ .../java/yooze/domain/MethodNotFound.java | 14 +++ src/main/java/yooze/output/DotPrinter.java | 49 ++++++++ src/main/java/yooze/output/GraphPrinter.java | 38 ++++++ src/main/java/yooze/output/JsonPrinter.java | 34 ++++++ .../java/yooze/scanner/ClassesDirScanner.java | 28 +++++ .../java/yooze/scanner/ClasspathAdder.java | 18 +++ src/main/java/yooze/scanner/DirClassPath.java | 90 +++++++++++++++ src/main/java/yooze/scanner/JarClassPath.java | 78 +++++++++++++ src/main/java/yooze/scanner/WarScanner.java | 108 ++++++++++++++++++ src/main/resources/go.js | 86 ++++++++++++++ .../java/yooze/output/DotPrinterTest.java | 72 ++++++++++++ .../java/yooze/output/JsonPrinterTest.java | 38 ++++++ .../yooze/scanner/ArchiveScannerTest.java | 30 +++++ .../java/yooze/scanner/EarScannerTest.java | 48 ++++++++ .../java/yooze/scanner/LibScannerTest.java | 19 +++ 22 files changed, 961 insertions(+) create mode 100644 .gitignore create mode 100644 src/main/java/yooze/ClassNotFound.java create mode 100644 src/main/java/yooze/InspectableClasspath.java create mode 100644 src/main/java/yooze/Scanner.java create mode 100644 src/main/java/yooze/application/GraphBuilderFactory.java create mode 100644 src/main/java/yooze/application/Neo4jDao.java create mode 100644 src/main/java/yooze/application/Yooze.java create mode 100644 src/main/java/yooze/domain/MethodNotFound.java create mode 100644 src/main/java/yooze/output/DotPrinter.java create mode 100644 src/main/java/yooze/output/GraphPrinter.java create mode 100644 src/main/java/yooze/output/JsonPrinter.java create mode 100644 src/main/java/yooze/scanner/ClassesDirScanner.java create mode 100644 src/main/java/yooze/scanner/ClasspathAdder.java create mode 100644 src/main/java/yooze/scanner/DirClassPath.java create mode 100644 src/main/java/yooze/scanner/JarClassPath.java create mode 100644 src/main/java/yooze/scanner/WarScanner.java create mode 100644 src/main/resources/go.js create mode 100644 src/test/java/yooze/output/DotPrinterTest.java create mode 100644 src/test/java/yooze/output/JsonPrinterTest.java create mode 100644 src/test/java/yooze/scanner/ArchiveScannerTest.java create mode 100644 src/test/java/yooze/scanner/EarScannerTest.java create mode 100644 src/test/java/yooze/scanner/LibScannerTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b229b07 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.settings +*~ +target diff --git a/src/main/java/yooze/ClassNotFound.java b/src/main/java/yooze/ClassNotFound.java new file mode 100644 index 0000000..78ef518 --- /dev/null +++ b/src/main/java/yooze/ClassNotFound.java @@ -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); + } + +} \ No newline at end of file diff --git a/src/main/java/yooze/InspectableClasspath.java b/src/main/java/yooze/InspectableClasspath.java new file mode 100644 index 0000000..e9694b3 --- /dev/null +++ b/src/main/java/yooze/InspectableClasspath.java @@ -0,0 +1,11 @@ +package yooze; + +import java.util.List; + +import javassist.ClassPath; + +public interface InspectableClasspath extends ClassPath { + public String getResourceName(); + + public List getClasses(); +} diff --git a/src/main/java/yooze/Scanner.java b/src/main/java/yooze/Scanner.java new file mode 100644 index 0000000..746a907 --- /dev/null +++ b/src/main/java/yooze/Scanner.java @@ -0,0 +1,11 @@ +package yooze; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +public interface Scanner { + public List scanArchive(String archiveName) throws IOException; + + public List scanArchive(File file) throws IOException; +} diff --git a/src/main/java/yooze/application/GraphBuilderFactory.java b/src/main/java/yooze/application/GraphBuilderFactory.java new file mode 100644 index 0000000..d2cf42a --- /dev/null +++ b/src/main/java/yooze/application/GraphBuilderFactory.java @@ -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()); + } +} diff --git a/src/main/java/yooze/application/Neo4jDao.java b/src/main/java/yooze/application/Neo4jDao.java new file mode 100644 index 0000000..302481d --- /dev/null +++ b/src/main/java/yooze/application/Neo4jDao.java @@ -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 nodes = new ConcurrentHashMap(); + + 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 + + } +} diff --git a/src/main/java/yooze/application/Yooze.java b/src/main/java/yooze/application/Yooze.java new file mode 100644 index 0000000..324f739 --- /dev/null +++ b/src/main/java/yooze/application/Yooze.java @@ -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); + } + } + +} diff --git a/src/main/java/yooze/domain/MethodNotFound.java b/src/main/java/yooze/domain/MethodNotFound.java new file mode 100644 index 0000000..b22ba7e --- /dev/null +++ b/src/main/java/yooze/domain/MethodNotFound.java @@ -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); + } + +} \ No newline at end of file diff --git a/src/main/java/yooze/output/DotPrinter.java b/src/main/java/yooze/output/DotPrinter.java new file mode 100644 index 0000000..e92ddff --- /dev/null +++ b/src/main/java/yooze/output/DotPrinter.java @@ -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 + } + } + } + } +} diff --git a/src/main/java/yooze/output/GraphPrinter.java b/src/main/java/yooze/output/GraphPrinter.java new file mode 100644 index 0000000..6e3ba49 --- /dev/null +++ b/src/main/java/yooze/output/GraphPrinter.java @@ -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 printedRelations; + + public GraphPrinter(OutputStream out) { + super(out); + printedRelations = new ArrayList(); + } + + 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); + } + } +} diff --git a/src/main/java/yooze/output/JsonPrinter.java b/src/main/java/yooze/output/JsonPrinter.java new file mode 100644 index 0000000..f220ec4 --- /dev/null +++ b/src/main/java/yooze/output/JsonPrinter.java @@ -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 + } + } + } + +} diff --git a/src/main/java/yooze/scanner/ClassesDirScanner.java b/src/main/java/yooze/scanner/ClassesDirScanner.java new file mode 100644 index 0000000..2f67cb7 --- /dev/null +++ b/src/main/java/yooze/scanner/ClassesDirScanner.java @@ -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 scanArchive(String archiveName) throws IOException { + return scanArchive(new File(archiveName)); + } + + @Override + public List scanArchive(File file) throws IOException { + List result = new ArrayList(); + result.add(new DirClassPath(file)); + return result; + } + +} diff --git a/src/main/java/yooze/scanner/ClasspathAdder.java b/src/main/java/yooze/scanner/ClasspathAdder.java new file mode 100644 index 0000000..eb6fd4f --- /dev/null +++ b/src/main/java/yooze/scanner/ClasspathAdder.java @@ -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 classpaths, + JarEntry entry) throws IOException { + File jarFile = Util.extractFile(warfile, entry); + classpaths.add(new JarClassPath(new JarFile(jarFile))); + } +} diff --git a/src/main/java/yooze/scanner/DirClassPath.java b/src/main/java/yooze/scanner/DirClassPath.java new file mode 100644 index 0000000..1c21e4c --- /dev/null +++ b/src/main/java/yooze/scanner/DirClassPath.java @@ -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 getClasses() { + List classes = new ArrayList(); + getClasses(dir, dir, classes); + return classes; + } + + private void getClasses(File root, File dir, List 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); + } + } +} diff --git a/src/main/java/yooze/scanner/JarClassPath.java b/src/main/java/yooze/scanner/JarClassPath.java new file mode 100644 index 0000000..af38763 --- /dev/null +++ b/src/main/java/yooze/scanner/JarClassPath.java @@ -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 getClasses() { + List classes = new ArrayList(); + for (Enumeration 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); + } + } + +} diff --git a/src/main/java/yooze/scanner/WarScanner.java b/src/main/java/yooze/scanner/WarScanner.java new file mode 100644 index 0000000..f907a5e --- /dev/null +++ b/src/main/java/yooze/scanner/WarScanner.java @@ -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 scanWars(List wars) { + List classpaths = new ArrayList(); + for (JarFile war : wars) { + classpaths.addAll(scanWar(war)); + } + return classpaths; + } + + private List scanWar(JarFile warfile) { + List classpaths = new ArrayList(); + File classesDir = createTempLocation(classpaths); + classpaths.add(new DirClassPath(classesDir)); + for (Enumeration 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 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()); + } + } + +} diff --git a/src/main/resources/go.js b/src/main/resources/go.js new file mode 100644 index 0000000..8c0c564 --- /dev/null +++ b/src/main/resources/go.js @@ -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"); + } diff --git a/src/test/java/yooze/output/DotPrinterTest.java b/src/test/java/yooze/output/DotPrinterTest.java new file mode 100644 index 0000000..d5b3827 --- /dev/null +++ b/src/test/java/yooze/output/DotPrinterTest.java @@ -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); + } +} diff --git a/src/test/java/yooze/output/JsonPrinterTest.java b/src/test/java/yooze/output/JsonPrinterTest.java new file mode 100644 index 0000000..1d3f4b4 --- /dev/null +++ b/src/test/java/yooze/output/JsonPrinterTest.java @@ -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); + } +} diff --git a/src/test/java/yooze/scanner/ArchiveScannerTest.java b/src/test/java/yooze/scanner/ArchiveScannerTest.java new file mode 100644 index 0000000..29f6304 --- /dev/null +++ b/src/test/java/yooze/scanner/ArchiveScannerTest.java @@ -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 scanArchive = archiveScanner.scanArchive("."); + + } +} diff --git a/src/test/java/yooze/scanner/EarScannerTest.java b/src/test/java/yooze/scanner/EarScannerTest.java new file mode 100644 index 0000000..a740c1b --- /dev/null +++ b/src/test/java/yooze/scanner/EarScannerTest.java @@ -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 classpaths = new ArchiveScanner(new WarScanner()).scanArchive(config.getEarFile()); + for (InspectableClasspath path : classpaths) { + if (path instanceof DirClassPath) { + List classes = ((InspectableClasspath) path).getClasses(); + assertThat(classes.size(), is(49)); + } + if (path instanceof JarClassPath) { + String name = ((InspectableClasspath) path).getResourceName(); + if (name.contains("standard")) { + List classes = ((InspectableClasspath) path).getClasses(); + assertTrue(classes.contains("org.apache.taglibs.standard.tag.common.sql.DataSourceUtil")); + } + } + } + } + + public void setConfig(Config config) { + this.config = config; + } + +} diff --git a/src/test/java/yooze/scanner/LibScannerTest.java b/src/test/java/yooze/scanner/LibScannerTest.java new file mode 100644 index 0000000..01ce1a8 --- /dev/null +++ b/src/test/java/yooze/scanner/LibScannerTest.java @@ -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 classpathList = libScanner.scanArchive("src"); + assertTrue("This should be empty", classpathList.isEmpty()); + } +}