* removed server instance
* agent now stores data in sqllite table (file) * other small improvements
This commit is contained in:
parent
2070a6328a
commit
a6f481fc43
16 changed files with 163 additions and 635 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -3,3 +3,4 @@
|
||||||
*.iml
|
*.iml
|
||||||
target/
|
target/
|
||||||
ui/node_modules
|
ui/node_modules
|
||||||
|
*.db
|
||||||
6
pom.xml
6
pom.xml
|
|
@ -21,9 +21,9 @@
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.nanohttpd</groupId>
|
<groupId>org.xerial</groupId>
|
||||||
<artifactId>nanohttpd</artifactId>
|
<artifactId>sqlite-jdbc</artifactId>
|
||||||
<version>2.3.1</version>
|
<version>3.28.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package perfix;
|
package perfix;
|
||||||
|
|
||||||
import perfix.instrument.Instrumentor;
|
import perfix.instrument.Instrumentor;
|
||||||
import perfix.server.HTTPServer;
|
|
||||||
|
|
||||||
import java.lang.instrument.Instrumentation;
|
import java.lang.instrument.Instrumentation;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -9,33 +8,33 @@ import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
import static java.util.Arrays.stream;
|
|
||||||
|
|
||||||
public class Agent {
|
public class Agent {
|
||||||
|
|
||||||
|
public static final String DBFILE_PROPERTY = "perfix.db";
|
||||||
|
public static final String DEFAULT_DBFILE = "perfix.db";
|
||||||
private static final String PORT_PROPERTY = "perfix.port";
|
private static final String PORT_PROPERTY = "perfix.port";
|
||||||
private static final String INCLUDES_PROPERTY = "perfix.includes";
|
private static final String INCLUDES_PROPERTY = "perfix.includes";
|
||||||
|
|
||||||
private static final String DEFAULT_PORT = "2048";
|
|
||||||
private static final String MESSAGE = " --- Perfix agent active";
|
private static final String MESSAGE = " --- Perfix agent active";
|
||||||
|
|
||||||
|
|
||||||
public static void premain(String agentArgs, Instrumentation instrumentation) {
|
public static void premain(String agentArgs, Instrumentation instrumentation) {
|
||||||
System.out.println(MESSAGE);
|
System.out.println(MESSAGE);
|
||||||
|
String dbFile = System.getProperty(DBFILE_PROPERTY);
|
||||||
int port = Integer.parseInt(System.getProperty(PORT_PROPERTY, DEFAULT_PORT));
|
if (dbFile == null) {
|
||||||
|
dbFile = DEFAULT_DBFILE;
|
||||||
|
}
|
||||||
|
System.out.println(" --- SQLite file written to " + dbFile);
|
||||||
|
|
||||||
Instrumentor.create(determineIncludes()).instrumentCode(instrumentation);
|
Instrumentor.create(determineIncludes()).instrumentCode(instrumentation);
|
||||||
|
|
||||||
new HTTPServer(port).start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<String> determineIncludes() {
|
private static List<String> determineIncludes() {
|
||||||
String includesPropertyValue = System.getProperty(INCLUDES_PROPERTY);
|
String includesPropertyValue = System.getProperty(INCLUDES_PROPERTY);
|
||||||
if (includesPropertyValue==null){
|
if (includesPropertyValue == null) {
|
||||||
System.out.println("WARNING: perfix.includes not set ");
|
System.out.println("WARNING: perfix.includes not set ");
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
System.out.println(" --- Instrumenting packages: " + includesPropertyValue + ".*");
|
||||||
return new ArrayList<>(asList(includesPropertyValue.split(",")));
|
return new ArrayList<>(asList(includesPropertyValue.split(",")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
package perfix;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* contains start and stop time for method/query/servlet
|
|
||||||
*/
|
|
||||||
public class MethodInvocation {
|
|
||||||
private final long timestamp;
|
|
||||||
long duration;
|
|
||||||
|
|
||||||
MethodInvocation(String name) {
|
|
||||||
timestamp = System.nanoTime();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getDuration() {
|
|
||||||
return duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void registerEndingTime(long t1) {
|
|
||||||
duration = t1 - timestamp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +1,29 @@
|
||||||
package perfix;
|
package perfix;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public class MethodNode {
|
public class MethodNode {
|
||||||
public final String name;
|
private final String name;
|
||||||
public final List<MethodNode> children;
|
private final long timestamp;
|
||||||
public MethodNode parent;
|
private final String threadName;
|
||||||
private MethodInvocation invocation;
|
private MethodNode parent;
|
||||||
|
private long duration;
|
||||||
|
private long invocationid;
|
||||||
|
|
||||||
|
|
||||||
public MethodNode(String name) {
|
public MethodNode(String name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.children = new ArrayList<>();
|
this.timestamp = System.nanoTime();
|
||||||
}
|
this.threadName = Thread.currentThread().getName();
|
||||||
|
|
||||||
public void addChild(MethodNode child) {
|
|
||||||
children.add(child);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<MethodNode> getChildren() {
|
public long getId() {
|
||||||
return children;
|
return Objects.hash(Thread.currentThread().getId(), timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -47,11 +46,47 @@ public class MethodNode {
|
||||||
return Objects.hash(name);
|
return Objects.hash(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MethodInvocation getInvocation() {
|
public long getParentId() {
|
||||||
return invocation;
|
if (parent == null) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return parent.getId();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setInvocation(MethodInvocation invocation) {
|
public long getDuration() {
|
||||||
this.invocation = invocation;
|
return duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerEndingTime(long t1) {
|
||||||
|
duration = t1 - timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MethodNode getParent() {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParent(MethodNode parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getThreadName() {
|
||||||
|
return threadName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInvocationId(long invocationid) {
|
||||||
|
this.invocationid = invocationid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getInvocationId() {
|
||||||
|
if (parent != null) {
|
||||||
|
return parent.getInvocationId();
|
||||||
|
} else {
|
||||||
|
return getId();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,65 @@ package perfix;
|
||||||
|
|
||||||
import perfix.instrument.Util;
|
import perfix.instrument.Util;
|
||||||
|
|
||||||
import java.util.*;
|
import java.sql.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.ConcurrentSkipListMap;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles start and stop of method invocations. Measures method/sql/url invocations. Stores individual measurements in sqlite.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused") //used from instrumented bytecode
|
||||||
public class Registry {
|
public class Registry {
|
||||||
|
|
||||||
private static final List<MethodNode> callstack = new ArrayList<>();
|
|
||||||
private static final ThreadLocal<MethodNode> currentMethod = new ThreadLocal<>();
|
private static final ThreadLocal<MethodNode> currentMethod = new ThreadLocal<>();
|
||||||
|
private static final String INSERT_REPORT = "insert into report (thread, id, parent_id, invocation_id, timestamp, name, duration) values (?,?,?,?,?,?,?)";
|
||||||
|
private static final String CREATE_TABLE = "create table report(thread varchar(255), id int, parent_id int, invocation_id int, timestamp int, name varchar(255), duration integer)";
|
||||||
|
private static final String SElECT_TABLE = "SELECT name FROM sqlite_master WHERE type='table' AND name='report';";
|
||||||
|
|
||||||
@SuppressWarnings("unused") //used in generated code
|
private static BlockingQueue<MethodNode> queue = new LinkedBlockingQueue<>();
|
||||||
public static MethodInvocation startJdbc(String name) {
|
private static Connection connection;
|
||||||
|
|
||||||
|
static {
|
||||||
|
initWorker();
|
||||||
|
initDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void initWorker() {
|
||||||
|
ExecutorService executorService = Executors.newFixedThreadPool(1);
|
||||||
|
executorService.submit(() -> {
|
||||||
|
while (true) {
|
||||||
|
MethodNode methodNode = queue.take();
|
||||||
|
|
||||||
|
store(methodNode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void initDatabase() {
|
||||||
|
try {
|
||||||
|
connection = DriverManager.getConnection("jdbc:sqlite:" + getSqliteFile());
|
||||||
|
Statement statement = connection.createStatement();
|
||||||
|
ResultSet resultSet = statement.executeQuery(SElECT_TABLE);
|
||||||
|
if (!resultSet.next()) {
|
||||||
|
statement.execute(CREATE_TABLE);
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getSqliteFile() {
|
||||||
|
String dbFileName = System.getProperty(Agent.DBFILE_PROPERTY);
|
||||||
|
if (dbFileName == null) {
|
||||||
|
return Agent.DEFAULT_DBFILE;
|
||||||
|
} else {
|
||||||
|
return dbFileName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MethodNode startJdbc(String name) {
|
||||||
if (!Util.isFirstExecutionStarted()) {
|
if (!Util.isFirstExecutionStarted()) {
|
||||||
Util.startExecution();
|
Util.startExecution();
|
||||||
return start(name);
|
return start(name);
|
||||||
|
|
@ -21,72 +69,54 @@ public class Registry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
public static MethodNode start(String name) {
|
||||||
public static MethodInvocation start(String name) {
|
|
||||||
MethodInvocation methodInvocation = new MethodInvocation(name);
|
|
||||||
MethodNode newNode = new MethodNode(name);
|
MethodNode newNode = new MethodNode(name);
|
||||||
|
|
||||||
MethodNode parent = currentMethod.get();
|
MethodNode parent = currentMethod.get();
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
parent.addChild(newNode);
|
newNode.setParent(parent);
|
||||||
newNode.parent = parent;
|
newNode.setInvocationId(parent.getInvocationId());
|
||||||
} else {
|
|
||||||
callstack.add(newNode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
currentMethod.set(newNode);
|
currentMethod.set(newNode);
|
||||||
return methodInvocation;
|
return newNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public static void stopJdbc(MethodInvocation queryInvocation) {
|
public static void stopJdbc() {
|
||||||
if (Util.isFirstExecutionStarted() && queryInvocation != null) {
|
MethodNode current = currentMethod.get();
|
||||||
stop(queryInvocation);
|
if (Util.isFirstExecutionStarted() && current != null) {
|
||||||
|
stop();
|
||||||
Util.endExecution();
|
Util.endExecution();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public static void stop(MethodInvocation methodInvocation) {
|
public static void stop() {
|
||||||
if (methodInvocation != null) {
|
MethodNode current = currentMethod.get();
|
||||||
methodInvocation.registerEndingTime(System.nanoTime());
|
if (current != null) {
|
||||||
|
current.registerEndingTime(System.nanoTime());
|
||||||
|
queue.add(current);
|
||||||
|
currentMethod.set(current.getParent());
|
||||||
}
|
}
|
||||||
MethodNode methodNode = currentMethod.get();
|
|
||||||
methodNode.setInvocation(methodInvocation);
|
|
||||||
|
|
||||||
currentMethod.set(methodNode.parent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SortedMap<Long, Report> sortedMethodsByDuration() {
|
private static void store(MethodNode methodNode) {
|
||||||
//walk the stack to group methods by their name
|
try {
|
||||||
Map<String, List<MethodInvocation>> methods = new ConcurrentHashMap<>();
|
PreparedStatement statement = connection.prepareStatement(INSERT_REPORT);
|
||||||
collectInvocationsPerMethodName(methods, callstack);
|
statement.setString(1, methodNode.getThreadName());
|
||||||
|
statement.setLong(2, methodNode.getId());
|
||||||
//gather invocations by method name and calculate statistics
|
statement.setLong(3, methodNode.getParentId());
|
||||||
SortedMap<Long, Report> sortedByTotal = new ConcurrentSkipListMap<>(Comparator.reverseOrder());
|
statement.setLong(4, methodNode.getInvocationId());
|
||||||
methods.forEach((name, measurements) -> {
|
statement.setLong(5, methodNode.getTimestamp());
|
||||||
long totalDuration = measurements.stream()
|
statement.setString(6, methodNode.getName());
|
||||||
.filter(Objects::nonNull)
|
statement.setLong(7, methodNode.getDuration());
|
||||||
.mapToLong(MethodInvocation::getDuration).sum();
|
statement.executeUpdate();
|
||||||
sortedByTotal.put(totalDuration, new Report(name, measurements.size(), totalDuration));
|
statement.close();
|
||||||
});
|
} catch (Exception e) {
|
||||||
return sortedByTotal;
|
e.printStackTrace();
|
||||||
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void collectInvocationsPerMethodName(Map<String, List<MethodInvocation>> invocations, List<MethodNode> nodes) {
|
|
||||||
nodes.forEach(methodNode -> {
|
|
||||||
invocations.computeIfAbsent(methodNode.getName(), key -> new ArrayList<>()).add(methodNode.getInvocation());
|
|
||||||
collectInvocationsPerMethodName(invocations, methodNode.children);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<MethodNode> getCallStack() {
|
|
||||||
return callstack;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void clear() {
|
|
||||||
callstack.clear();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ public class ClassInstrumentor extends Instrumentor {
|
||||||
ClassInstrumentor(List<String> includes, ClassPool classPool) {
|
ClassInstrumentor(List<String> includes, ClassPool classPool) {
|
||||||
super(includes, classPool);
|
super(includes, classPool);
|
||||||
try {
|
try {
|
||||||
perfixMethodInvocationClass = getCtClass(PERFIX_METHODINVOCATION_CLASS);
|
|
||||||
stringClass = classpool.get(JAVA_STRING);
|
stringClass = classpool.get(JAVA_STRING);
|
||||||
|
|
||||||
} catch (NotFoundException e) {
|
} catch (NotFoundException e) {
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import java.util.List;
|
||||||
public abstract class Instrumentor {
|
public abstract class Instrumentor {
|
||||||
static final String JAVA_STRING = "java.lang.String";
|
static final String JAVA_STRING = "java.lang.String";
|
||||||
static final String JAVA_HASHMAP = "java.util.HashMap";
|
static final String JAVA_HASHMAP = "java.util.HashMap";
|
||||||
static final String PERFIX_METHODINVOCATION_CLASS = "perfix.MethodInvocation";
|
|
||||||
static final String JAVASSIST_FIRST_ARGUMENT_NAME = "$1";
|
static final String JAVASSIST_FIRST_ARGUMENT_NAME = "$1";
|
||||||
static final String JAVASSIST_RETURNVALUE = "$_";
|
static final String JAVASSIST_RETURNVALUE = "$_";
|
||||||
|
|
||||||
|
|
@ -17,14 +16,12 @@ public abstract class Instrumentor {
|
||||||
final List<String> includes;
|
final List<String> includes;
|
||||||
protected CtClass stringClass;
|
protected CtClass stringClass;
|
||||||
protected CtClass hashMapClass;
|
protected CtClass hashMapClass;
|
||||||
protected CtClass perfixMethodInvocationClass;
|
|
||||||
|
|
||||||
Instrumentor(List<String> includes, ClassPool classPool) {
|
Instrumentor(List<String> includes, ClassPool classPool) {
|
||||||
this.includes = includes;
|
this.includes = includes;
|
||||||
this.classpool = classPool;
|
this.classpool = classPool;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
perfixMethodInvocationClass = getCtClass(PERFIX_METHODINVOCATION_CLASS);
|
|
||||||
stringClass = classpool.get(JAVA_STRING);
|
stringClass = classpool.get(JAVA_STRING);
|
||||||
hashMapClass = classPool.get(JAVA_HASHMAP);
|
hashMapClass = classPool.get(JAVA_HASHMAP);
|
||||||
|
|
||||||
|
|
@ -52,9 +49,8 @@ public abstract class Instrumentor {
|
||||||
/* record times at beginning and end of method body*/
|
/* record times at beginning and end of method body*/
|
||||||
void instrumentMethod(CtMethod methodToinstrument, String metricName) {
|
void instrumentMethod(CtMethod methodToinstrument, String metricName) {
|
||||||
try {
|
try {
|
||||||
methodToinstrument.addLocalVariable("_perfixmethod", perfixMethodInvocationClass);
|
methodToinstrument.insertBefore("perfix.Registry.start(" + metricName + ");");
|
||||||
methodToinstrument.insertBefore("_perfixmethod = perfix.Registry.start(" + metricName + ");");
|
methodToinstrument.insertAfter("perfix.Registry.stop();");
|
||||||
methodToinstrument.insertAfter("perfix.Registry.stop(_perfixmethod);");
|
|
||||||
} catch (CannotCompileException e) {
|
} catch (CannotCompileException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
@ -66,9 +62,8 @@ public abstract class Instrumentor {
|
||||||
* (measured) calls if not handled in a way to prevent this */
|
* (measured) calls if not handled in a way to prevent this */
|
||||||
void instrumentJdbcCall(CtMethod methodToinstrument, String metricName) {
|
void instrumentJdbcCall(CtMethod methodToinstrument, String metricName) {
|
||||||
try {
|
try {
|
||||||
methodToinstrument.addLocalVariable("_perfixmethod", perfixMethodInvocationClass);
|
methodToinstrument.insertBefore("perfix.Registry.startJdbc(" + metricName + ");");
|
||||||
methodToinstrument.insertBefore("_perfixmethod = perfix.Registry.startJdbc(" + metricName + ");");
|
methodToinstrument.insertAfter("perfix.Registry.stopJdbc();");
|
||||||
methodToinstrument.insertAfter("perfix.Registry.stopJdbc(_perfixmethod);");
|
|
||||||
} catch (CannotCompileException e) {
|
} catch (CannotCompileException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
@ -77,9 +72,8 @@ public abstract class Instrumentor {
|
||||||
/* record times at beginning and end of method body*/
|
/* record times at beginning and end of method body*/
|
||||||
void instrumentJdbcCall(CtMethod methodToinstrument) {
|
void instrumentJdbcCall(CtMethod methodToinstrument) {
|
||||||
try {
|
try {
|
||||||
methodToinstrument.addLocalVariable("_perfixmethod", perfixMethodInvocationClass);
|
methodToinstrument.insertBefore("perfix.Registry.startJdbc(perfix.instrument.StatementText.toString(_perfixSqlStatement));");
|
||||||
methodToinstrument.insertBefore("_perfixmethod = perfix.Registry.startJdbc(perfix.instrument.StatementText.toString(_perfixSqlStatement));");
|
methodToinstrument.insertAfter("perfix.Registry.stopJdbc();");
|
||||||
methodToinstrument.insertAfter("perfix.Registry.stopJdbc(_perfixmethod);");
|
|
||||||
} catch (CannotCompileException e) {
|
} catch (CannotCompileException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ public class JdbcInstrumentor extends Instrumentor {
|
||||||
try {
|
try {
|
||||||
preparedStatementInterface.getDeclaredMethod("setSqlForPerfix");
|
preparedStatementInterface.getDeclaredMethod("setSqlForPerfix");
|
||||||
} catch (NotFoundException e1) {
|
} catch (NotFoundException e1) {
|
||||||
e1.printStackTrace();
|
// e1.printStackTrace();
|
||||||
try {
|
try {
|
||||||
CtMethod setSqlForPerfix = new CtMethod(CtClass.voidType, "setSqlForPerfix", new CtClass[]{stringClass}, preparedStatementInterface);
|
CtMethod setSqlForPerfix = new CtMethod(CtClass.voidType, "setSqlForPerfix", new CtClass[]{stringClass}, preparedStatementInterface);
|
||||||
preparedStatementInterface.addMethod(setSqlForPerfix);
|
preparedStatementInterface.addMethod(setSqlForPerfix);
|
||||||
|
|
@ -94,7 +94,7 @@ public class JdbcInstrumentor extends Instrumentor {
|
||||||
getDeclaredMethods(preparedStatementClass, "setString", "setObject", "setDate", "setTime", "setTimestamp")
|
getDeclaredMethods(preparedStatementClass, "setString", "setObject", "setDate", "setTime", "setTimestamp")
|
||||||
.forEach(method -> {
|
.forEach(method -> {
|
||||||
try {
|
try {
|
||||||
method.insertBefore("perfix.instrument.StatementText.set(_perfixSqlStatement,$1, \"\'\"+$2+\"\'\");");
|
method.insertBefore("perfix.instrument.StatementText.set(_perfixSqlStatement,$1, \"'\"+$2+\"'\");");
|
||||||
} catch (CannotCompileException e) {
|
} catch (CannotCompileException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
@ -161,7 +161,7 @@ public class JdbcInstrumentor extends Instrumentor {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addPerfixFields(CtClass preparedStatementClass) throws CannotCompileException, NotFoundException {
|
private void addPerfixFields(CtClass preparedStatementClass) throws CannotCompileException {
|
||||||
// add a String field that will contain the statement
|
// add a String field that will contain the statement
|
||||||
CtField perfixSqlField = new CtField(statementTextClass, "_perfixSqlStatement", preparedStatementClass);
|
CtField perfixSqlField = new CtField(statementTextClass, "_perfixSqlStatement", preparedStatementClass);
|
||||||
perfixSqlField.setModifiers(Modifier.PRIVATE);
|
perfixSqlField.setModifiers(Modifier.PRIVATE);
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,10 @@ public class ServletInstrumentor extends Instrumentor {
|
||||||
try {
|
try {
|
||||||
stream(classToInstrument.getDeclaredMethods(JAVA_SERVLET_SERVICE_METHOD)).forEach(methodToInstrument -> {
|
stream(classToInstrument.getDeclaredMethods(JAVA_SERVLET_SERVICE_METHOD)).forEach(methodToInstrument -> {
|
||||||
try {
|
try {
|
||||||
methodToInstrument.addLocalVariable("_perfixmethod", perfixMethodInvocationClass);
|
methodToInstrument.insertBefore("perfix.Registry.start($1.getRequestURI());");
|
||||||
methodToInstrument.insertBefore("_perfixmethod = perfix.Registry.start($1.getRequestURI());");
|
methodToInstrument.insertAfter("perfix.Registry.stop();");
|
||||||
methodToInstrument.insertAfter("perfix.Registry.stop(_perfixmethod);");
|
|
||||||
} catch (CannotCompileException e) {
|
} catch (CannotCompileException e) {
|
||||||
|
// ignore and return uninstrumented bytecode
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return bytecode(classToInstrument);
|
return bytecode(classToInstrument);
|
||||||
|
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
package perfix.server;
|
|
||||||
|
|
||||||
import fi.iki.elonen.NanoHTTPD;
|
|
||||||
import perfix.Registry;
|
|
||||||
import perfix.server.json.Serializer;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
public class HTTPServer extends NanoHTTPD {
|
|
||||||
|
|
||||||
|
|
||||||
public HTTPServer(int port) {
|
|
||||||
super(port);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start() {
|
|
||||||
|
|
||||||
try {
|
|
||||||
start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
|
|
||||||
System.out.println(" --- Perfix http server running. Point your browser to http://localhost:" + getListeningPort() + "/");
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
System.err.println(" --- Couldn't start Perfix http server:\n" + ioe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Response serve(IHTTPSession session) {
|
|
||||||
String uri = session.getUri();
|
|
||||||
switch (uri) {
|
|
||||||
case "/report":
|
|
||||||
return perfixMetrics();
|
|
||||||
case "/callstack":
|
|
||||||
return perfixCallstack();
|
|
||||||
case "/clear":
|
|
||||||
return clear();
|
|
||||||
default:
|
|
||||||
return newFixedLengthResponse(Response.Status.NOT_FOUND, "text/plain", "NOT FOUND");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Response perfixMetrics() {
|
|
||||||
try {
|
|
||||||
return addCors(newFixedLengthResponse(Response.Status.OK, "application/json", Serializer.toJSONString(new ArrayList<>(Registry.sortedMethodsByDuration().values()))));
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return newFixedLengthResponse(e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Response addCors(Response response) {
|
|
||||||
response.addHeader("Access-Control-Allow-Origin", "*");
|
|
||||||
response.addHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Response perfixCallstack() {
|
|
||||||
try {
|
|
||||||
return addCors(newFixedLengthResponse(Response.Status.OK, "application/json", Serializer.toJSONString(Registry.getCallStack())));
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return newFixedLengthResponse(e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Response clear() {
|
|
||||||
Registry.clear();
|
|
||||||
try {
|
|
||||||
return addCors(newFixedLengthResponse(Response.Status.OK, "application/json", Serializer.toJSONString(Registry.getCallStack())));
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return newFixedLengthResponse(e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
package perfix.server.json;
|
|
||||||
|
|
||||||
import java.util.Formatter;
|
|
||||||
|
|
||||||
public abstract class JSONSerializer<T> {
|
|
||||||
protected abstract String handle(T object);
|
|
||||||
|
|
||||||
protected Formatter formatter = new Formatter();
|
|
||||||
|
|
||||||
public String toJSONString(T object) {
|
|
||||||
if (object == null) {
|
|
||||||
return "";
|
|
||||||
} else if (object instanceof Number || object instanceof Boolean) {
|
|
||||||
return "" + object.toString();
|
|
||||||
} else if (object instanceof CharSequence || object instanceof Character) {
|
|
||||||
return "\"" + object.toString() + "\"";
|
|
||||||
} else {
|
|
||||||
return handle(object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
package perfix.server.json;
|
|
||||||
|
|
||||||
public class Serializer {
|
|
||||||
private static SerializerFactory instance = new SynthSerializerFactory();
|
|
||||||
|
|
||||||
public static String toJSONString(boolean b) {
|
|
||||||
return Boolean.toString(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String toJSONString(short s) {
|
|
||||||
return Short.toString(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String toJSONString(int i) {
|
|
||||||
return Integer.toString(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String toJSONString(float f) {
|
|
||||||
return Float.toString(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String toJSONString(double d) {
|
|
||||||
return Double.toString(d);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String toJSONString(long l) {
|
|
||||||
return Long.toString(l);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String toJSONString(char c) {
|
|
||||||
return "\"" + Character.toString(c) + "\"";
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public static <T> String toJSONString(T o) {
|
|
||||||
if (o == null) {
|
|
||||||
return "null";
|
|
||||||
}
|
|
||||||
return instance.createSerializer((Class<T>) o.getClass()).toJSONString(o);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setInstance(SerializerFactory instance) {
|
|
||||||
Serializer.instance = instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
package perfix.server.json;
|
|
||||||
|
|
||||||
@SuppressWarnings("serial")
|
|
||||||
public class SerializerCreationException extends RuntimeException {
|
|
||||||
|
|
||||||
public SerializerCreationException(Throwable t) {
|
|
||||||
super(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
package perfix.server.json;
|
|
||||||
|
|
||||||
public interface SerializerFactory {
|
|
||||||
public <T> JSONSerializer<T> createSerializer(Class<T> beanjavaClass);
|
|
||||||
}
|
|
||||||
|
|
@ -1,350 +0,0 @@
|
||||||
package perfix.server.json;
|
|
||||||
|
|
||||||
import javassist.*;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import static java.util.Arrays.asList;
|
|
||||||
|
|
||||||
public class SynthSerializerFactory implements SerializerFactory {
|
|
||||||
private static final String STRING = "java.lang.String";
|
|
||||||
private static final String BOOLEAN = "java.lang.Boolean";
|
|
||||||
private static final String CHARACTER = "java.lang.Character";
|
|
||||||
private static final String BYTE = "java.lang.Byte";
|
|
||||||
private static final String DOUBLE = "java.lang.Double";
|
|
||||||
private static final String FLOAT = "java.lang.Float";
|
|
||||||
private static final String LONG = "java.lang.Long";
|
|
||||||
private static final String SHORT = "java.lang.Short";
|
|
||||||
private static final String INTEGER = "java.lang.Integer";
|
|
||||||
|
|
||||||
private final static Set<String> wrappersAndString = new HashSet<String>(asList(BOOLEAN, CHARACTER, BYTE, DOUBLE, FLOAT, LONG, SHORT, INTEGER,
|
|
||||||
STRING));
|
|
||||||
|
|
||||||
private static final String COLLECTION = "java.util.Collection";
|
|
||||||
private static final String LIST = "java.util.List";
|
|
||||||
private static final String SET = "java.util.Set";
|
|
||||||
private static final List<String> mapInterfaces = asList("java.util.Map", "java.util.concurrent.ConcurrentHashMap");
|
|
||||||
|
|
||||||
private static final Map<String, JSONSerializer<?>> serializers = new HashMap<>();
|
|
||||||
private static final String ROOT_PACKAGE = "serializer.";
|
|
||||||
|
|
||||||
private final ClassPool pool = ClassPool.getDefault();
|
|
||||||
private final Map<String, CtClass> primitiveWrappers = new HashMap<String, CtClass>();
|
|
||||||
private CtClass serializerBase;
|
|
||||||
|
|
||||||
SynthSerializerFactory() {
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isPrimitiveOrWrapperOrString(CtClass beanClass) {
|
|
||||||
return beanClass.isPrimitive() || wrappersAndString.contains(beanClass.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
void init() {
|
|
||||||
try {
|
|
||||||
serializerBase = pool.get(JSONSerializer.class.getName());
|
|
||||||
|
|
||||||
primitiveWrappers.put("int", pool.get(INTEGER));
|
|
||||||
primitiveWrappers.put("short", pool.get(SHORT));
|
|
||||||
primitiveWrappers.put("byte", pool.get(BYTE));
|
|
||||||
primitiveWrappers.put("long", pool.get(LONG));
|
|
||||||
primitiveWrappers.put("float", pool.get(FLOAT));
|
|
||||||
primitiveWrappers.put("double", pool.get(DOUBLE));
|
|
||||||
primitiveWrappers.put("boolean", pool.get(BOOLEAN));
|
|
||||||
primitiveWrappers.put("char", pool.get(CHARACTER));
|
|
||||||
} catch (NotFoundException e) {
|
|
||||||
throw new SerializerCreationException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> JSONSerializer<T> createSerializer(Class<T> beanjavaClass) {
|
|
||||||
try {
|
|
||||||
CtClass beanClass = pool.get(beanjavaClass.getName());
|
|
||||||
|
|
||||||
return createSerializer(beanClass);
|
|
||||||
} catch (NotFoundException e) {
|
|
||||||
throw new SerializerCreationException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private <T> JSONSerializer<T> createSerializer(CtClass beanClass) {
|
|
||||||
if (serializers.containsKey(createSerializerName(beanClass))) {
|
|
||||||
return (JSONSerializer<T>) serializers.get(createSerializerName(beanClass));
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return tryCreateSerializer(beanClass);
|
|
||||||
} catch (NotFoundException | CannotCompileException | InstantiationException | IllegalAccessException e) {
|
|
||||||
throw new SerializerCreationException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private <T> JSONSerializer<T> tryCreateSerializer(CtClass beanClass) throws NotFoundException, CannotCompileException, InstantiationException,
|
|
||||||
IllegalAccessException {
|
|
||||||
CtClass serializerClass = pool.makeClass(createSerializerName(beanClass), serializerBase);
|
|
||||||
|
|
||||||
addToJsonStringMethod(beanClass, serializerClass);
|
|
||||||
|
|
||||||
JSONSerializer<T> jsonSerializer = createSerializerInstance(serializerClass);
|
|
||||||
|
|
||||||
serializers.put(createSerializerName(beanClass), jsonSerializer);
|
|
||||||
return jsonSerializer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* create method source, compile it and add it to the class under construction
|
|
||||||
*/
|
|
||||||
private void addToJsonStringMethod(CtClass beanClass, CtClass serializerClass) throws NotFoundException, CannotCompileException {
|
|
||||||
String body = createToJSONStringMethodSource(beanClass);
|
|
||||||
serializerClass.addMethod(CtNewMethod.make(body, serializerClass));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Creates the source, handling the for JSON different types of classes
|
|
||||||
*/
|
|
||||||
private String createToJSONStringMethodSource(CtClass beanClass) throws NotFoundException {
|
|
||||||
String source = "public String handle(Object object){\n";
|
|
||||||
if (beanClass.isArray()) {
|
|
||||||
source += "\tObject[] array=(Object[])object;\n";
|
|
||||||
source += handleArray(beanClass);
|
|
||||||
} else if (isCollection(beanClass)) {
|
|
||||||
source += "\tObject[] array=((java.util.Collection)object).toArray();\n";
|
|
||||||
source += handleArray(beanClass);
|
|
||||||
} else if (isMap(beanClass)) {
|
|
||||||
source += handleMap(beanClass);
|
|
||||||
} else if (!isPrimitiveOrWrapperOrString(beanClass)) {
|
|
||||||
List<CtMethod> getters = getGetters(beanClass);
|
|
||||||
if (shouldAddGetterCallers(getters)) {
|
|
||||||
source = addGetterCallers(beanClass, source, getters);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
source += "\treturn \"\";}";
|
|
||||||
}
|
|
||||||
return source;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Any Collection is converted to an array, after which code is generated to handle the single elements.
|
|
||||||
*
|
|
||||||
* A subserializer is created for every single element, but most of the time it will be the same cached instance.
|
|
||||||
*
|
|
||||||
* The generated code fills a StringBuilder. The values are generated by the subserializers
|
|
||||||
*/
|
|
||||||
private String handleArray(CtClass beanClass) {
|
|
||||||
String source = "\tjava.util.StringJoiner result=new java.util.StringJoiner(\",\",\"[\",\"]\");\n";
|
|
||||||
source += "\tfor (int i=0; i<array.length; i++){\n";
|
|
||||||
source += "\t\tresult.add(" + Serializer.class.getName() + ".toJSONString(array[i]));\n";
|
|
||||||
source += "\t};\n\treturn result.toString();\n}";
|
|
||||||
return source;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String handleMap(CtClass beanClass) {
|
|
||||||
String source = "StringBuilder result=new StringBuilder(\"{\");\n";
|
|
||||||
source += "\tfor (java.util.Iterator entries=((java.util.Map)object).entrySet().iterator();entries.hasNext();){\n";
|
|
||||||
source += "\t\tjava.util.Map.Entry entry=(java.util.Map.Entry)entries.next();\n";
|
|
||||||
source += "\t\tresult.append(\"\\\"\"+entry.getKey().toString()+\"\\\"\");\n";
|
|
||||||
source += "\t\tresult.append(\": \");\n";
|
|
||||||
source += "\t\tresult.append(" + Serializer.class.getName() + ".toJSONString(entry.getValue()));\n";
|
|
||||||
source += "\t\tresult.append(\", \");\n";
|
|
||||||
source += "\t};\n";
|
|
||||||
source += "\tresult.setLength(result.length()-2);\n";
|
|
||||||
source += "\tresult.append(\"}\");\n";
|
|
||||||
source += "\treturn result.toString();\n";
|
|
||||||
source += "}";
|
|
||||||
return source;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If the class contains fields for which public getters are available, then these will be called in the generated code.
|
|
||||||
*/
|
|
||||||
private String addGetterCallers(CtClass beanClass, String source, List<CtMethod> getters) throws NotFoundException {
|
|
||||||
int index = 0;
|
|
||||||
source += "\treturn ";
|
|
||||||
source += "\"{";
|
|
||||||
for (CtMethod getter : getters) {
|
|
||||||
source = addPair(beanClass, source, getter);
|
|
||||||
if (index++ < getters.size() - 1) {
|
|
||||||
source += ",";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
source += "}\";\n}";
|
|
||||||
return source;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private <T> JSONSerializer<T> createSerializerInstance(CtClass serializerClass) throws InstantiationException, IllegalAccessException,
|
|
||||||
CannotCompileException {
|
|
||||||
return (JSONSerializer<T>) serializerClass.toClass().newInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* custom root package is prepended to avoid the java.lang class in which it's illegal to create new classes
|
|
||||||
*
|
|
||||||
* Array marks ( '[]' ) are replaced by the 'Array', Otherwise the SerializerClassName would be syntactically incorrect
|
|
||||||
*/
|
|
||||||
public String createSerializerName(CtClass beanClass) {
|
|
||||||
return createSerializerName(beanClass.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
public String createSerializerName(String name) {
|
|
||||||
return ROOT_PACKAGE + name.replaceAll("\\[\\]", "Array") + "Serializer";
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isCollection(CtClass beanClass) throws NotFoundException {
|
|
||||||
List<CtClass> interfaces = new ArrayList<>(asList(beanClass.getInterfaces()));
|
|
||||||
interfaces.add(beanClass);
|
|
||||||
boolean is = interfaces.stream().anyMatch(interfaze -> interfaze.getName().equals(COLLECTION) || interfaze.getName().equals(LIST) || interfaze.getName().equals(SET));
|
|
||||||
return is;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isMap(CtClass beanClass) throws NotFoundException {
|
|
||||||
List<CtClass> interfaces = new ArrayList<>(asList(beanClass.getInterfaces()));
|
|
||||||
interfaces.add(beanClass);
|
|
||||||
return interfaces.stream().anyMatch(i -> mapInterfaces.contains(i.getName()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The JSON vernacular for key:value is pair...
|
|
||||||
*/
|
|
||||||
private String addPair(CtClass classToSerialize, String source, CtMethod getter) throws NotFoundException {
|
|
||||||
source += jsonKey(getter);
|
|
||||||
source += ":";
|
|
||||||
source += jsonValue(classToSerialize, getter);
|
|
||||||
return source;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* derive property key from getter
|
|
||||||
*/
|
|
||||||
private String jsonKey(CtMethod getter) {
|
|
||||||
return "\\\"" + toFieldName(getter.getName()) + "\\\"";
|
|
||||||
}
|
|
||||||
|
|
||||||
private String jsonValue(CtClass classToSerialize, CtMethod getter) throws NotFoundException {
|
|
||||||
String source = "";
|
|
||||||
CtClass returnType = getter.getReturnType();
|
|
||||||
|
|
||||||
/* primitives are wrapped so the produced methods adhere to the JSONSerializer interface */
|
|
||||||
source = createSubSerializerForReturnTypeAndAddInvocationToSource(classToSerialize, getter, source, returnType);
|
|
||||||
|
|
||||||
return source;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String createSubSerializerForReturnTypeAndAddInvocationToSource(CtClass classToSerialize, CtMethod getter, String source, CtClass returnType) {
|
|
||||||
/* NB there does not seem to be auto(un))boxing nor generic types (or other jdk1.5 stuff) in javassist compileable code */
|
|
||||||
|
|
||||||
source += "\"+" + Serializer.class.getName() + ".toJSONString(";
|
|
||||||
|
|
||||||
// cast because of lack of generics
|
|
||||||
source += "(" + cast(regularClassname(classToSerialize.getName())) + "object)." + getter.getName() + "()";
|
|
||||||
|
|
||||||
source += ")+\"";
|
|
||||||
return source;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* turns for example 'getValue' into 'value'
|
|
||||||
*/
|
|
||||||
private String toFieldName(String name) {
|
|
||||||
return name.substring(3, 4).toLowerCase() + (name.length() > 4 ? name.substring(4) : "");
|
|
||||||
}
|
|
||||||
|
|
||||||
public String regularClassname(String name) {
|
|
||||||
return name.replaceAll("\\$", ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
private String cast(String classToSerialize) {
|
|
||||||
return "(" + classToSerialize + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Retrieves getter methods from a class
|
|
||||||
*/
|
|
||||||
private List<CtMethod> getGetters(CtClass beanClass) {
|
|
||||||
List<CtMethod> methods = new ArrayList<CtMethod>();
|
|
||||||
List<CtField> fields = getAllFields(beanClass);
|
|
||||||
for (CtField field : fields) {
|
|
||||||
try {
|
|
||||||
CtMethod method = beanClass.getMethod(getGetterMethod(field), getDescription(field));
|
|
||||||
if (Modifier.isPublic(method.getModifiers())) {
|
|
||||||
methods.add(method);
|
|
||||||
}
|
|
||||||
} catch (NotFoundException n) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return methods;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getGetterMethod(CtField field) {
|
|
||||||
return "get" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<CtField> getAllFields(CtClass beanClass) {
|
|
||||||
try {
|
|
||||||
List<CtField> allfields = new ArrayList<>();
|
|
||||||
for (CtField field : beanClass.getDeclaredFields()) {
|
|
||||||
allfields.add(field);
|
|
||||||
}
|
|
||||||
if (beanClass.getSuperclass() != null) {
|
|
||||||
return getAllFields(beanClass.getSuperclass(), allfields);
|
|
||||||
}
|
|
||||||
return allfields;
|
|
||||||
} catch (NotFoundException e) {
|
|
||||||
throw new SerializerCreationException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<CtField> getAllFields(CtClass beanClass, List<CtField> allfields) {
|
|
||||||
for (CtField field : beanClass.getDeclaredFields()) {
|
|
||||||
allfields.add(field);
|
|
||||||
}
|
|
||||||
|
|
||||||
return allfields;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* is getter list is not empty then callers should be added
|
|
||||||
*/
|
|
||||||
boolean shouldAddGetterCallers(List<CtMethod> getters) {
|
|
||||||
return !getters.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
String getDescription(CtField field) throws NotFoundException {
|
|
||||||
if (field.getType().isArray()) {
|
|
||||||
return "()[" + innerClassName(field.getType().getName()) + ";";
|
|
||||||
} else if (!field.getType().isPrimitive()) {
|
|
||||||
return "()" + innerClassName(field.getType().getName()) + ";";
|
|
||||||
} else {
|
|
||||||
|
|
||||||
return "()" + asPrimitive(field.getType().getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String asPrimitive(String name) {
|
|
||||||
switch (name) {
|
|
||||||
case "int":
|
|
||||||
return "I";
|
|
||||||
case "byte":
|
|
||||||
return "B";
|
|
||||||
case "float":
|
|
||||||
return "F";
|
|
||||||
case "long":
|
|
||||||
return "J";
|
|
||||||
case "boolean":
|
|
||||||
return "Z";
|
|
||||||
case "char":
|
|
||||||
return "C";
|
|
||||||
case "double":
|
|
||||||
return "D";
|
|
||||||
case "short":
|
|
||||||
return "S";
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
String innerClassName(String name) {
|
|
||||||
return "L" + name.replaceAll("\\.", "/").replaceAll("\\[\\]", "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Reference in a new issue