Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 62 additions & 29 deletions src/main/java/com/appland/appmap/output/v1/CodeObject.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
package com.appland.appmap.output.v1;

import com.alibaba.fastjson.annotation.JSONField;

import com.appland.appmap.util.Logger;

import javassist.CtBehavior;
import javassist.CtClass;
import javassist.bytecode.SourceFileAttribute;

import java.lang.reflect.Modifier;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Collections;
import java.util.*;
import java.util.regex.Matcher;

/**
* Represents a package, class or method.
Expand Down Expand Up @@ -79,7 +75,7 @@ public CodeObject setLocation(String location) {
public List<CodeObject> getChildren() {
return this.children;
}

/**
* Return the list of children, or an empty list if there are
* none. {@code getChildren} behaves differently to satisfy
Expand All @@ -100,15 +96,15 @@ private CodeObject setFile(String file) {
this.file = file;
return this;
}

@JSONField(serialize = false, deserialize = false)
private Integer lineno;
private CodeObject setLineno(Integer lineno) {
this.lineno = lineno;
return this;
}


@Override
public boolean equals(Object obj) {
if (obj == null) {
Expand All @@ -129,7 +125,7 @@ public boolean equals(Object obj) {
&& codeObject.isStatic == this.isStatic
&& (codeObject.file == null? this.file == null : codeObject.file.equals(this.file))
&& codeObject.lineno == this.lineno;

}

/**
Expand Down Expand Up @@ -194,12 +190,39 @@ public CodeObject(CodeObject src) {
* @return An estimated source file path
*/
public static String getSourceFilePath(CtClass classType) {
String[] parts = {
"src", "main", "java",
classType.getPackageName().replace('.', '/'),
classType.getClassFile().getSourceFile()
};
return String.join("/", parts);
String sourceFilePath = getSourceFilePathWithDebugInfo(classType);

if (sourceFilePath == null) {
sourceFilePath = guessSourceFilePath(classType);
}

return sourceFilePath;
}

private static String getSourceFilePathWithDebugInfo(CtClass classType) {
final String sourceFile = classType.getClassFile2().getSourceFile();
if (sourceFile == null) {
return null;
}

final List<String> tokens = new ArrayList<>();
final String packageName = classType.getPackageName();

if (packageName != null) {
for(String token : packageName.split("\\.")) {
tokens.add(token);
}
}

tokens.add(sourceFile);
return String.join("/", tokens);
}

private static String guessSourceFilePath(CtClass classType) {
return classType
.getName()
.replaceAll("\\.", Matcher.quoteReplacement("/"))
.replaceAll("\\$\\w+", "") + ".java";
}

