Skip to content

Instantly share code, notes, and snippets.

@libetl
Last active October 4, 2016 12:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save libetl/00bc6079b3dd91af55bb6cf8229e942a to your computer and use it in GitHub Desktop.
Save libetl/00bc6079b3dd91af55bb6cf8229e942a to your computer and use it in GitHub Desktop.
Dive Deep Analyzer
package org.toilelibre.libe.divedeepanalyzer;
import java.io.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import spoon.Launcher;
import spoon.SpoonAPI;
import spoon.reflect.code.*;
import spoon.reflect.cu.SourcePosition;
import spoon.reflect.declaration.CtAnnotation;
import spoon.reflect.declaration.CtClass;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.declaration.CtType;
import spoon.reflect.reference.CtTypeReference;
public class DiveDeepAnalyzer {
public enum Type {
SERVICE, DATABASE
}
public @interface Call {
String name();
String why();
Type value();
}
public @interface Calls {
Call[] several();
String name();
String why();
Type value();
}
public static class CallObject {
private String name;
private String why;
private String value;
private CallObject (Builder builder) {
name = builder.name;
why = builder.why;
value = builder.value;
}
public static Builder newBuilder () {
return new Builder ();
}
public static final class Builder {
private String name;
private String why;
private String value;
private Builder () {
}
public Builder name (String val) {
name = val;
return this;
}
public Builder why (String val) {
why = val;
return this;
}
public Builder value (String val) {
value = val;
return this;
}
public CallObject build () {
return new CallObject (this);
}
}
@Override
public String toString () {
return "@Call(" + "name=\"" + name + "\", why=\"" + why + "\"" + ", value=" + value + ")";
}
}
private static final Pattern LINES_TO_BE_REMOVED_PATTERN1 = Pattern.compile("^[ ]+@Call(s)?[ ]*\\(.*");
private static final Pattern LINES_TO_BE_REMOVED_PATTERN2 = Pattern.compile("^import org\\.toilelibre\\.libe\\.divedeepanalyzer\\.DiveDeepAnalyzer.*;");
private static final String LINES_TO_BE_ADDED =
"import org.toilelibre.libe.divedeepanalyzer.DiveDeepAnalyzer.Calls;\n" +
"import org.toilelibre.libe.divedeepanalyzer.DiveDeepAnalyzer.Call;\n" +
"import static org.toilelibre.libe.divedeepanalyzer.DiveDeepAnalyzer.Type.*;\n";
private static class RecognitionData {
Pattern pattern;
String name;
String value;
public RecognitionData (Pattern pattern, String name, String value) {
super ();
this.pattern = pattern;
this.name = name;
this.value = value;
}
}
private static class CallsMaps {
Map<String, Set<CallObject>> callsTemplates = new HashMap<> ();
Map<String, SourcePosition> methodsSignLoc = new HashMap<> ();
Map<String, Set<CallObject>> indirectCalls = new HashMap<> ();
int numberOfIndirectCalls = 0;
@Override
public String toString () {
return callsTemplates + "\n" + indirectCalls + "\n" + numberOfIndirectCalls;
}
}
@FunctionalInterface
interface AddToCallsMapsFunction {
void add (CallsMaps callsMaps, String fullMethodName, CtCodeElement realStatement);
}
public static void main (String [] args) {
RecognitionData [] recData = new RecognitionData [] {
new RecognitionData (Pattern.compile("org\\.springframework\\.web\\.client\\.RestTemplate"), "$6", "SERVICE"),
new RecognitionData (Pattern.compile("com\\.mycompany\\.myproject\\.infra\\.service\\.soap\\.TechnicalSoapServiceCaller"), "$6", "SERVICE"),
new RecognitionData (Pattern.compile("org\\.springframework\\.jdbc\\.core\\.simple\\.SimpleJdbcCallOperations"), "$6", "DATABASE"),
new RecognitionData (Pattern.compile("org\\.springframework\\.jdbc\\.core\\.JdbcTemplate"), "$6", "DATABASE"),
new RecognitionData (Pattern.compile("com\\.mongodb\\.DBCollection"), "$6", "DATABASE"),
};
List<Pattern> excludedPatterns = Arrays.asList (Pattern.compile ("org\\.springframework\\.context\\.annotation\\.Configuration"));
List<String> excludedFiles = Arrays.asList ("generated-test-sources", "sample", "test");
File rootDir = new File ("/path/to/my/project");
run (recData, excludedPatterns, excludedFiles, rootDir);
}
private static void run (RecognitionData [] recData, List<Pattern> excludedPatterns, List<String> excludedFiles, File rootDir) {
final SpoonAPI spoon = new Launcher ();
removeAllCallsAnnotations (rootDir, excludedFiles);
addRecursively (spoon, rootDir, excludedFiles);
spoon.run ();
CallsMaps callsMaps = enumerateCalls (spoon, excludedPatterns, recData);
readStatementsRecursively (spoon, excludedPatterns, callsMaps, (callsMapsValue, fullMethodName, expr) -> addFromCallsTemplatesToIndirectCalls (callsMapsValue, fullMethodName, expr));
int i = -1;
while (i != callsMaps.numberOfIndirectCalls) {
i = callsMaps.numberOfIndirectCalls;
readStatementsRecursively (spoon, excludedPatterns, callsMaps, (callsMapsValue, fullMethodName, expr) -> addFromIndirectCallsToIndirectCalls (callsMapsValue, fullMethodName, expr));
}
saveAnnotations (callsMaps, rootDir);
}
private static void readStatementsRecursively (SpoonAPI spoon, List<Pattern> excludedPatterns, CallsMaps callsMaps, AddToCallsMapsFunction add) {
for (CtType<?> type : spoon.getModel ().getAllTypes ()) {
if (isTypeExcluded (type, excludedPatterns)) continue;
for (CtMethod<?> method : type.getAllMethods ()) {
if (method.getBody () == null) continue;
readStatementsRecursively (callsMaps, type.getQualifiedName () + "." + method.getSimpleName (), method.getBody ().getStatements (), add);
}
}
}
private static void readStatementsRecursively (CallsMaps callsMaps, String fullMethodName, List<CtStatement> statements, AddToCallsMapsFunction add) {
for (CtStatement statement : statements) {
CtCodeElement readExpr = statement;
if (readExpr instanceof CtLocalVariable && ((CtLocalVariable<?>) readExpr).getDefaultExpression () instanceof CtStatement) {
readExpr = ((CtLocalVariable<?>) readExpr).getDefaultExpression ();
}
if (readExpr instanceof CtReturn && ((CtReturn<?>) readExpr).getReturnedExpression () instanceof CtStatement) {
readExpr = ((CtReturn<?>) readExpr).getReturnedExpression ();
}
if (readExpr instanceof CtConditional) {
readExpression(callsMaps, fullMethodName, ((CtConditional) readExpr).getThenExpression(), add);
readExpr = ((CtConditional) readExpr).getElseExpression();
}
if (readExpr instanceof CtStatementList) {
readStatementsRecursively (callsMaps, fullMethodName, ((CtStatementList) readExpr).getStatements (), add);
}
readExpression(callsMaps, fullMethodName, readExpr, add);
}
}
private static void readExpression(CallsMaps callsMaps, String fullMethodName, CtCodeElement readExpr, AddToCallsMapsFunction add) {
if (readExpr instanceof CtTry) {
readStatementsRecursively (callsMaps, fullMethodName, ((CtTry) readExpr).getBody ().getStatements (), add);
if (((CtTry)readExpr).getCatchers () != null) {
for (CtCatch ctCatch : ((CtTry)readExpr).getCatchers ()) {
readStatementsRecursively (callsMaps, fullMethodName, ctCatch.getBody ().getStatements (), add);
}
}
if (((CtTry) readExpr).getFinalizer () != null) {
readStatementsRecursively (callsMaps, fullMethodName, ((CtTry) readExpr).getFinalizer ().getStatements (), add);
}
}
if (readExpr instanceof CtSwitch) {
for (CtCase<?> aCase : ((CtSwitch<?>) readExpr).getCases ()) {
readStatementsRecursively (callsMaps, fullMethodName, aCase.getStatements (), add);
}
}
if (readExpr instanceof CtFor) {
readStatementsRecursively (callsMaps, fullMethodName, ((CtFor) readExpr).getForInit (), add);
readStatementsRecursively (callsMaps, fullMethodName, ((CtFor) readExpr).getForUpdate (), add);
readOneExpr (callsMaps, fullMethodName, ((CtFor) readExpr).getBody (), add);
readOneExpr (callsMaps, fullMethodName, ((CtFor) readExpr).getExpression (), add);
}
if (readExpr instanceof CtForEach) {
readStatementsRecursively (callsMaps, fullMethodName, Collections.singletonList (((CtLoop) readExpr).getBody ()), add);
readOneExpr (callsMaps, fullMethodName, ((CtForEach) readExpr).getExpression (), add);
}
if (readExpr instanceof CtWhile) {
readStatementsRecursively (callsMaps, fullMethodName, Collections.singletonList (((CtLoop) readExpr).getBody ()), add);
readOneExpr (callsMaps, fullMethodName, ((CtWhile) readExpr).getLoopingExpression (), add);
}
if (readExpr instanceof CtIf) {
readOneExpr (callsMaps, fullMethodName, ((CtIf) readExpr).getCondition (), add);
readOneExpr (callsMaps, fullMethodName, ((CtIf) readExpr).getThenStatement(), add);
readStatementsRecursively (callsMaps, fullMethodName, Arrays.asList (((CtIf) readExpr).getThenStatement (), ((CtIf) readExpr).getElseStatement ()), add);
}
if (readExpr instanceof CtAbstractInvocation) {
for (CtExpression<?> expr : ((CtAbstractInvocation<?>) readExpr).getArguments ()) {
readOneExpr (callsMaps, fullMethodName, expr, add);
}
}
if (readExpr instanceof CtInvocation) {
readOneExpr(callsMaps, fullMethodName, ((CtInvocation<?>) readExpr).getTarget(), add);
readExpression(callsMaps, fullMethodName, ((CtInvocation<?>) readExpr).getTarget(), add );
}
if (readExpr instanceof CtTargetedExpression) {
readOneExpr (callsMaps, fullMethodName, ((CtTargetedExpression<?, ?>) readExpr).getTarget(), add);
}
readOneExpr (callsMaps, fullMethodName, readExpr, add);
}
private static void readOneExpr (CallsMaps callsMaps, String fullMethodName, CtCodeElement expr, AddToCallsMapsFunction add) {
if (expr instanceof CtAbstractInvocation) {
add.add (callsMaps, fullMethodName, expr);
}
}
private static void addFromCallsTemplatesToIndirectCalls (CallsMaps callsMaps, String fullMethodName, CtCodeElement realStatement) {
String nameOfTheMethod = ((CtAbstractInvocation<?>) realStatement).getExecutable ().getSimpleName ();
String className = ((CtAbstractInvocation<?>) realStatement).getExecutable ().getDeclaringType ().getQualifiedName ();
String alternateNameOfTheMethod = null;
if (realStatement.getParent (CtMethod.class).getAnnotation (Override.class) != null && realStatement.getParent (CtClass.class).getSuperInterfaces () != null && !realStatement.getParent (CtClass.class).getSuperInterfaces ().isEmpty ()) {
alternateNameOfTheMethod = realStatement.getParent (CtClass.class).getSuperInterfaces ().iterator ().next ().getQualifiedName () + "." + realStatement.getParent (CtMethod.class).getSimpleName ();
}
Set<CallObject> call = callsMaps.callsTemplates.get (className + "." + nameOfTheMethod);
if (call != null) {
callsMaps.indirectCalls.putIfAbsent (fullMethodName, new HashSet<> ());
callsMaps.indirectCalls.get (fullMethodName).add (call.iterator().next());
callsMaps.methodsSignLoc.put(fullMethodName, realStatement.getParent (CtMethod.class).getPosition());
if (alternateNameOfTheMethod != null) {
callsMaps.indirectCalls.putIfAbsent (alternateNameOfTheMethod, new HashSet<> ());
callsMaps.indirectCalls.get (alternateNameOfTheMethod).add (call.iterator().next());
callsMaps.methodsSignLoc.put(alternateNameOfTheMethod, realStatement.getParent (CtClass.class).getSuperInterfaces ().iterator ().next ().getPosition());
}
}
}
private static void addFromIndirectCallsToIndirectCalls (CallsMaps callsMaps, String fullMethodName, CtCodeElement expr) {
String nameOfTheMethod = ((CtAbstractInvocation<?>) expr).getExecutable ().getSimpleName ();
String className = ((CtAbstractInvocation<?>) expr).getExecutable ().getDeclaringType ().getQualifiedName ();
String fullOtherMethodName = className + "." + nameOfTheMethod;
String alternateNameOfTheMethod = null;
if (expr.getParent (CtMethod.class).getAnnotation (Override.class) != null && expr.getParent (CtClass.class).getSuperInterfaces () != null && !expr.getParent (CtClass.class).getSuperInterfaces ().isEmpty ()) {
alternateNameOfTheMethod = expr.getParent (CtClass.class).getSuperInterfaces ().iterator ().next ().getQualifiedName () + "." + expr.getParent (CtMethod.class).getSimpleName ();
}
if (callsMaps.indirectCalls.get (fullOtherMethodName) != null && !fullMethodName.contains("Technical")) {
callsMaps.indirectCalls.putIfAbsent (fullMethodName, new HashSet<> ());
int sizeBeforeAddAll = callsMaps.indirectCalls.get (fullMethodName).size ();
callsMaps.indirectCalls.get (fullMethodName).addAll (callsMaps.indirectCalls.get (fullOtherMethodName));
callsMaps.methodsSignLoc.put(fullMethodName, expr.getParent (CtMethod.class).getPosition());
int sizeAfterAddAll = callsMaps.indirectCalls.get (fullMethodName).size ();
callsMaps.numberOfIndirectCalls = callsMaps.numberOfIndirectCalls + sizeAfterAddAll - sizeBeforeAddAll;
if (alternateNameOfTheMethod != null) {
callsMaps.indirectCalls.putIfAbsent (alternateNameOfTheMethod, new HashSet<> ());
callsMaps.indirectCalls.get (alternateNameOfTheMethod).addAll (callsMaps.indirectCalls.get (fullOtherMethodName));
callsMaps.methodsSignLoc.put(alternateNameOfTheMethod, expr.getParent (CtClass.class).getSuperInterfaces ().iterator ().next ().getPosition());
}
}
}
private static CallsMaps enumerateCalls (SpoonAPI spoon, List<Pattern> excludedPatterns, RecognitionData... recognitionData) {
CallsMaps callsMaps = new CallsMaps ();
for (CtType<?> type : spoon.getModel ().getAllTypes ()) {
if (isTypeExcluded (type, excludedPatterns)) continue;
for (CtMethod<?> method : type.getAllMethods ()) {
if (method.getBody () == null) continue;
for (CtStatement statement : method.getBody ().getStatements ()) {
for (CtTypeReference<?> typeRef : statement.getReferencedTypes ()) {
if ("<nulltype>".equals (typeRef.getQualifiedName ())) continue;
for (RecognitionData data : recognitionData){
Matcher matcher = data.pattern.matcher (typeRef.getActualClass ().getName ());
if (matcher.find ()) {
String fullMethodName = type.getActualClass ().getName () + "." + method.getSimpleName ();
CallObject value = CallObject.newBuilder ().name (data.name.matches("\\$[0-9]+") ?
fullMethodName.split("\\.") [Integer.parseInt(data.name.substring(1)) - 1]
: data.name).value (data.value).why (fullMethodName).build ();
if (method.getAnnotation (Override.class) != null && ((CtClass<?>) method.getParent ()).getSuperInterfaces () != null && !((CtClass<?>) method.getParent ()).getSuperInterfaces ().isEmpty ()) {
callsMaps.callsTemplates.put(((CtClass<?>) method.getParent()).getSuperInterfaces().iterator().next().getQualifiedName() + "." + method.getSimpleName(), Collections.singleton(value));
callsMaps.methodsSignLoc.put(((CtClass<?>) method.getParent()).getSuperInterfaces().iterator().next().getQualifiedName() + "." + method.getSimpleName(),
((CtClass<?>) method.getParent()).getSuperInterfaces().iterator().next().getPosition());
}
callsMaps.callsTemplates.put(fullMethodName, Collections.singleton(value));
callsMaps.methodsSignLoc.put(fullMethodName, method.getPosition());
}
}
}
}
}
}
return callsMaps;
}
private static boolean isTypeExcluded (CtType<?> type, List<Pattern> excludedPatterns) {
boolean excluded = false;
for (Pattern excludedPattern : excludedPatterns) {
for (CtAnnotation<?> annotation : type.getAnnotations ()) {
excluded |= excludedPattern.matcher (annotation.getAnnotationType ().getQualifiedName ()).find();
}
excluded |= excludedPattern.matcher (type.getQualifiedName ()).find();
}
return excluded;
}
private static void removeAllCallsAnnotations(File dir, List<String> excludedFiles) {
if (dir.listFiles () == null) return;
for (File file : dir.listFiles ()) {
if (file.isDirectory () && !file.equals (dir) && !file.equals (dir.getParentFile ()) && !excludedFiles.contains (file.getName ())) {
removeAllCallsAnnotations (file, excludedFiles);
}
if (file.isFile () && file.getName ().endsWith (".java")) {
try {
insertStringInFile(file, -1, "", LINES_TO_BE_REMOVED_PATTERN1);
insertStringInFile(file, -1, "", LINES_TO_BE_REMOVED_PATTERN2);
} catch (IOException e) {
}
}
}
}
private static void addRecursively (SpoonAPI spoon, File dir, List<String> excludedFiles) {
if (dir.listFiles () == null) return;
for (File file : dir.listFiles ()) {
if (file.isDirectory () && !file.equals (dir) && !file.equals (dir.getParentFile ()) && !excludedFiles.contains (file.getName ())) {
addRecursively (spoon, file, excludedFiles);
}
if (file.isFile () && file.getName ().endsWith (".java")) {
spoon.addInputResource (file.getAbsolutePath ());
}
}
}
private static void saveAnnotations(CallsMaps callsMaps, File rootDir) {
Map<String, Set<CallObject>> bigMap = new HashMap<>();
bigMap.putAll(callsMaps.callsTemplates);
bigMap.putAll(callsMaps.indirectCalls);
List<Map.Entry<String, SourcePosition>> locations = callsMaps.methodsSignLoc.entrySet().stream().sorted((entry1, entry2) -> Integer.compare(entry2.getValue().getLine(), entry1.getValue().getLine())).collect(Collectors.toList());
Set<String> fileNames = new HashSet<>();
for (Map.Entry<String, SourcePosition> sourceEntry : locations) {
Set<CallObject> calls = bigMap.get(sourceEntry.getKey());
if (!fileNames.contains(sourceEntry.getValue().getFile().getAbsolutePath())) {
try {
insertStringInFile(sourceEntry.getValue().getFile(), 2, LINES_TO_BE_ADDED, null);
} catch (IOException e) {
}
fileNames.add(sourceEntry.getValue().getFile().getAbsolutePath());
}
if (sourceEntry.getValue().getFile().getAbsolutePath().contains(rootDir.getAbsolutePath())) {
try {
if (calls.size() == 1) {
insertStringInFile(sourceEntry.getValue().getFile(), sourceEntry.getValue().getLine() + 4 , " " + calls.iterator().next().toString().replace("@Call", "@Calls"), null);
}else {
String callsToString = calls.toString();
callsToString = callsToString.substring(1, callsToString.length() - 1);
insertStringInFile(sourceEntry.getValue().getFile(), sourceEntry.getValue().getLine() + 4, " @Calls (several = {" + callsToString.replaceAll(", @Call", ",\n @Call") + "})", null);
}
} catch (IOException e) {
}
}
}
}
private static void insertStringInFile(File inFile, int lineNo, String lineToBeInserted, Pattern linesToBeRemovedPattern) throws IOException {
// temp file
File outFile = File.createTempFile("aaaaa", "bbbb");
// input
FileInputStream fis = new FileInputStream(inFile);
BufferedReader in = new BufferedReader
(new InputStreamReader(fis));
// output
FileOutputStream fos = new FileOutputStream(outFile);
PrintWriter out = new PrintWriter(fos);
String thisLine;
int i =1;
while ((thisLine = in.readLine()) != null) {
if(i == lineNo) out.println(lineToBeInserted);
if (linesToBeRemovedPattern == null || !linesToBeRemovedPattern.matcher(thisLine).find()) out.println(thisLine);
i++;
}
out.flush();
out.close();
in.close();
inFile.delete();
outFile.renameTo(inFile);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment