Revert "* removed server instance * agent now stores data in sqllite table (file) * other small improvements"
This reverts commit a6f481fc
This commit is contained in:
parent
5a2ee4e7ec
commit
338e69e55d
16 changed files with 635 additions and 163 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -2,5 +2,4 @@
|
||||||
.idea/
|
.idea/
|
||||||
*.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.xerial</groupId>
|
<groupId>org.nanohttpd</groupId>
|
||||||
<artifactId>sqlite-jdbc</artifactId>
|
<artifactId>nanohttpd</artifactId>
|
||||||
<version>3.28.0</version>
|
<version>2.3.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
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;
|
||||||
|
|
@ -8,33 +9,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);
|
|
||||||
if (dbFile == null) {
|
int port = Integer.parseInt(System.getProperty(PORT_PROPERTY, DEFAULT_PORT));
|
||||||
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(",")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
22
src/main/java/perfix/MethodInvocation.java
Normal file
22
src/main/java/perfix/MethodInvocation.java
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
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,29 +1,30 @@
|
||||||
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 {
|
||||||
private final String name;
|
public final String name;
|
||||||
private final long timestamp;
|
public final List<MethodNode> children;
|
||||||
private final String threadName;
|
public MethodNode parent;
|
||||||
private MethodNode parent;
|
private MethodInvocation invocation;
|
||||||
private long duration;
|
|
||||||
private long invocationid;
|
|
||||||
|
|
||||||
|
|
||||||
public MethodNode(String name) {
|
public MethodNode(String name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.timestamp = System.nanoTime();
|
this.children = new ArrayList<>();
|
||||||
this.threadName = Thread.currentThread().getName();
|
}
|
||||||
|
|
||||||
|
public void addChild(MethodNode child) {
|
||||||
|
children.add(child);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getId() {
|
public List<MethodNode> getChildren() {
|
||||||
return Objects.hash(Thread.currentThread().getId(), timestamp);
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -46,47 +47,11 @@ public class MethodNode {
|
||||||
return Objects.hash(name);
|
return Objects.hash(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getParentId() {
|
public MethodInvocation getInvocation() {
|
||||||
if (parent == null) {
|
return invocation;
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return parent.getId();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getDuration() {
|
public void setInvocation(MethodInvocation invocation) {
|
||||||
return duration;
|
this.invocation = invocation;
|
||||||
}
|
|
||||||
|
|
||||||
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,65 +2,17 @@ package perfix;
|
||||||
|
|
||||||
import perfix.instrument.Util;
|
import perfix.instrument.Util;
|
||||||
|
|
||||||
import java.sql.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ConcurrentSkipListMap;
|
||||||
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';";
|
|
||||||
|
|
||||||
private static BlockingQueue<MethodNode> queue = new LinkedBlockingQueue<>();
|
@SuppressWarnings("unused") //used in generated code
|
||||||
private static Connection connection;
|
public static MethodInvocation startJdbc(String name) {
|
||||||
|
|
||||||
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);
|
||||||
|
|
@ -69,54 +21,72 @@ public class Registry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MethodNode start(String name) {
|
@SuppressWarnings("unused")
|
||||||
|
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) {
|
||||||
newNode.setParent(parent);
|
parent.addChild(newNode);
|
||||||
newNode.setInvocationId(parent.getInvocationId());
|
newNode.parent = parent;
|
||||||
|
} else {
|
||||||
|
callstack.add(newNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentMethod.set(newNode);
|
currentMethod.set(newNode);
|
||||||
return newNode;
|
return methodInvocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public static void stopJdbc() {
|
public static void stopJdbc(MethodInvocation queryInvocation) {
|
||||||
MethodNode current = currentMethod.get();
|
if (Util.isFirstExecutionStarted() && queryInvocation != null) {
|
||||||
if (Util.isFirstExecutionStarted() && current != null) {
|
stop(queryInvocation);
|
||||||
stop();
|
|
||||||
Util.endExecution();
|
Util.endExecution();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public static void stop() {
|
public static void stop(MethodInvocation methodInvocation) {
|
||||||
MethodNode current = currentMethod.get();
|
if (methodInvocation != null) {
|
||||||
if (current != null) {
|
methodInvocation.registerEndingTime(System.nanoTime());
|
||||||
current.registerEndingTime(System.nanoTime());
|
|
||||||
queue.add(current);
|
|
||||||
currentMethod.set(current.getParent());
|
|
||||||
}
|
}
|
||||||
|
MethodNode methodNode = currentMethod.get();
|
||||||
|
methodNode.setInvocation(methodInvocation);
|
||||||
|
|
||||||
|
currentMethod.set(methodNode.parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void store(MethodNode methodNode) {
|
public static SortedMap<Long, Report> sortedMethodsByDuration() {
|
||||||
try {
|
//walk the stack to group methods by their name
|
||||||
PreparedStatement statement = connection.prepareStatement(INSERT_REPORT);
|
Map<String, List<MethodInvocation>> methods = new ConcurrentHashMap<>();
|
||||||
statement.setString(1, methodNode.getThreadName());
|
collectInvocationsPerMethodName(methods, callstack);
|
||||||
statement.setLong(2, methodNode.getId());
|
|
||||||
statement.setLong(3, methodNode.getParentId());
|
//gather invocations by method name and calculate statistics
|
||||||
statement.setLong(4, methodNode.getInvocationId());
|
SortedMap<Long, Report> sortedByTotal = new ConcurrentSkipListMap<>(Comparator.reverseOrder());
|
||||||
statement.setLong(5, methodNode.getTimestamp());
|
methods.forEach((name, measurements) -> {
|
||||||
statement.setString(6, methodNode.getName());
|
long totalDuration = measurements.stream()
|
||||||
statement.setLong(7, methodNode.getDuration());
|
.filter(Objects::nonNull)
|
||||||
statement.executeUpdate();
|
.mapToLong(MethodInvocation::getDuration).sum();
|
||||||
statement.close();
|
sortedByTotal.put(totalDuration, new Report(name, measurements.size(), totalDuration));
|
||||||
} catch (Exception e) {
|
});
|
||||||
e.printStackTrace();
|
return sortedByTotal;
|
||||||
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,6 +24,7 @@ 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,6 +9,7 @@ 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 = "$_";
|
||||||
|
|
||||||
|
|
@ -16,12 +17,14 @@ 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);
|
||||||
|
|
||||||
|
|
@ -49,8 +52,9 @@ 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.insertBefore("perfix.Registry.start(" + metricName + ");");
|
methodToinstrument.addLocalVariable("_perfixmethod", perfixMethodInvocationClass);
|
||||||
methodToinstrument.insertAfter("perfix.Registry.stop();");
|
methodToinstrument.insertBefore("_perfixmethod = perfix.Registry.start(" + metricName + ");");
|
||||||
|
methodToinstrument.insertAfter("perfix.Registry.stop(_perfixmethod);");
|
||||||
} catch (CannotCompileException e) {
|
} catch (CannotCompileException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
@ -62,8 +66,9 @@ 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.insertBefore("perfix.Registry.startJdbc(" + metricName + ");");
|
methodToinstrument.addLocalVariable("_perfixmethod", perfixMethodInvocationClass);
|
||||||
methodToinstrument.insertAfter("perfix.Registry.stopJdbc();");
|
methodToinstrument.insertBefore("_perfixmethod = perfix.Registry.startJdbc(" + metricName + ");");
|
||||||
|
methodToinstrument.insertAfter("perfix.Registry.stopJdbc(_perfixmethod);");
|
||||||
} catch (CannotCompileException e) {
|
} catch (CannotCompileException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
@ -72,8 +77,9 @@ 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.insertBefore("perfix.Registry.startJdbc(perfix.instrument.StatementText.toString(_perfixSqlStatement));");
|
methodToinstrument.addLocalVariable("_perfixmethod", perfixMethodInvocationClass);
|
||||||
methodToinstrument.insertAfter("perfix.Registry.stopJdbc();");
|
methodToinstrument.insertBefore("_perfixmethod = perfix.Registry.startJdbc(perfix.instrument.StatementText.toString(_perfixSqlStatement));");
|
||||||
|
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 {
|
private void addPerfixFields(CtClass preparedStatementClass) throws CannotCompileException, NotFoundException {
|
||||||
// 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,10 +26,11 @@ 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.insertBefore("perfix.Registry.start($1.getRequestURI());");
|
methodToInstrument.addLocalVariable("_perfixmethod", perfixMethodInvocationClass);
|
||||||
methodToInstrument.insertAfter("perfix.Registry.stop();");
|
methodToInstrument.insertBefore("_perfixmethod = perfix.Registry.start($1.getRequestURI());");
|
||||||
|
methodToInstrument.insertAfter("perfix.Registry.stop(_perfixmethod);");
|
||||||
} catch (CannotCompileException e) {
|
} catch (CannotCompileException e) {
|
||||||
// ignore and return uninstrumented bytecode
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return bytecode(classToInstrument);
|
return bytecode(classToInstrument);
|
||||||
|
|
|
||||||
76
src/main/java/perfix/server/HTTPServer.java
Normal file
76
src/main/java/perfix/server/HTTPServer.java
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
21
src/main/java/perfix/server/json/JSONSerializer.java
Normal file
21
src/main/java/perfix/server/json/JSONSerializer.java
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/main/java/perfix/server/json/Serializer.java
Normal file
45
src/main/java/perfix/server/json/Serializer.java
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package perfix.server.json;
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class SerializerCreationException extends RuntimeException {
|
||||||
|
|
||||||
|
public SerializerCreationException(Throwable t) {
|
||||||
|
super(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
5
src/main/java/perfix/server/json/SerializerFactory.java
Normal file
5
src/main/java/perfix/server/json/SerializerFactory.java
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
package perfix.server.json;
|
||||||
|
|
||||||
|
public interface SerializerFactory {
|
||||||
|
public <T> JSONSerializer<T> createSerializer(Class<T> beanjavaClass);
|
||||||
|
}
|
||||||
350
src/main/java/perfix/server/json/SynthSerializerFactory.java
Normal file
350
src/main/java/perfix/server/json/SynthSerializerFactory.java
Normal file
|
|
@ -0,0 +1,350 @@
|
||||||
|
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