/**
Expand All @@ -209,7 +232,13 @@ public static String getSourceFilePath(CtClass classType) {
* @return The root CodeObject
*/
public static CodeObject createTree(String packageName) {
String[] packageTokens = packageName.split("\\.");
final List<String> packageTokens = new ArrayList();
if (packageName != null) {
for (String token : packageName.split("\\.")) {
packageTokens.add(token);
}
}

CodeObject rootObject = null;
CodeObject previousObject = null;

Expand Down Expand Up @@ -241,15 +270,19 @@ public static CodeObject createTree(String packageName) {
*/
public static CodeObject createTree(CtClass classType) {
String packageName = classType.getPackageName();
CodeObject classObj = new CodeObject(classType);
CodeObject rootObject = CodeObject.createTree(packageName);
CodeObject pkgLeafObject = rootObject.get(packageName);
if (pkgLeafObject == null) {
Logger.println("failed to get leaf pkg object for package " + packageName);
return null;
}
if (rootObject == null) {
rootObject = classObj;
} else {
CodeObject pkgLeafObject = rootObject.get(packageName);
if (pkgLeafObject == null) {
Logger.println("failed to get leaf pkg object for package " + packageName);
return null;
}

CodeObject classObj = new CodeObject(classType);
pkgLeafObject.addChild(classObj);
pkgLeafObject.addChild(classObj);
}

return rootObject;
}
Expand Down Expand Up @@ -321,7 +354,7 @@ public CodeObject get(String path) {
public CodeObject findChild(String name, Boolean isStatic, int lineNumber) {
for (CodeObject child : this.safeGetChildren()) {
if (child.name.equals(name)
&& child.isStatic == isStatic
&& child.isStatic == isStatic
&& child.lineno == lineNumber) {
return child;
}
Expand Down Expand Up @@ -357,7 +390,7 @@ private boolean equalBySubstring(String s1, String s2, int start, int end) {

return true;
}

public CodeObject findChildBySubstring(String name, int start, int end) {
for (CodeObject child : this.safeGetChildren()) {
if (equalBySubstring(child.name, name, start, end)) {
Expand All @@ -367,7 +400,7 @@ public CodeObject findChildBySubstring(String name, int start, int end) {

return null;
}

/**
* Add an immediate child to this CodeObject.
* @param child The child to be added
Expand Down
91 changes: 91 additions & 0 deletions src/test/java/com/appland/appmap/output/v1/CodeObjectTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.appland.appmap.output.v1;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import org.junit.Test;

import com.appland.appmap.output.v1.CodeObject;
import com.appland.appmap.output.v1.Event;
import com.appland.appmap.test.util.ClassBuilder;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;

import java.util.List;

public class CodeObjectTest {

/**
* Validate the objects in a CodeObject tree match the given names and types
* @param obj The root object
* @param names The names of the objects, beginning with the root
* @param types The types of the objects, beginning with the root
*/
private void validateCodeObjectTree(CodeObject obj, String[] names, String[] types) {
CodeObject currentObject = obj;
assertTrue(currentObject != null);

for (int i = 0; currentObject != null; ++i) {
assertEquals(currentObject.name, names[i]);
assertEquals(currentObject.type, types[i]);

List<CodeObject> children = currentObject.getChildren();
if (children != null && children.size() > 0) {
currentObject = children.get(0);
} else {
currentObject = null;
}
}
}

@Test
public void testGetSourceFilePath() {
CtClass testClass = new ClassBuilder("testGetSourceFilePath1").ctClass();
assertEquals(CodeObject.getSourceFilePath(testClass), "testGetSourceFilePath1.java");

testClass = new ClassBuilder("com.myorg.testGetSourceFilePath2").ctClass();
assertEquals(CodeObject.getSourceFilePath(testClass), "com/myorg/testGetSourceFilePath2.java");

// It shouldn't be possible to hit this case in the wild, but make sure it
// doesn't raise an exception
testClass = new ClassBuilder("").ctClass();
assertEquals(CodeObject.getSourceFilePath(testClass), ".java");
}

@Test
public void testCreateTree() {
CtClass testClass = new ClassBuilder("testCreateTree").ctClass();
validateCodeObjectTree(
CodeObject.createTree(testClass),
new String[] { "testCreateTree" },
new String[] { "class" }
);

testClass = new ClassBuilder("com.myorg.testCreateTree").ctClass();
validateCodeObjectTree(
CodeObject.createTree(testClass),
new String[] { "com", "myorg", "testCreateTree" },
new String[] { "package", "package", "class" }
);
}

@Test
public void getSourceFilePathForRegularClass() throws NotFoundException {
CtClass testCtClass = ClassPool.getDefault().get("com.appland.appmap.ExampleClass");
assertEquals("com/appland/appmap/ExampleClass.java", CodeObject.getSourceFilePath(testCtClass));
}

@Test
public void getSourceFilePath_for_InnerClass_ResultInBaseClass() throws NotFoundException {
CtClass testCtClass = ClassPool.getDefault().get("com.appland.appmap.output.v1.testclasses.ExampleInnerClass$StaticFinalInnerClass");
assertEquals("com/appland/appmap/output/v1/testclasses/ExampleInnerClass.java", CodeObject.getSourceFilePath(testCtClass));
}

@Test
public void getSourceFilePath_for_AnonymousClass_ResultInBaseClass() throws NotFoundException {
CtClass testCtClass = ClassPool.getDefault().get("com.appland.appmap.output.v1.testclasses.Anonymous$1");
assertEquals("com/appland/appmap/output/v1/testclasses/Anonymous.java", CodeObject.getSourceFilePath(testCtClass));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.appland.appmap.output.v1.testclasses;

public class Anonymous {

public static Runnable getAnonymousImpl(){
return new Runnable() {
@Override
public void run() {
System.err.println("Hello Anonymous!");
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.appland.appmap.output.v1.testclasses;

import com.appland.appmap.ExampleClass;

public class ExampleInnerClass extends ExampleClass {

private static final class StaticFinalInnerClass {

}

}