java-callgraph

Details

pom.xml 18(+18 -0)

diff --git a/pom.xml b/pom.xml
index 0992d00..3b51020 100644
--- a/pom.xml
+++ b/pom.xml
@@ -26,6 +26,24 @@
       <version>3.12.1.GA</version>
       <scope>provided</scope>
     </dependency>
+    <dependency>
+        <groupId>io.cucumber</groupId>
+        <artifactId>cucumber-java</artifactId>
+        <version>2.3.1</version>
+        <scope>test</scope>
+    </dependency>
+    <dependency>
+        <groupId>io.cucumber</groupId>
+        <artifactId>cucumber-junit</artifactId>
+        <version>2.3.1</version>
+        <scope>test</scope>
+    </dependency>
+    <dependency>
+        <groupId>junit</groupId>
+        <artifactId>junit</artifactId>
+        <version>4.12</version>
+        <scope>test</scope>
+    </dependency>
   </dependencies>
   <build>
     <plugins>
diff --git a/src/test/java/gr/gousiosg/javacg/JARBuilder.java b/src/test/java/gr/gousiosg/javacg/JARBuilder.java
new file mode 100644
index 0000000..327c55b
--- /dev/null
+++ b/src/test/java/gr/gousiosg/javacg/JARBuilder.java
@@ -0,0 +1,117 @@
+package gr.gousiosg.javacg;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+
+import javax.tools.Diagnostic;
+import javax.tools.DiagnosticCollector;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaCompiler.CompilationTask;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.StandardLocation;
+import javax.tools.ToolProvider;
+
+public class JARBuilder {
+	private static final String TEMP_DIR = System.getProperty("java.io.tmpdir");
+
+	private final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+	private final DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
+	private final StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
+	private final LinkedList<JavaFileObject> compilationUnits = new LinkedList<>();
+	private final Collection<File> classFiles = new LinkedList<>();
+
+	public JARBuilder() throws IOException {
+		fileManager.setLocation(StandardLocation.CLASS_OUTPUT, Arrays.asList(new File(TEMP_DIR)));
+	}
+
+	public void add(String className, String classCode) throws IOException {
+		compilationUnits.add(createJavaFile(className, classCode));
+		classFiles.add(new File(TEMP_DIR, className + ".class"));
+	}
+
+	public File build() throws FileNotFoundException, IOException {
+		CompilationTask task = compiler.getTask(null, fileManager, diagnostics, null, null, compilationUnits);
+		boolean success = task.call();
+		if (!success) {
+			displayDiagnostic(diagnostics);
+			throw new RuntimeException("Cannot compile classes for the JAR");
+		}
+
+		File file = File.createTempFile("test", ".jar");
+		JarOutputStream jar = new JarOutputStream(new FileOutputStream(file), createManifest());
+		for (File classFile : classFiles) {
+			add(classFile, jar);
+		}
+		jar.close();
+		return file;
+	}
+
+	private void displayDiagnostic(DiagnosticCollector<JavaFileObject> diagnostics) {
+		for (Diagnostic<?> diagnostic : diagnostics.getDiagnostics()) {
+			JavaSourceFromString sourceClass = (JavaSourceFromString) diagnostic.getSource();
+			System.err.println("-----");
+			System.err.println("Source: " + sourceClass.getName());
+			System.err.println("Message: " + diagnostic.getMessage(null));
+			System.err.println("Position: " + diagnostic.getPosition());
+			System.err.println(diagnostic.getKind() + " " + diagnostic.getCode());
+		}
+	}
+
+	private JavaFileObject createJavaFile(String className, String classCode) throws IOException {
+		StringWriter writer = new StringWriter();
+		writer.append(classCode);
+		writer.close();
+		JavaFileObject file = new JavaSourceFromString(className, writer.toString());
+		return file;
+	}
+
+	private Manifest createManifest() {
+		Manifest manifest = new Manifest();
+		manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
+		return manifest;
+	}
+
+	private void add(File classFile, JarOutputStream jar) throws IOException {
+		JarEntry entry = new JarEntry(classFile.getPath().replace("\\", "/"));
+		jar.putNextEntry(entry);
+		try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(classFile))) {
+			byte[] buffer = new byte[1024];
+			while (true) {
+				int count = in.read(buffer);
+				if (count == -1)
+					break;
+				jar.write(buffer, 0, count);
+			}
+			jar.closeEntry();
+		}
+	}
+}
+
+class JavaSourceFromString extends SimpleJavaFileObject {
+	final String code;
+
+	JavaSourceFromString(String name, String code) throws IOException {
+		super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
+		this.code = code;
+	}
+
+	@Override
+	public CharSequence getCharContent(boolean ignoreEncodingErrors) {
+		return code;
+	}
+}
\ No newline at end of file
diff --git a/src/test/java/gr/gousiosg/javacg/RunCucumberTest.java b/src/test/java/gr/gousiosg/javacg/RunCucumberTest.java
new file mode 100644
index 0000000..e99e65a
--- /dev/null
+++ b/src/test/java/gr/gousiosg/javacg/RunCucumberTest.java
@@ -0,0 +1,10 @@
+package gr.gousiosg.javacg;
+
+import cucumber.api.CucumberOptions;
+import cucumber.api.junit.Cucumber;
+import org.junit.runner.RunWith;
+
+@RunWith(Cucumber.class)
+@CucumberOptions(plugin = { "pretty" })
+public class RunCucumberTest {
+}
\ No newline at end of file
diff --git a/src/test/java/gr/gousiosg/javacg/StepDefinitions.java b/src/test/java/gr/gousiosg/javacg/StepDefinitions.java
new file mode 100644
index 0000000..281c1c1
--- /dev/null
+++ b/src/test/java/gr/gousiosg/javacg/StepDefinitions.java
@@ -0,0 +1,48 @@
+package gr.gousiosg.javacg;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+
+import cucumber.api.java.en.Given;
+import cucumber.api.java.en.Then;
+import cucumber.api.java.en.When;
+import gr.gousiosg.javacg.stat.JCallGraph;
+
+public class StepDefinitions {
+	private final JARBuilder jarBuilder;
+	private String result;
+
+	public StepDefinitions() throws IOException {
+		jarBuilder = new JARBuilder();
+	}
+
+	@Given("^I have the class \"([^\"]*)\" with code:$")
+	public void i_have_the_class_with_code(String className, String classCode) throws Exception {
+		jarBuilder.add(className, classCode);
+	}
+
+	@When("^I run the analyze$")
+	public void i_analyze_it() throws Exception {
+		File jarFile = jarBuilder.build();
+
+		PrintStream oldOut = System.out;
+		ByteArrayOutputStream resultBuffer = new ByteArrayOutputStream();
+		System.setOut(new PrintStream(resultBuffer));
+		JCallGraph.main(new String[] { jarFile.getPath() });
+		System.setOut(oldOut);
+
+		result = resultBuffer.toString();
+	}
+
+	@Then("^the result should contain:$")
+	public void the_result_should_contain(String line) throws Exception {
+		if (result.contains(line)) {
+			// OK
+		} else {
+			System.err.println(result);
+			throw new RuntimeException("Cannot found: " + line);
+		}
+	}
+}
diff --git a/src/test/resources/gr/gousiosg/javacg/lambda.feature b/src/test/resources/gr/gousiosg/javacg/lambda.feature
new file mode 100644
index 0000000..14d8f3f
--- /dev/null
+++ b/src/test/resources/gr/gousiosg/javacg/lambda.feature
@@ -0,0 +1,37 @@
+#Author: matthieu.vergne@gmail.com
+Feature: Lambda
+  I want to identify all lambdas within the analyzed code.
+
+  Background: 
+    # Introduce the lambda we will use
+    Given I have the class "Runner" with code:
+      """
+      @FunctionalInterface
+      public interface Runner {
+       public void run();
+      }
+      """
+
+  Scenario: Retrieve lambda in method
+    Given I have the class "LambdaTest" with code:
+      """
+      public class LambdaTest {
+       public void methodA() {
+        Runner r = () -> methodB();
+        r.run();
+       }
+       
+       public void methodB() {}
+      }
+      """
+    When I run the analyze
+    # Creation of r in methodA
+    Then the result should contain:
+      """
+      M:LambdaTest:methodA() (D)Runner:run(LambdaTest)
+      """
+    # Call of methodB in r
+    And the result should contain:
+      """
+      M:LambdaTest:lambda$methodA$0() (M)LambdaTest:methodB()
+      """