Last active
October 4, 2016 12:00
-
-
Save libetl/00bc6079b3dd91af55bb6cf8229e942a to your computer and use it in GitHub Desktop.
Dive Deep Analyzer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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