From f5b9a90e538b3ad7a0eee3d4386c223d0bb19e5c Mon Sep 17 00:00:00 2001 From: Wesley Shann Date: Tue, 9 Jun 2020 15:37:56 +0200 Subject: [PATCH 01/27] Remove unused source code Issue: S2-group/NAPPA#43 The try/catch blocks never find the AndroidManifest for none of the test apps --- .../action/InstrumentActivityAction.java | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java index 94e1e8ce..c57ebc1e 100644 --- a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java +++ b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java @@ -347,32 +347,6 @@ private void instrumentActivityFiles() { public void actionPerformed(AnActionEvent e) { // Open the specific project project = e.getProject(); - System.out.println("ciao"); - - VirtualFile file = null; - - try { - // Get the project folder and find the Android Manifest file from - // somewhere within the project - cat += " "+project.getBasePath()+" "; - - file = project.getProjectFile().findChild("AndroidManifest.xml"); - } catch (Exception exc) { - // If there is no manifest file, print out the failure - cat = "no_ok_file"; - } - try { - // Has to do with whether an Android Manifest File has a different extension? - cat += " "+file.getPath()+" "; - cat += file.isDirectory()? " isDir" : " notisdir"; - FileType fileType = file.getFileType(); - cat += " with extension"+file.getExtension(); - } catch (Exception exc) { - cat += " and no extensionzzzzz"; - } - - PsiFile psiFile = e.getData(LangDataKeys.PSI_FILE); - //psiFile.ele // OPEN the Android Manifest File(s), and process each one of them in order to instrument them (project level) PsiFile[] listpsi = FilenameIndex.getFilesByName(project, "AndroidManifest.xml", GlobalSearchScope.projectScope(project)); From d0a1b1ff732d8341adf7086a2bc1fab1c00e93bc Mon Sep 17 00:00:00 2001 From: Wesley Shann Date: Tue, 9 Jun 2020 16:03:47 +0200 Subject: [PATCH 02/27] Remove commented source code and unused imports Issue: S2-group/NAPPA#43 --- .../nappa/plugin/action/InstrumentActivityAction.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java index c57ebc1e..e3eacfc1 100644 --- a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java +++ b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java @@ -2,12 +2,9 @@ import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.LangDataKeys; import com.intellij.openapi.command.WriteCommandAction; -import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.search.FilenameIndex; import com.intellij.psi.search.GlobalSearchScope; @@ -29,10 +26,6 @@ public class InstrumentActivityAction extends AnAction { private List javaActivityNameList = new LinkedList<>(); private Project project; - //PrefetchingLib.init(this); - - - /** * This detects the activitis from the manifest file and also initializes the prefetching library. * From bdc3526fb5275523b3652b7a26b5dd1abc208676 Mon Sep 17 00:00:00 2001 From: Wesley Shann Date: Tue, 9 Jun 2020 18:23:21 +0200 Subject: [PATCH 03/27] Partial refactorying of intrument activities action --- .../action/InstrumentActivityAction.java | 157 ++++++++++++++---- 1 file changed, 127 insertions(+), 30 deletions(-) diff --git a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java index e3eacfc1..a1b72aa3 100644 --- a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java +++ b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java @@ -8,21 +8,25 @@ import com.intellij.psi.*; import com.intellij.psi.search.FilenameIndex; import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.xml.XmlAttribute; import com.intellij.psi.xml.XmlFile; import com.intellij.psi.xml.XmlTag; +import org.jetbrains.annotations.NotNull; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; /** - * This class pertains to the parsing of the android manifest files for Activities - * and also setting up the Pre-fetching library package imports for usage on the application/ Project. - * Furthermore injects the Prefetch.init code to the project in order to initialize the prefetching - * library + * This class pertains to the parsing of the android manifest files for Activities + * and also setting up the Pre-fetching library package imports for usage on the application/ Project. + * Furthermore injects the Prefetch.init code to the project in order to initialize the prefetching + * library */ public class InstrumentActivityAction extends AnAction { - private String cat = "okfile"; + private String cat = "okfile"; private List javaActivityNameList = new LinkedList<>(); private Project project; @@ -36,7 +40,7 @@ private void populateActivityNameListFromManifestAndInitLibrary(PsiFile[] listps // Iterate through all Manifest Files detected within the project for (PsiFile file1 : listpsi) { - cat += "\n"+file1.getVirtualFile().getPath()+"\n"; + cat += "\n" + file1.getVirtualFile().getPath() + "\n"; PsiElement psiElement = file1.findElementAt(0); // Treat the Android.Xml file as an XML structure @@ -54,7 +58,7 @@ private void populateActivityNameListFromManifestAndInitLibrary(PsiFile[] listps // Iterate through each activity tag for (XmlTag tag : activityTags) { // Add the activity name as defined in the manifest - cat += "\n"+tag.getAttribute("android:name").getValue(); + cat += "\n" + tag.getAttribute("android:name").getValue(); try { // Fetch the java resource file corresponding to the activity name String[] names = tag.getAttribute("android:name").getValue().split("\\."); @@ -82,14 +86,14 @@ private void populateActivityNameListFromManifestAndInitLibrary(PsiFile[] listps // activity if this is found to be present for a given activity. // There can only be one of each for (XmlTag actionTag : actionTags) { - if (actionTag.getAttribute("android:name")!=null && + if (actionTag.getAttribute("android:name") != null && actionTag.getAttribute("android:name").getValue().compareTo("android.intent.action.MAIN") == 0) { isMain = true; break; } } - for (XmlTag categoryTag: categoryTags) { - if (categoryTag.getAttribute("android:name")!=null && + for (XmlTag categoryTag : categoryTags) { + if (categoryTag.getAttribute("android:name") != null && categoryTag.getAttribute("android:name").getValue().compareTo("android.intent.category.LAUNCHER") == 0) { isLauncher = true; break; @@ -97,7 +101,6 @@ private void populateActivityNameListFromManifestAndInitLibrary(PsiFile[] listps } - } cat += name + "\n"; @@ -163,7 +166,7 @@ private void populateActivityNameListFromManifestAndInitLibrary(PsiFile[] listps cat += "FOUND IN SECOND ELSE\n"; //break; } else { - cat += "\n"+statement.getText()+"\n"; + cat += "\n" + statement.getText() + "\n"; } } @@ -188,10 +191,10 @@ private void populateActivityNameListFromManifestAndInitLibrary(PsiFile[] listps } }); } else { - cat += "\nINIT PREFETCHING LIB NOT ADDED in "+name+"\n\n"; + cat += "\nINIT PREFETCHING LIB NOT ADDED in " + name + "\n\n"; } } catch (Exception e) { - cat += "\n"+e.getMessage()+"\n\n"; + cat += "\n" + e.getMessage() + "\n\n"; e.printStackTrace(); } } @@ -201,7 +204,7 @@ private void populateActivityNameListFromManifestAndInitLibrary(PsiFile[] listps } } catch (Exception e2) { - cat += " "+e2.toString(); + cat += " " + e2.toString(); } } @@ -216,7 +219,7 @@ private void populateActivityNameListFromManifestAndInitLibrary(PsiFile[] listps private void instrumentActivityFiles() { // Iterate through all the java resource files corresponding to each activity in the Android // App. Usin the name of the files collected in the previous function - for (String javaActivityName: javaActivityNameList) { + for (String javaActivityName : javaActivityNameList) { // Fetch the file in PSI structure PsiFile[] listActJava = FilenameIndex.getFilesByName(project, javaActivityName, GlobalSearchScope.projectScope(project)); @@ -224,7 +227,7 @@ private void instrumentActivityFiles() { * IMPORTANT: This iterates through every activity's corresponding resouce file in order * to instrument each activity to make use of the Prefetching lib. */ - for (PsiFile actJava: listActJava) { + for (PsiFile actJava : listActJava) { if (actJava instanceof PsiJavaFile) { PsiJavaFile javaFile = (PsiJavaFile) actJava; // FIXME: Assumption of first defined class being the public class @@ -261,7 +264,7 @@ private void instrumentActivityFiles() { } // write the full name of the class (inclulding full package description). - cat += " "+psiClass.getQualifiedName()+" "; + cat += " " + psiClass.getQualifiedName() + " "; // Fetch onResume callbackmethod PsiMethod[] psiMethods = psiClass.findMethodsByName("onResume", false); @@ -279,7 +282,7 @@ private void instrumentActivityFiles() { try { // Determine if On Resume is writable - cat += psiMethods[0].getBody().isWritable()? "writable" : "not writable"; + cat += psiMethods[0].getBody().isWritable() ? "writable" : "not writable"; cat += "\n"; // THis is the statement added to instrument intent transitions @@ -299,7 +302,7 @@ private void instrumentActivityFiles() { addStatement = false; break; } else { - cat += "\n"+statement.getText()+"\n\n"; + cat += "\n" + statement.getText() + "\n\n"; } } @@ -312,11 +315,11 @@ private void instrumentActivityFiles() { ); }); } else { - cat += "\nNOT ADDED in "+javaActivityName+"\n\n"; + cat += "\nNOT ADDED in " + javaActivityName + "\n\n"; } } catch (Exception e3) { - cat += e3.toString()+"\n"; + cat += e3.toString() + "\n"; } } else { //NO onResume METHOD FOUND @@ -341,18 +344,112 @@ public void actionPerformed(AnActionEvent e) { // Open the specific project project = e.getProject(); + getAllJavaFilesWithAnActivity().forEach((fileName, isMainLauncherActivity) -> { + System.out.println(fileName + " -> " + isMainLauncherActivity); + PsiFile[] psiFiles = FilenameIndex.getFilesByName(project, fileName, GlobalSearchScope.projectScope(project)); + for (PsiFile psiFile : psiFiles) { + PsiJavaFile psiJavaFile = (PsiJavaFile) psiFile; + if (isMainLauncherActivity) addLibraryInitializationStatement(psiJavaFile); + } + }); + // OPEN the Android Manifest File(s), and process each one of them in order to instrument them (project level) - PsiFile[] listpsi = FilenameIndex.getFilesByName(project, "AndroidManifest.xml", GlobalSearchScope.projectScope(project)); +// PsiFile[] listpsi = FilenameIndex.getFilesByName(project, "AndroidManifest.xml", GlobalSearchScope.projectScope(project)); +// +// /**** Activity Instrumentation begins here ***/ +// +// populateActivityNameListFromManifestAndInitLibrary(listpsi); +// instrumentActivityFiles(); +// +// // This should print "Hello" + "okFile" + <> + <> +// // The actual output from the plug in is generated from +// // populateActivityNameListFromManifestAndInitLibrary(listpsi); +// Messages.showMessageDialog("Hello\t" + cat, "World", Messages.getInformationIcon()); + } - /**** Activity Instrumentation begins here ***/ + improve this method as it is quite messy + private void addLibraryInitializationStatement(PsiJavaFile javaFile) { + String instrumentedText = "Prefetch.init(this);"; + PsiClass[] psiClasses = javaFile.getClasses(); + + for (PsiClass psiClass : psiClasses) { + // There is only one initialization per app + if (psiClass.getText().contains(instrumentedText)) break; + + // The library must be initialized only in the file main class + PsiModifierList classModifier = psiClass.getModifierList(); + if (classModifier == null) continue; + if (!classModifier.getText().equals("public")) continue; + + PsiMethod[] psiMethods = psiClass.findMethodsByName("onCreate", false); + if (psiMethods.length == 0) return; + + // There should be no other method with the name "onCreate" + PsiCodeBlock psiBody = psiMethods[0].getBody(); + if (psiBody != null) { + PsiStatement[] psiStatements = psiBody.getStatements(); + PsiStatement superOnCreateStatement = null; + for (PsiStatement psiStatement : psiStatements) { + if (!psiStatement.getText().contains("super.onCreate")) continue; + superOnCreateStatement = psiStatement; + break; + } - populateActivityNameListFromManifestAndInitLibrary(listpsi); - instrumentActivityFiles(); + PsiElement instrumentedElement = PsiElementFactory + .getInstance(project) + .createStatementFromText(instrumentedText, psiClass); + + if (superOnCreateStatement == null) { + WriteCommandAction.runWriteCommandAction(project, () -> { + This inserts the statemnt at the end of the oncreate. + since it is possible to make http requests in this emthod, it would be better to instrument + in the beggining of the file instead. + + psiBody.add(instrumentedElement); + }); + } else { + PsiStatement finalSuperOnCreateStatement = superOnCreateStatement; + WriteCommandAction.runWriteCommandAction(project, () -> { + psiBody.addAfter(instrumentedElement, finalSuperOnCreateStatement); + }); + } + } - // This should print "Hello" + "okFile" + <> + <> - // The actual output from the plug in is generated from - // populateActivityNameListFromManifestAndInitLibrary(listpsi); - Messages.showMessageDialog("Hello\t"+cat, "World", Messages.getInformationIcon()); + } + } + /** + * Identify all Java classes that are child of the class {@link android.app.Activity} by scanning + * all AndroidManifest files within a project. This method also identifies the main launcher activity. + * + * @return A map of java file names to a flag indicating if this file contains the main launcher activity + */ + @NotNull + private Map getAllJavaFilesWithAnActivity() { + Map javaFiles = new HashMap<>(); + PsiFile[] androidManifestFiles = FilenameIndex.getFilesByName(project, "AndroidManifest.xml", GlobalSearchScope.projectScope(project)); + + for (PsiFile psiFile : androidManifestFiles) { + XmlFile androidManifestFile = (XmlFile) psiFile; + XmlTag rootTag = androidManifestFile.getRootTag(); + + if (rootTag == null) continue; + XmlTag applicationTag = rootTag.findFirstSubTag("application"); + + if (applicationTag == null) continue; + XmlTag[] activityTags = applicationTag.findSubTags("activity"); + + for (XmlTag activityTag : activityTags) { + XmlAttribute tagAndroidName = activityTag.getAttribute("android:name"); + if (tagAndroidName == null) continue; + String activityName = tagAndroidName.getValue(); + if (activityName == null) continue; + activityName = activityName.substring(activityName.lastIndexOf(".") + 1) + ".java"; + boolean isMainLauncherActivity = activityTag.getText().contains("android.intent.action.MAIN") && + activityTag.getText().contains("android.intent.category.LAUNCHER"); + javaFiles.put(activityName, isMainLauncherActivity); + } + } + return javaFiles; } } From 903add294bcee9e713ad5baf3c68e66a86d81917 Mon Sep 17 00:00:00 2001 From: Wesley Shann Date: Wed, 10 Jun 2020 10:38:45 +0200 Subject: [PATCH 04/27] Simplify and finalize library initialization instrumentation method Issue: S2-group/NAPPA#43 --- .../action/InstrumentActivityAction.java | 44 ++++++------------- 1 file changed, 13 insertions(+), 31 deletions(-) diff --git a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java index a1b72aa3..d1798e7e 100644 --- a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java +++ b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java @@ -367,9 +367,8 @@ public void actionPerformed(AnActionEvent e) { // Messages.showMessageDialog("Hello\t" + cat, "World", Messages.getInformationIcon()); } - improve this method as it is quite messy private void addLibraryInitializationStatement(PsiJavaFile javaFile) { - String instrumentedText = "Prefetch.init(this);"; + String instrumentedText = "Prefetch.init(this, PrefetchingStrategy.STRATEGY_GREEDY);"; PsiClass[] psiClasses = javaFile.getClasses(); for (PsiClass psiClass : psiClasses) { @@ -381,40 +380,23 @@ private void addLibraryInitializationStatement(PsiJavaFile javaFile) { if (classModifier == null) continue; if (!classModifier.getText().equals("public")) continue; + // There should be exactly a single method named "onCreate" and it should not be empty PsiMethod[] psiMethods = psiClass.findMethodsByName("onCreate", false); if (psiMethods.length == 0) return; - - // There should be no other method with the name "onCreate" PsiCodeBlock psiBody = psiMethods[0].getBody(); - if (psiBody != null) { - PsiStatement[] psiStatements = psiBody.getStatements(); - PsiStatement superOnCreateStatement = null; - for (PsiStatement psiStatement : psiStatements) { - if (!psiStatement.getText().contains("super.onCreate")) continue; - superOnCreateStatement = psiStatement; - break; - } + if (psiBody == null) return; - PsiElement instrumentedElement = PsiElementFactory - .getInstance(project) - .createStatementFromText(instrumentedText, psiClass); - - if (superOnCreateStatement == null) { - WriteCommandAction.runWriteCommandAction(project, () -> { - This inserts the statemnt at the end of the oncreate. - since it is possible to make http requests in this emthod, it would be better to instrument - in the beggining of the file instead. - - psiBody.add(instrumentedElement); - }); - } else { - PsiStatement finalSuperOnCreateStatement = superOnCreateStatement; - WriteCommandAction.runWriteCommandAction(project, () -> { - psiBody.addAfter(instrumentedElement, finalSuperOnCreateStatement); - }); - } - } + PsiStatement firstStatement = psiBody.getStatements()[0]; + boolean isSuperOnCreate = firstStatement.getText().contains("super.onCreate("); + + PsiElement instrumentedElement = PsiElementFactory + .getInstance(project) + .createStatementFromText(instrumentedText, psiClass); + WriteCommandAction.runWriteCommandAction(project, () -> { + if (isSuperOnCreate) psiBody.addAfter(instrumentedElement, firstStatement); + else psiBody.addBefore(instrumentedElement, firstStatement); + }); } } From 36fbc8078442c6a258aee6074a6208ff97feb47a Mon Sep 17 00:00:00 2001 From: Wesley Shann Date: Wed, 10 Jun 2020 10:45:49 +0200 Subject: [PATCH 05/27] Add JavaDoc explaining method Issue: S2-group/NAPPA#43 --- .../action/InstrumentActivityAction.java | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java index d1798e7e..3ca49e88 100644 --- a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java +++ b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java @@ -367,7 +367,27 @@ public void actionPerformed(AnActionEvent e) { // Messages.showMessageDialog("Hello\t" + cat, "World", Messages.getInformationIcon()); } - private void addLibraryInitializationStatement(PsiJavaFile javaFile) { + /** + * This method finds the {@code onCreate()} method implemented in the main launcher + * {@link android.app.Activity} and insert an instrumented text containing the Prefetching Library + * initialization with the default Greedy Prefetching Strategy + *

+ * + *

The initialization is inserted at the top of the {@code onCreate()} method, after + * invoking the super constructor, if present, or before the first statement in the method. + *

+ * + *

The following source code is instrumented: + * + *

+     * {@code
+     * Prefetch.init(this, PrefetchingStrategy.STRATEGY_GREEDY);
+     * }
+     * 
+ * + * @param javaFile The Java file containing the main launcher {@link android.app.Activity} + */ + private void addLibraryInitializationStatement(@NotNull PsiJavaFile javaFile) { String instrumentedText = "Prefetch.init(this, PrefetchingStrategy.STRATEGY_GREEDY);"; PsiClass[] psiClasses = javaFile.getClasses(); From 339dc4586ad953613beb19325d491763c4dcac24 Mon Sep 17 00:00:00 2001 From: Wesley Shann Date: Wed, 10 Jun 2020 10:53:51 +0200 Subject: [PATCH 06/27] Restore comments explaining the script Issue: S2-group/NAPPA#43 --- .../nappa/plugin/action/InstrumentActivityAction.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java index 3ca49e88..da3bcce9 100644 --- a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java +++ b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java @@ -406,9 +406,14 @@ private void addLibraryInitializationStatement(@NotNull PsiJavaFile javaFile) { PsiCodeBlock psiBody = psiMethods[0].getBody(); if (psiBody == null) return; + // If there is a super constructor invocation, is must be in the first line of the method PsiStatement firstStatement = psiBody.getStatements()[0]; boolean isSuperOnCreate = firstStatement.getText().contains("super.onCreate("); + // This is the Element which contains the statement to connect the + // Android application's Main activity to the NAPPA Prefetching Library. + // Essentially, we add a statement which initializes Nappa at the very beginning + // of the application launch PsiElement instrumentedElement = PsiElementFactory .getInstance(project) .createStatementFromText(instrumentedText, psiClass); @@ -431,6 +436,8 @@ private Map getAllJavaFilesWithAnActivity() { Map javaFiles = new HashMap<>(); PsiFile[] androidManifestFiles = FilenameIndex.getFilesByName(project, "AndroidManifest.xml", GlobalSearchScope.projectScope(project)); + // Navigate tags until you reach the Activity Tags according to the following hierarchy + // Manifest -> application -> activity for (PsiFile psiFile : androidManifestFiles) { XmlFile androidManifestFile = (XmlFile) psiFile; XmlTag rootTag = androidManifestFile.getRootTag(); @@ -446,6 +453,8 @@ private Map getAllJavaFilesWithAnActivity() { if (tagAndroidName == null) continue; String activityName = tagAndroidName.getValue(); if (activityName == null) continue; + + // Fetch the java resource file corresponding to the activity name activityName = activityName.substring(activityName.lastIndexOf(".") + 1) + ".java"; boolean isMainLauncherActivity = activityTag.getText().contains("android.intent.action.MAIN") && activityTag.getText().contains("android.intent.category.LAUNCHER"); From 6859018fcd751ba10fec36d0c1a589c2094a23f7 Mon Sep 17 00:00:00 2001 From: Wesley Shann Date: Wed, 10 Jun 2020 10:57:48 +0200 Subject: [PATCH 07/27] Add statement to instrument the Prefetching Library import Issue: S2-group/NAPPA#43 --- .../s2group/nappa/plugin/action/InstrumentActivityAction.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java index da3bcce9..6f1bb1f2 100644 --- a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java +++ b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java @@ -11,6 +11,7 @@ import com.intellij.psi.xml.XmlAttribute; import com.intellij.psi.xml.XmlFile; import com.intellij.psi.xml.XmlTag; +import nl.vu.cs.s2group.nappa.plugin.util.InstrumentUtil; import org.jetbrains.annotations.NotNull; import java.util.HashMap; @@ -349,6 +350,7 @@ public void actionPerformed(AnActionEvent e) { PsiFile[] psiFiles = FilenameIndex.getFilesByName(project, fileName, GlobalSearchScope.projectScope(project)); for (PsiFile psiFile : psiFiles) { PsiJavaFile psiJavaFile = (PsiJavaFile) psiFile; + InstrumentUtil.addLibraryImport(project, psiJavaFile); if (isMainLauncherActivity) addLibraryInitializationStatement(psiJavaFile); } }); From d99339b1e892f3f695c4f2931d60dd740a710792 Mon Sep 17 00:00:00 2001 From: Wesley Shann Date: Wed, 10 Jun 2020 11:03:57 +0200 Subject: [PATCH 08/27] Add JavaDoc explaining method Issue: S2-group/NAPPA#43 --- .../nappa/plugin/action/InstrumentActivityAction.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java index 6f1bb1f2..8e16ad6f 100644 --- a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java +++ b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java @@ -340,9 +340,15 @@ private void instrumentActivityFiles() { } } + /** + * This Action is responsible for initializing the Prefetching Library in the main launcher + * {@link android.app.Activity} and to inject navigation probes in all {@link android.app.Activity}. + *

+ * + * @param e {@inheritDoc} + */ @Override public void actionPerformed(AnActionEvent e) { - // Open the specific project project = e.getProject(); getAllJavaFilesWithAnActivity().forEach((fileName, isMainLauncherActivity) -> { From e43c4a8a246c4b815ad4715b20ee2d3dee708453 Mon Sep 17 00:00:00 2001 From: Wesley Shann Date: Wed, 10 Jun 2020 12:45:34 +0200 Subject: [PATCH 09/27] Refactor method to inject navigation probes Issue: S2-group/NAPPA#43 --- .../action/InstrumentActivityAction.java | 99 ++++++++++++++++++- .../nappa/plugin/util/InstrumentUtil.java | 14 ++- 2 files changed, 107 insertions(+), 6 deletions(-) diff --git a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java index 8e16ad6f..bc200fe3 100644 --- a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java +++ b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java @@ -357,6 +357,7 @@ public void actionPerformed(AnActionEvent e) { for (PsiFile psiFile : psiFiles) { PsiJavaFile psiJavaFile = (PsiJavaFile) psiFile; InstrumentUtil.addLibraryImport(project, psiJavaFile); + injectNavigationProbes(psiJavaFile); if (isMainLauncherActivity) addLibraryInitializationStatement(psiJavaFile); } }); @@ -375,6 +376,96 @@ public void actionPerformed(AnActionEvent e) { // Messages.showMessageDialog("Hello\t" + cat, "World", Messages.getInformationIcon()); } + private void injectNavigationProbes(PsiJavaFile javaFile) { + String instrumentedText = "PrefetchingLib.setCurrentActivity(this);"; + PsiClass[] psiClasses = javaFile.getClasses(); + for (PsiClass psiClass : psiClasses) { + // There is only one initialization per app + if (psiClass.getText().contains(instrumentedText)) continue; + + // The library must be initialized only in the file main class + if (!InstrumentUtil.isMainPublicClass(psiClass)) continue; + + // There are three cases to inject a navigation probe + PsiMethod[] psiMethods = psiClass.findMethodsByName("onResume", false); + // Case 1. There is no method "onResume" + if (psiMethods.length == 0) injectNavigationProbesWithoutOnResumeMethod(psiClass, instrumentedText); + else { + PsiCodeBlock psiBody = psiMethods[0].getBody(); + // Case 2. There is a method "onResume" and it an empty body + // noinspection ConstantConditions > If an activity has an "onResume" method, it will always have a body + if (psiBody.getStatements().length == 0) + injectNavigationProbesWithEmptyOnResumeMethod(psiClass, psiBody, instrumentedText); + // Case 3. There is a method "onResume" and it has a non-empty body + else + injectNavigationProbesWithNonEmptyOnResumeMethod(psiClass, psiBody, instrumentedText); + } + } + } + + /** + * Inject the navigation probe to the method {@code onCreate} with empty body to the class + * + * @param psiClass Represents a Java class. + * @param psiBody Represents the body of the method {@code onCreate} found in the class + * @param instrumentedText Represents the source code to inject + */ + private void injectNavigationProbesWithEmptyOnResumeMethod(PsiClass psiClass, PsiCodeBlock psiBody, String instrumentedText) { + PsiElement instrumentedElement = PsiElementFactory + .getInstance(project) + .createStatementFromText("" + + "super.onResume();\n" + + instrumentedText, psiClass); + + WriteCommandAction.runWriteCommandAction(project, () -> { + psiBody.add(instrumentedElement); + }); + } + + /** + * Inject the navigation probe to the method {@code onCreate} with empty body to the class + * + * @param psiClass Represents a Java class. + * @param psiBody Represents the body of the method {@code onCreate} found in the class + * @param instrumentedText Represents the source code to inject + */ + private void injectNavigationProbesWithNonEmptyOnResumeMethod(PsiClass psiClass, @NotNull PsiCodeBlock psiBody, String instrumentedText) { + // If there is a super constructor invocation, is must be in the first line of the method + PsiStatement firstStatement = psiBody.getStatements()[0]; + boolean isSuperOnResume = firstStatement.getText().contains("super.onResume("); + + PsiElement instrumentedElement = PsiElementFactory + .getInstance(project) + .createStatementFromText(instrumentedText, psiClass); + + WriteCommandAction.runWriteCommandAction(project, () -> { + if (isSuperOnResume) psiBody.addAfter(instrumentedElement, firstStatement); + else psiBody.addBefore(instrumentedElement, firstStatement); + }); + } + + /** + * Inject a {@code onResume} method with the navigation probes to the class + * + * @param psiClass Represents a Java class. + * @param instrumentedText Represents the source code to inject + */ + private void injectNavigationProbesWithoutOnResumeMethod(PsiClass psiClass, String instrumentedText) { + PsiElement instrumentedElement = PsiElementFactory + .getInstance(project) + .createMethodFromText("" + + "@Override\n" + + "protected void onResume(){\n" + + "super.onResume();\n" + + instrumentedText + "\n" + + "}", psiClass); + + WriteCommandAction.runWriteCommandAction(project, () -> { + psiClass.add(instrumentedElement); + }); + } + + /** * This method finds the {@code onCreate()} method implemented in the main launcher * {@link android.app.Activity} and insert an instrumented text containing the Prefetching Library @@ -404,15 +495,13 @@ private void addLibraryInitializationStatement(@NotNull PsiJavaFile javaFile) { if (psiClass.getText().contains(instrumentedText)) break; // The library must be initialized only in the file main class - PsiModifierList classModifier = psiClass.getModifierList(); - if (classModifier == null) continue; - if (!classModifier.getText().equals("public")) continue; + if (!InstrumentUtil.isMainPublicClass(psiClass)) continue; // There should be exactly a single method named "onCreate" and it should not be empty PsiMethod[] psiMethods = psiClass.findMethodsByName("onCreate", false); - if (psiMethods.length == 0) return; + if (psiMethods.length == 0) break; PsiCodeBlock psiBody = psiMethods[0].getBody(); - if (psiBody == null) return; + if (psiBody == null) break; // If there is a super constructor invocation, is must be in the first line of the method PsiStatement firstStatement = psiBody.getStatements()[0]; diff --git a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/util/InstrumentUtil.java b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/util/InstrumentUtil.java index 3c786d2d..e140b31c 100644 --- a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/util/InstrumentUtil.java +++ b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/util/InstrumentUtil.java @@ -25,7 +25,8 @@ public final class InstrumentUtil { * @param project An object representing an IntelliJ project. * @return A list of all Java source files in the project */ - public static @NotNull List getAllJavaFilesInProjectAsPsi(Project project) { + public static @NotNull + List getAllJavaFilesInProjectAsPsi(Project project) { List psiFiles = new LinkedList<>(); String[] fileNames = FilenameIndex.getAllFilenames(project); @@ -134,4 +135,15 @@ private static void runFullScanOnJavaClass(@NotNull PsiClass psiClass, String[] el = el.getParent(); } } + + /** + * Verifies if a class is the main public class + * + * @param psiClass Represents a Java class or interface. + * @return {@code True} if it is the main class or {@code False} otherwise + */ + public static boolean isMainPublicClass(@NotNull PsiClass psiClass) { + PsiModifierList classModifier = psiClass.getModifierList(); + return classModifier != null && classModifier.getText().equals("public"); + } } From f08c7ed00295206986540a6a2590e31efdb9b50e Mon Sep 17 00:00:00 2001 From: Wesley Shann Date: Wed, 10 Jun 2020 13:06:23 +0200 Subject: [PATCH 10/27] Describe navigation probes injection via JavaDoc Issue: S2-group/NAPPA#43 --- .../action/InstrumentActivityAction.java | 49 ++++++++++++++++--- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java index bc200fe3..28fc35dd 100644 --- a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java +++ b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java @@ -376,7 +376,42 @@ public void actionPerformed(AnActionEvent e) { // Messages.showMessageDialog("Hello\t" + cat, "World", Messages.getInformationIcon()); } - private void injectNavigationProbes(PsiJavaFile javaFile) { + /** + * This method finds the {@code onResume()} method implemented in an {@link android.app.Activity} and + * insert an instrumented text to add the navigation probes.There are three instrumentation cases for + * injecting the navigation probes. + *

+ * + *

Case 1. The {@link android.app.Activity} don't have the method {@code onResume()}. In this case, + * the method is injected containing the super constructor and the navigation probe. The injected code + * is as follows: + * + *

{@code
+     * @Override
+     * protected void onResume() {
+     *     super.onResume();
+     *     PrefetchingLib.setCurrentActivity(this);
+     * }
+     * }
+ * + *

Case 2. The {@link android.app.Activity} has a method {@code onResume()} with existing source + * code. In this case, the navigation probe is inserted at the top of the method {@code onResume()}, + * after invoking the super constructor, if it present, or before the first statement in the method. + * The injected code is as follows: + * + *

{@code PrefetchingLib.setCurrentActivity(this);}
+ * + *

Case 3. The {@link android.app.Activity} has an empty method {@code onResume()}. In this case, + * the super constructor is injected together with the navigation probe. The injected code is as follows: + * + *

{@code
+     * super.onResume();
+     * PrefetchingLib.setCurrentActivity(this);
+     * }
+ * + * @param javaFile The Java file containing the an {@link android.app.Activity} + */ + private void injectNavigationProbes(@NotNull PsiJavaFile javaFile) { String instrumentedText = "PrefetchingLib.setCurrentActivity(this);"; PsiClass[] psiClasses = javaFile.getClasses(); for (PsiClass psiClass : psiClasses) { @@ -393,7 +428,9 @@ private void injectNavigationProbes(PsiJavaFile javaFile) { else { PsiCodeBlock psiBody = psiMethods[0].getBody(); // Case 2. There is a method "onResume" and it an empty body - // noinspection ConstantConditions > If an activity has an "onResume" method, it will always have a body + // Only interfaces and abstracts methods don't have a body. + // The method "onResume" will always have a body. + // noinspection ConstantConditions if (psiBody.getStatements().length == 0) injectNavigationProbesWithEmptyOnResumeMethod(psiClass, psiBody, instrumentedText); // Case 3. There is a method "onResume" and it has a non-empty body @@ -455,7 +492,7 @@ private void injectNavigationProbesWithoutOnResumeMethod(PsiClass psiClass, Stri .getInstance(project) .createMethodFromText("" + "@Override\n" + - "protected void onResume(){\n" + + "protected void onResume() {\n" + "super.onResume();\n" + instrumentedText + "\n" + "}", psiClass); @@ -478,11 +515,7 @@ private void injectNavigationProbesWithoutOnResumeMethod(PsiClass psiClass, Stri * *

The following source code is instrumented: * - *

-     * {@code
-     * Prefetch.init(this, PrefetchingStrategy.STRATEGY_GREEDY);
-     * }
-     * 
+ *
{@code Prefetch.init(this, PrefetchingStrategy.STRATEGY_GREEDY);}
* * @param javaFile The Java file containing the main launcher {@link android.app.Activity} */ From b659ed2eab089529906fcb0533d14d0dd5895cd4 Mon Sep 17 00:00:00 2001 From: Wesley Shann Date: Wed, 10 Jun 2020 13:17:43 +0200 Subject: [PATCH 11/27] Remove the reference source code Issue: S2-group/NAPPA#43 --- .../action/InstrumentActivityAction.java | 328 ------------------ 1 file changed, 328 deletions(-) diff --git a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java index 28fc35dd..f5748984 100644 --- a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java +++ b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java @@ -4,7 +4,6 @@ import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.Messages; import com.intellij.psi.*; import com.intellij.psi.search.FilenameIndex; import com.intellij.psi.search.GlobalSearchScope; @@ -15,8 +14,6 @@ import org.jetbrains.annotations.NotNull; import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; import java.util.Map; /** @@ -26,320 +23,8 @@ * library */ public class InstrumentActivityAction extends AnAction { - - private String cat = "okfile"; - private List javaActivityNameList = new LinkedList<>(); private Project project; - /** - * This detects the activitis from the manifest file and also initializes the prefetching library. - * - * @param listpsi: A list containing references to all the AndroidManifest Files found within a project - */ - private void populateActivityNameListFromManifestAndInitLibrary(PsiFile[] listpsi) { - javaActivityNameList.clear(); - - // Iterate through all Manifest Files detected within the project - for (PsiFile file1 : listpsi) { - cat += "\n" + file1.getVirtualFile().getPath() + "\n"; - PsiElement psiElement = file1.findElementAt(0); - - // Treat the Android.Xml file as an XML structure - XmlFile xmlFile = (XmlFile) file1; - try { - - // Navigate tags until you reach the Activity Tags according to the following hierarchy - // (Manifest ) -> (application) -> activity - String package_name = xmlFile.getRootTag().getAttribute("package").getValue(); - - XmlTag applicationTag = xmlFile.getRootTag().findFirstSubTag("application"); - - XmlTag[] activityTags = applicationTag.findSubTags("activity"); - - // Iterate through each activity tag - for (XmlTag tag : activityTags) { - // Add the activity name as defined in the manifest - cat += "\n" + tag.getAttribute("android:name").getValue(); - try { - // Fetch the java resource file corresponding to the activity name - String[] names = tag.getAttribute("android:name").getValue().split("\\."); - String name = names[names.length - 1] + ".java"; - javaActivityNameList.add(name); - - // Append the related filename - cat += " " + name; - - //To determine whether this activity is the main activity and the launcher activity - boolean isMain = false; - boolean isLauncher = false; - - // Find the intent filter tag in order to be able to DETERMINE whether - // this activity is the main activity AND/OR the launcher activity - XmlTag[] intentFilterTags = tag.findSubTags("intent-filter"); - - for (XmlTag intentFilterTag : intentFilterTags) { - // Find the relevant Action and category subtags - XmlTag[] actionTags = intentFilterTag.findSubTags("action"); - XmlTag[] categoryTags = intentFilterTag.findSubTags("category"); - - - // Update the boolean for a given activity as MAIN activity and LAUNCHER - // activity if this is found to be present for a given activity. - // There can only be one of each - for (XmlTag actionTag : actionTags) { - if (actionTag.getAttribute("android:name") != null && - actionTag.getAttribute("android:name").getValue().compareTo("android.intent.action.MAIN") == 0) { - isMain = true; - break; - } - } - for (XmlTag categoryTag : categoryTags) { - if (categoryTag.getAttribute("android:name") != null && - categoryTag.getAttribute("android:name").getValue().compareTo("android.intent.category.LAUNCHER") == 0) { - isLauncher = true; - break; - } - } - - - } - - cat += name + "\n"; - cat += "isMain? " + isMain + "\n"; - cat += "isLauncher? " + isLauncher + "\n"; - - // Consider only the Main activity - if (isMain && isLauncher) { - // Open the Java File corresponding to the main activity - PsiFile[] listActJava = FilenameIndex.getFilesByName(project, name, GlobalSearchScope.projectScope(project)); - for (PsiFile psiFile : listActJava) { - // Open the activity's Java file and extract the set of all the classes within it - if (psiFile instanceof PsiJavaFile) { - - PsiJavaFile javaFile = (PsiJavaFile) psiFile; - - // FIXME: How do we know that the class the first class defined in the Java file is effectively the class we are looking for? - // There can only be one public class per java file, given that public classes must have the same name as - // the resource file. But there may be more private classes within - PsiClass psiClass = javaFile.getClasses()[0]; - - // Find the onCreate Callback method - PsiMethod[] psiMethods = psiClass.findMethodsByName("onCreate", false); - if (psiMethods.length > 0) { - try { - - /** IMPORTATN: This is the Element which contatins the statement to connect the - * Android application's Main activity to the NAPPA Prefetching Library. - * Essentially, we add a statement which initializes Nappa at the very beginning - * of the application launch - * */ - final PsiElement psiElementToAdd = PsiElementFactory.SERVICE.getInstance(project).createStatementFromText( - "PrefetchingLib.init(this);", psiClass); - - // Get the statements of the onCreate callback - PsiStatement[] psiStatements = psiMethods[0].getBody().getStatements(); - PsiElement onCreateElement = null; - - boolean addStatement = true; - - /** - * This loop determines whether a reference to the prefetch lib will be - * added to the main activity or not. - */ - for (PsiStatement statement : psiStatements) { - // Find the Super OnCreate Statement and store it as a - // PsiElement to be used later in order to inject the PreFetch Lib initializer - // If super.onCreate constructor is never found, this reference is not used and the - // injected code is added at the start of the onCreate method - if (statement.getText().startsWith("super.onCreate")) { - onCreateElement = statement; - } - // Check if the statement to be injected (the injection to - // link to the prefetch lib) already exists in the - // project. ALSO print all the statements within - // this class - if (statement.isEquivalentTo(psiElementToAdd)) { - addStatement = false; - cat += "FOUND IN FIRST ELSE\n"; - //break; - } else if (statement.textMatches(psiElementToAdd)) { - addStatement = false; - cat += "FOUND IN SECOND ELSE\n"; - //break; - } else { - cat += "\n" + statement.getText() + "\n"; - } - } - - // Keep track of the reference (element) to the super.oncreate - // statement - final PsiElement onCreateElementFinal = onCreateElement; - - /** - * This is where the reference to the prefetching lib is added, assuming - * that the previous loop determines it is necessary to do so. - */ - if (addStatement) { - cat += "\nADDING INIT PREFETCHING LIB IN " + name + "\n\n"; - - // Inject Prefetch Lib to the project - WriteCommandAction.runWriteCommandAction(project, () -> { - // Super.oncreate constructor found / not found scenarios - if (onCreateElementFinal != null) { - psiMethods[0].getBody().addAfter(psiElementToAdd, onCreateElementFinal); - } else { - psiMethods[0].getBody().add(psiElementToAdd); - } - }); - } else { - cat += "\nINIT PREFETCHING LIB NOT ADDED in " + name + "\n\n"; - } - } catch (Exception e) { - cat += "\n" + e.getMessage() + "\n\n"; - e.printStackTrace(); - } - } - } - } - - } - - } catch (Exception e2) { - cat += " " + e2.toString(); - } - - } - } catch (Exception e1) { - e1.printStackTrace(); - cat += " error in tag reading\n"; - } - } - - } - - private void instrumentActivityFiles() { - // Iterate through all the java resource files corresponding to each activity in the Android - // App. Usin the name of the files collected in the previous function - for (String javaActivityName : javaActivityNameList) { - // Fetch the file in PSI structure - PsiFile[] listActJava = FilenameIndex.getFilesByName(project, javaActivityName, GlobalSearchScope.projectScope(project)); - - /** - * IMPORTANT: This iterates through every activity's corresponding resouce file in order - * to instrument each activity to make use of the Prefetching lib. - */ - for (PsiFile actJava : listActJava) { - if (actJava instanceof PsiJavaFile) { - PsiJavaFile javaFile = (PsiJavaFile) actJava; - // FIXME: Assumption of first defined class being the public class - PsiClass psiClass = javaFile.getClasses()[0]; - - /** IMPORTATN: This is the Element which contains the statement to import - * the prefetching lib's package - * */ - final PsiElement importToAdd = PsiElementFactory.SERVICE.getInstance(project).createImportStatementOnDemand( - "nl.vu.cs.s2group.nappa"); - - // Fetch all the import statements for this java file - PsiImportList importList = javaFile.getImportList(); - PsiImportStatement[] importStatements = importList.getImportStatements(); - - boolean imported = false; - - // Check to see that the prefetch lib import has already taken place, to - // avoid duplicates - for (PsiImportStatement importStatement : importStatements) { - if (importStatement.getText().compareTo("import nl.vu.cs.s2group.nappa*;") == 0) { - imported = true; - break; - } - } - - cat += "\nIMPORT FOUND FOR CLASS " + javaActivityName + ": " + imported + "\n\n"; - - // If the prefetcht lib import has not been found in the import list, add it now - if (!imported) { - WriteCommandAction.runWriteCommandAction(project, () -> { - importList.add(importToAdd); - }); - } - - // write the full name of the class (inclulding full package description). - cat += " " + psiClass.getQualifiedName() + " "; - // Fetch onResume callbackmethod - PsiMethod[] psiMethods = psiClass.findMethodsByName("onResume", false); - - // Determine whether onResume was found or not - cat += " osResume() "; - cat += psiMethods.length > 0 ? "found\n" : "not found\n"; - - - /** - * IMPORTANT: This is where the prefetching lib adds the PrefetchingLib transition to - * the next activity. This is done by instrumenting the onResume Callback. If the - * callback is missing, then the callback is added to the activity. - */ - if (psiMethods.length > 0) { - - try { - // Determine if On Resume is writable - cat += psiMethods[0].getBody().isWritable() ? "writable" : "not writable"; - cat += "\n"; - - // THis is the statement added to instrument intent transitions - final PsiElement psiElementToAdd = PsiElementFactory.SERVICE.getInstance(project).createStatementFromText( - // "System.out.println(\"ciao amico\");", psiClass); - "PrefetchingLib.setCurrentActivity(this);", psiClass); - - - PsiStatement[] psiStatements = psiMethods[0].getBody().getStatements(); - boolean addStatement = true; - - for (PsiStatement statement : psiStatements) { - if (statement.isEquivalentTo(psiElementToAdd)) { - addStatement = false; - break; - } else if (statement.textMatches(psiElementToAdd)) { - addStatement = false; - break; - } else { - cat += "\n" + statement.getText() + "\n\n"; - } - } - - if (addStatement) { - WriteCommandAction.runWriteCommandAction(project, () -> { - psiMethods[0].getBody().add( - //PsiElementFactory.SERVICE.getInstance(project).createExpressionFromText("System.out.println(\"ciao amico\");", psiClass) - //PsiElementFactory.SERVICE.getInstance(project).createStatementFromText("System.out.println(\"ciao amico\");", psiClass) - psiElementToAdd - ); - }); - } else { - cat += "\nNOT ADDED in " + javaActivityName + "\n\n"; - } - - } catch (Exception e3) { - cat += e3.toString() + "\n"; - } - - } else { //NO onResume METHOD FOUND - WriteCommandAction.runWriteCommandAction(project, () -> { - psiClass.add( - PsiElementFactory.SERVICE.getInstance(project).createMethodFromText("@Override\n" + - "protected void onResume(){\n" + - "super.onResume();\n" + - "PrefetchingLib.setCurrentActivity(this);\n" + - "}", psiClass) - ); - }); - } - - } - } - } - } - /** * This Action is responsible for initializing the Prefetching Library in the main launcher * {@link android.app.Activity} and to inject navigation probes in all {@link android.app.Activity}. @@ -361,19 +46,6 @@ public void actionPerformed(AnActionEvent e) { if (isMainLauncherActivity) addLibraryInitializationStatement(psiJavaFile); } }); - - // OPEN the Android Manifest File(s), and process each one of them in order to instrument them (project level) -// PsiFile[] listpsi = FilenameIndex.getFilesByName(project, "AndroidManifest.xml", GlobalSearchScope.projectScope(project)); -// -// /**** Activity Instrumentation begins here ***/ -// -// populateActivityNameListFromManifestAndInitLibrary(listpsi); -// instrumentActivityFiles(); -// -// // This should print "Hello" + "okFile" + <> + <> -// // The actual output from the plug in is generated from -// // populateActivityNameListFromManifestAndInitLibrary(listpsi); -// Messages.showMessageDialog("Hello\t" + cat, "World", Messages.getInformationIcon()); } /** From 659e2d8d4039bc53ff6a90ad2cd900d07f386fcf Mon Sep 17 00:00:00 2001 From: Wesley Shann Date: Wed, 10 Jun 2020 13:18:13 +0200 Subject: [PATCH 12/27] Add annotation Issue: S2-group/NAPPA#43 --- .../s2group/nappa/plugin/action/InstrumentActivityAction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java index f5748984..37972bf2 100644 --- a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java +++ b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java @@ -33,7 +33,7 @@ public class InstrumentActivityAction extends AnAction { * @param e {@inheritDoc} */ @Override - public void actionPerformed(AnActionEvent e) { + public void actionPerformed(@NotNull AnActionEvent e) { project = e.getProject(); getAllJavaFilesWithAnActivity().forEach((fileName, isMainLauncherActivity) -> { From 41b5491c4abb85dc2e71a09d2abaa52b62067020 Mon Sep 17 00:00:00 2001 From: Wesley Shann Date: Wed, 10 Jun 2020 13:51:59 +0200 Subject: [PATCH 13/27] Add result messages Issue: S2-group/NAPPA#43 * Overview stats * Instrumented classes * Instrumented methods --- .../action/InstrumentActivityAction.java | 44 ++++++++++++++++--- .../plugin/util/InstrumentResultMessage.java | 14 +++++- 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java index 37972bf2..00b1eaa8 100644 --- a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java +++ b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java @@ -10,6 +10,7 @@ import com.intellij.psi.xml.XmlAttribute; import com.intellij.psi.xml.XmlFile; import com.intellij.psi.xml.XmlTag; +import nl.vu.cs.s2group.nappa.plugin.util.InstrumentResultMessage; import nl.vu.cs.s2group.nappa.plugin.util.InstrumentUtil; import org.jetbrains.annotations.NotNull; @@ -24,6 +25,7 @@ */ public class InstrumentActivityAction extends AnAction { private Project project; + private InstrumentResultMessage resultMessage; /** * This Action is responsible for initializing the Prefetching Library in the main launcher @@ -35,24 +37,31 @@ public class InstrumentActivityAction extends AnAction { @Override public void actionPerformed(@NotNull AnActionEvent e) { project = e.getProject(); + resultMessage = new InstrumentResultMessage(); getAllJavaFilesWithAnActivity().forEach((fileName, isMainLauncherActivity) -> { System.out.println(fileName + " -> " + isMainLauncherActivity); PsiFile[] psiFiles = FilenameIndex.getFilesByName(project, fileName, GlobalSearchScope.projectScope(project)); for (PsiFile psiFile : psiFiles) { + resultMessage.incrementPossibleInstrumentationCount(); PsiJavaFile psiJavaFile = (PsiJavaFile) psiFile; InstrumentUtil.addLibraryImport(project, psiJavaFile); injectNavigationProbes(psiJavaFile); - if (isMainLauncherActivity) addLibraryInitializationStatement(psiJavaFile); + if (isMainLauncherActivity) { + resultMessage.incrementPossibleInstrumentationCount(); + addLibraryInitializationStatement(psiJavaFile); + } } }); + + resultMessage.getMessage(); } /** * This method finds the {@code onResume()} method implemented in an {@link android.app.Activity} and * insert an instrumented text to add the navigation probes.There are three instrumentation cases for * injecting the navigation probes. - *

+ *

git che * *

Case 1. The {@link android.app.Activity} don't have the method {@code onResume()}. In this case, * the method is injected containing the super constructor and the navigation probe. The injected code @@ -88,7 +97,10 @@ private void injectNavigationProbes(@NotNull PsiJavaFile javaFile) { PsiClass[] psiClasses = javaFile.getClasses(); for (PsiClass psiClass : psiClasses) { // There is only one initialization per app - if (psiClass.getText().contains(instrumentedText)) continue; + if (psiClass.getText().contains(instrumentedText)) { + resultMessage.incrementAlreadyInstrumentedCount(); + break; + } // The library must be initialized only in the file main class if (!InstrumentUtil.isMainPublicClass(psiClass)) continue; @@ -96,8 +108,14 @@ private void injectNavigationProbes(@NotNull PsiJavaFile javaFile) { // There are three cases to inject a navigation probe PsiMethod[] psiMethods = psiClass.findMethodsByName("onResume", false); // Case 1. There is no method "onResume" - if (psiMethods.length == 0) injectNavigationProbesWithoutOnResumeMethod(psiClass, instrumentedText); - else { + if (psiMethods.length == 0) { + injectNavigationProbesWithoutOnResumeMethod(psiClass, instrumentedText); + + resultMessage.incrementInstrumentationCount() + .appendPsiClass(psiClass) + .appendText("Override method \"onResume()\"") + .appendNewBlock(); + } else { PsiCodeBlock psiBody = psiMethods[0].getBody(); // Case 2. There is a method "onResume" and it an empty body // Only interfaces and abstracts methods don't have a body. @@ -108,7 +126,13 @@ private void injectNavigationProbes(@NotNull PsiJavaFile javaFile) { // Case 3. There is a method "onResume" and it has a non-empty body else injectNavigationProbesWithNonEmptyOnResumeMethod(psiClass, psiBody, instrumentedText); + + resultMessage.incrementInstrumentationCount() + .appendPsiClass(psiClass) + .appendPsiMethod(psiMethods[0]) + .appendNewBlock(); } + } } @@ -197,7 +221,10 @@ private void addLibraryInitializationStatement(@NotNull PsiJavaFile javaFile) { for (PsiClass psiClass : psiClasses) { // There is only one initialization per app - if (psiClass.getText().contains(instrumentedText)) break; + if (psiClass.getText().contains(instrumentedText)) { + resultMessage.incrementAlreadyInstrumentedCount(); + break; + } // The library must be initialized only in the file main class if (!InstrumentUtil.isMainPublicClass(psiClass)) continue; @@ -220,6 +247,11 @@ private void addLibraryInitializationStatement(@NotNull PsiJavaFile javaFile) { .getInstance(project) .createStatementFromText(instrumentedText, psiClass); + resultMessage.incrementInstrumentationCount() + .appendPsiClass(psiClass) + .appendPsiMethod(psiMethods[0]) + .appendNewBlock(); + WriteCommandAction.runWriteCommandAction(project, () -> { if (isSuperOnCreate) psiBody.addAfter(instrumentedElement, firstStatement); else psiBody.addBefore(instrumentedElement, firstStatement); diff --git a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/util/InstrumentResultMessage.java b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/util/InstrumentResultMessage.java index edcb5002..13896e76 100644 --- a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/util/InstrumentResultMessage.java +++ b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/util/InstrumentResultMessage.java @@ -134,7 +134,7 @@ public InstrumentResultMessage appendPsiClass(@NotNull PsiClass psiClass) { } /** - * Append the class or interface qualified name to the result message + * Append the method or constructor qualified name to the result message * * @param psiMethod A Java method or constructor. * @return A instance of this object @@ -145,6 +145,18 @@ public InstrumentResultMessage appendPsiMethod(@NotNull PsiMethod psiMethod) { return this; } + /** + * Append a free text to the result message + * + * @param text The text to append. + * @return A instance of this object + */ + @SuppressWarnings("UnusedReturnValue") + public InstrumentResultMessage appendText(String text) { + builder.append(text).append("\n"); + return this; + } + /** * Append a string to inform the class initializer was instrumented to the result message * From 3e92c2e9c2f4c9de136264c669edafd27d532131 Mon Sep 17 00:00:00 2001 From: Wesley Shann Date: Wed, 10 Jun 2020 13:54:34 +0200 Subject: [PATCH 14/27] Encapsulate action in try/catch block to handle exceptions Issue: S2-group/NAPPA#43 --- .../action/InstrumentActivityAction.java | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java index 00b1eaa8..5b84c741 100644 --- a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java +++ b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java @@ -39,22 +39,25 @@ public void actionPerformed(@NotNull AnActionEvent e) { project = e.getProject(); resultMessage = new InstrumentResultMessage(); - getAllJavaFilesWithAnActivity().forEach((fileName, isMainLauncherActivity) -> { - System.out.println(fileName + " -> " + isMainLauncherActivity); - PsiFile[] psiFiles = FilenameIndex.getFilesByName(project, fileName, GlobalSearchScope.projectScope(project)); - for (PsiFile psiFile : psiFiles) { - resultMessage.incrementPossibleInstrumentationCount(); - PsiJavaFile psiJavaFile = (PsiJavaFile) psiFile; - InstrumentUtil.addLibraryImport(project, psiJavaFile); - injectNavigationProbes(psiJavaFile); - if (isMainLauncherActivity) { + try { + getAllJavaFilesWithAnActivity().forEach((fileName, isMainLauncherActivity) -> { + System.out.println(fileName + " -> " + isMainLauncherActivity); + PsiFile[] psiFiles = FilenameIndex.getFilesByName(project, fileName, GlobalSearchScope.projectScope(project)); + for (PsiFile psiFile : psiFiles) { resultMessage.incrementPossibleInstrumentationCount(); - addLibraryInitializationStatement(psiJavaFile); + PsiJavaFile psiJavaFile = (PsiJavaFile) psiFile; + InstrumentUtil.addLibraryImport(project, psiJavaFile); + injectNavigationProbes(psiJavaFile); + if (isMainLauncherActivity) { + resultMessage.incrementPossibleInstrumentationCount(); + addLibraryInitializationStatement(psiJavaFile); + } } - } - }); - - resultMessage.getMessage(); + }); + resultMessage.showResultDialog(project, "Navigation Probes Instrumentation Result"); + } catch (Exception exception) { + resultMessage.showErrorDialog(project, exception, "Failed to Instrument Navigation Probes"); + } } /** From 792fbbeb125f8cc084ceee8558d842e4cf014f58 Mon Sep 17 00:00:00 2001 From: Wesley Shann Date: Wed, 10 Jun 2020 14:01:10 +0200 Subject: [PATCH 15/27] Create a method for logging overridden methods Issue: S2-group/NAPPA#43 --- .../plugin/action/InstrumentActivityAction.java | 17 ++++++++--------- .../plugin/util/InstrumentResultMessage.java | 15 ++++++++++++++- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java index 5b84c741..47b75a79 100644 --- a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java +++ b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java @@ -111,14 +111,8 @@ private void injectNavigationProbes(@NotNull PsiJavaFile javaFile) { // There are three cases to inject a navigation probe PsiMethod[] psiMethods = psiClass.findMethodsByName("onResume", false); // Case 1. There is no method "onResume" - if (psiMethods.length == 0) { - injectNavigationProbesWithoutOnResumeMethod(psiClass, instrumentedText); - - resultMessage.incrementInstrumentationCount() - .appendPsiClass(psiClass) - .appendText("Override method \"onResume()\"") - .appendNewBlock(); - } else { + if (psiMethods.length == 0) injectNavigationProbesWithoutOnResumeMethod(psiClass, instrumentedText); + else { PsiCodeBlock psiBody = psiMethods[0].getBody(); // Case 2. There is a method "onResume" and it an empty body // Only interfaces and abstracts methods don't have a body. @@ -187,7 +181,7 @@ private void injectNavigationProbesWithNonEmptyOnResumeMethod(PsiClass psiClass, * @param instrumentedText Represents the source code to inject */ private void injectNavigationProbesWithoutOnResumeMethod(PsiClass psiClass, String instrumentedText) { - PsiElement instrumentedElement = PsiElementFactory + PsiMethod instrumentedElement = PsiElementFactory .getInstance(project) .createMethodFromText("" + "@Override\n" + @@ -196,6 +190,11 @@ private void injectNavigationProbesWithoutOnResumeMethod(PsiClass psiClass, Stri instrumentedText + "\n" + "}", psiClass); + resultMessage.incrementInstrumentationCount() + .appendPsiClass(psiClass) + .appendOverridePsiMethod(instrumentedElement) + .appendNewBlock(); + WriteCommandAction.runWriteCommandAction(project, () -> { psiClass.add(instrumentedElement); }); diff --git a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/util/InstrumentResultMessage.java b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/util/InstrumentResultMessage.java index 13896e76..ba3bd9a8 100644 --- a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/util/InstrumentResultMessage.java +++ b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/util/InstrumentResultMessage.java @@ -146,7 +146,20 @@ public InstrumentResultMessage appendPsiMethod(@NotNull PsiMethod psiMethod) { } /** - * Append a free text to the result message + * Append the overridden method or constructor qualified name to the result message + * + * @param psiMethod A Java method or constructor. + * @return A instance of this object + */ + @SuppressWarnings("UnusedReturnValue") + public InstrumentResultMessage appendOverridePsiMethod(@NotNull PsiMethod psiMethod) { + builder.append("Override method: ").append(psiMethod.getName()).append("\n"); + return this; + } + + /** + * Append a free text to the result message. If possible, prefer to use the specific append + * methods or create your own append method here. * * @param text The text to append. * @return A instance of this object From 4d16f2e30f96a27d328f2f9a969ad86db88942af Mon Sep 17 00:00:00 2001 From: Wesley Shann Date: Wed, 10 Jun 2020 14:16:12 +0200 Subject: [PATCH 16/27] Fix incorrect JavaDoc Issue: S2-group/NAPPA#43 --- .../nappa/plugin/action/InstrumentActivityAction.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java index 47b75a79..2341033e 100644 --- a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java +++ b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java @@ -134,10 +134,10 @@ private void injectNavigationProbes(@NotNull PsiJavaFile javaFile) { } /** - * Inject the navigation probe to the method {@code onCreate} with empty body to the class + * Inject the navigation probe to the method {@code onResume} with empty body to the class * * @param psiClass Represents a Java class. - * @param psiBody Represents the body of the method {@code onCreate} found in the class + * @param psiBody Represents the body of the method {@code onResume} found in the class * @param instrumentedText Represents the source code to inject */ private void injectNavigationProbesWithEmptyOnResumeMethod(PsiClass psiClass, PsiCodeBlock psiBody, String instrumentedText) { @@ -153,10 +153,10 @@ private void injectNavigationProbesWithEmptyOnResumeMethod(PsiClass psiClass, Ps } /** - * Inject the navigation probe to the method {@code onCreate} with empty body to the class + * Inject the navigation probe to the method {@code onResume} containing existing code to the class * * @param psiClass Represents a Java class. - * @param psiBody Represents the body of the method {@code onCreate} found in the class + * @param psiBody Represents the body of the method {@code onResume} found in the class * @param instrumentedText Represents the source code to inject */ private void injectNavigationProbesWithNonEmptyOnResumeMethod(PsiClass psiClass, @NotNull PsiCodeBlock psiBody, String instrumentedText) { From 4631d6577911223acfd962052a08f11ddd38e911 Mon Sep 17 00:00:00 2001 From: Wesley Shann Date: Wed, 10 Jun 2020 15:01:05 +0200 Subject: [PATCH 17/27] Store only the activity name in the hash Issue: S2-group/NAPPA#43 --- .../nappa/plugin/action/InstrumentActivityAction.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java index 2341033e..76e23daa 100644 --- a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java +++ b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java @@ -40,9 +40,9 @@ public void actionPerformed(@NotNull AnActionEvent e) { resultMessage = new InstrumentResultMessage(); try { - getAllJavaFilesWithAnActivity().forEach((fileName, isMainLauncherActivity) -> { - System.out.println(fileName + " -> " + isMainLauncherActivity); - PsiFile[] psiFiles = FilenameIndex.getFilesByName(project, fileName, GlobalSearchScope.projectScope(project)); + getAllJavaFilesWithAnActivity().forEach((activityName, isMainLauncherActivity) -> { + System.out.println(activityName + " -> " + isMainLauncherActivity); + PsiFile[] psiFiles = FilenameIndex.getFilesByName(project, activityName + ".java", GlobalSearchScope.projectScope(project)); for (PsiFile psiFile : psiFiles) { resultMessage.incrementPossibleInstrumentationCount(); PsiJavaFile psiJavaFile = (PsiJavaFile) psiFile; @@ -291,7 +291,7 @@ private Map getAllJavaFilesWithAnActivity() { if (activityName == null) continue; // Fetch the java resource file corresponding to the activity name - activityName = activityName.substring(activityName.lastIndexOf(".") + 1) + ".java"; + activityName = activityName.substring(activityName.lastIndexOf(".") + 1); boolean isMainLauncherActivity = activityTag.getText().contains("android.intent.action.MAIN") && activityTag.getText().contains("android.intent.category.LAUNCHER"); javaFiles.put(activityName, isMainLauncherActivity); From ec16770b760643c8a5ff19ca7d3d026665d56cdb Mon Sep 17 00:00:00 2001 From: Wesley Shann Date: Wed, 10 Jun 2020 15:12:22 +0200 Subject: [PATCH 18/27] Remove println statement Issue: S2-group/NAPPA#43 --- .../cs/s2group/nappa/plugin/action/InstrumentActivityAction.java | 1 - 1 file changed, 1 deletion(-) diff --git a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java index 76e23daa..22f1e333 100644 --- a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java +++ b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java @@ -41,7 +41,6 @@ public void actionPerformed(@NotNull AnActionEvent e) { try { getAllJavaFilesWithAnActivity().forEach((activityName, isMainLauncherActivity) -> { - System.out.println(activityName + " -> " + isMainLauncherActivity); PsiFile[] psiFiles = FilenameIndex.getFilesByName(project, activityName + ".java", GlobalSearchScope.projectScope(project)); for (PsiFile psiFile : psiFiles) { resultMessage.incrementPossibleInstrumentationCount(); From 9952b266e5b8de60b7952736d01f0347a918ee10 Mon Sep 17 00:00:00 2001 From: Wesley Shann Date: Wed, 10 Jun 2020 15:19:32 +0200 Subject: [PATCH 19/27] Upgrade version and set group ID as package name Issue: S2-group/NAPPA#43 --- Android-Studio-Plugin/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Android-Studio-Plugin/build.gradle b/Android-Studio-Plugin/build.gradle index 5c81940a..de27e8b7 100644 --- a/Android-Studio-Plugin/build.gradle +++ b/Android-Studio-Plugin/build.gradle @@ -4,8 +4,8 @@ plugins { id "org.sonarqube" version "2.8" } -group 'test' -version '1.1' +group 'nl.vu.cs.s2group.nappa.plugin' +version '1.2.0' sourceCompatibility = 1.8 From 71b66484f1aa090b4f24df0c2ca4cf4535224577 Mon Sep 17 00:00:00 2001 From: Wesley Shann Date: Wed, 10 Jun 2020 15:26:34 +0200 Subject: [PATCH 20/27] Fix instrumented statement text Issue: S2-group/NAPPA#43 --- .../s2group/nappa/plugin/action/InstrumentActivityAction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java index 22f1e333..2ef4c02e 100644 --- a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java +++ b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/action/InstrumentActivityAction.java @@ -217,7 +217,7 @@ private void injectNavigationProbesWithoutOnResumeMethod(PsiClass psiClass, Stri * @param javaFile The Java file containing the main launcher {@link android.app.Activity} */ private void addLibraryInitializationStatement(@NotNull PsiJavaFile javaFile) { - String instrumentedText = "Prefetch.init(this, PrefetchingStrategy.STRATEGY_GREEDY);"; + String instrumentedText = "PrefetchingLib.init(this, PrefetchingStrategy.STRATEGY_GREEDY);"; PsiClass[] psiClasses = javaFile.getClasses(); for (PsiClass psiClass : psiClasses) { From 0d83e1de576ce2aa593b96af14403e14a0f25c9b Mon Sep 17 00:00:00 2001 From: Wesley Shann Date: Wed, 10 Jun 2020 15:38:42 +0200 Subject: [PATCH 21/27] Verify if the class modifier contains the word public Issue: S2-group/NAPPA#43 If the class defines modifies such as final or static, the strict equals comparator will fail --- .../java/nl/vu/cs/s2group/nappa/plugin/util/InstrumentUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/util/InstrumentUtil.java b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/util/InstrumentUtil.java index e140b31c..5709a400 100644 --- a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/util/InstrumentUtil.java +++ b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/util/InstrumentUtil.java @@ -144,6 +144,6 @@ private static void runFullScanOnJavaClass(@NotNull PsiClass psiClass, String[] */ public static boolean isMainPublicClass(@NotNull PsiClass psiClass) { PsiModifierList classModifier = psiClass.getModifierList(); - return classModifier != null && classModifier.getText().equals("public"); + return classModifier != null && classModifier.getText().contains("public"); } } From fcdf5bff4101f570b750efba5c613e24437c5657 Mon Sep 17 00:00:00 2001 From: Wesley Shann Date: Wed, 10 Jun 2020 16:08:29 +0200 Subject: [PATCH 22/27] Set the root project name Issue: S2-group/NAPPA#43 --- Android-Studio-Plugin/settings.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Android-Studio-Plugin/settings.gradle b/Android-Studio-Plugin/settings.gradle index 323f0f0e..6ebb2f7e 100644 --- a/Android-Studio-Plugin/settings.gradle +++ b/Android-Studio-Plugin/settings.gradle @@ -1,4 +1,4 @@ -rootProject.name = 'test1' +rootProject.name = 'nappa-plugin-android-studio' From 71bb72ce90ff792158c4624ffbd304adf148fffe Mon Sep 17 00:00:00 2001 From: Wesley Shann Date: Wed, 10 Jun 2020 16:09:15 +0200 Subject: [PATCH 23/27] Update README with the project new name Issue: S2-group/NAPPA#43 --- Android-Studio-Plugin/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Android-Studio-Plugin/README.md b/Android-Studio-Plugin/README.md index e80703fd..c260b79f 100644 --- a/Android-Studio-Plugin/README.md +++ b/Android-Studio-Plugin/README.md @@ -134,7 +134,7 @@ The cause of these exceptions are undefined configurations (e.g. SDK) and can be #### Running the plugin from IntelliJ IDEA * Open the [Gradle tool window](https://www.jetbrains.com/help/idea/jetgradle-tool-window.html#). -* Double click on `test1 > Tasks > IntelliJ > runIde`. +* Double click on `nappa-plugin-android-studio > Tasks > IntelliJ > runIde`. Running from the IntelliJ IDEA allows the plugin to be executed in debug mode. @@ -151,7 +151,7 @@ Gradle places new builds in the directory [build/distributions](build/distributi #### Create a new build from IntelliJ IDEA * Open the [Gradle tool window](https://www.jetbrains.com/help/idea/jetgradle-tool-window.html#). -* Double click on `test1 > Tasks > IntelliJ > buildPlugin` +* Double click on `nappa-plugin-android-studio > Tasks > IntelliJ > buildPlugin` #### Create a new build from bash From e44c0030c268ab21b876c6fcb9a5c6fab7fc7359 Mon Sep 17 00:00:00 2001 From: Wesley Shann Date: Wed, 10 Jun 2020 16:18:22 +0200 Subject: [PATCH 24/27] Not allow InstrumentUtil class to be instantiated Issue: S2-group/NAPPA#43 Utility classes should not have public constructors. Utility classes, which are collections of static members, are not meant to be instantiated. --- .../nl/vu/cs/s2group/nappa/plugin/util/InstrumentUtil.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/util/InstrumentUtil.java b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/util/InstrumentUtil.java index 5709a400..884dd0d0 100644 --- a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/util/InstrumentUtil.java +++ b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/util/InstrumentUtil.java @@ -19,6 +19,10 @@ public final class InstrumentUtil { private static final String NAPPA_PACKAGE_NAME = "nl.vu.cs.s2group.nappa"; + private InstrumentUtil() { + throw new IllegalStateException("InstrumentUtil is a utility class and should be instantiated!"); + } + /** * Scan the project directory for all source files to search for all Java source files * From 5d705524cd4aeade72e02d332c800aee39c2369a Mon Sep 17 00:00:00 2001 From: Wesley Shann Date: Wed, 10 Jun 2020 16:18:40 +0200 Subject: [PATCH 25/27] Split method declaration in multiple lines Issue: S2-group/NAPPA#43 --- .../nl/vu/cs/s2group/nappa/plugin/util/InstrumentUtil.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/util/InstrumentUtil.java b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/util/InstrumentUtil.java index 884dd0d0..b3832419 100644 --- a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/util/InstrumentUtil.java +++ b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/util/InstrumentUtil.java @@ -131,7 +131,8 @@ private static void runFullScanOnJavaClass(@NotNull PsiClass psiClass, String[] * @param classType The class of the desired Psi element * @return The Psi representation of the first {@code element} of the type {@code classType}. {@code null} if no Java class is found. */ - public static @Nullable PsiElement getAncestorPsiElementFromElement(PsiElement element, Class classType) { + public static @Nullable + PsiElement getAncestorPsiElementFromElement(PsiElement element, Class classType) { PsiElement el = element; while (true) { if (el == null || el instanceof PsiDirectory) return null; From 346dff27d7c2006b4f02f94e79686b90ad1db0d3 Mon Sep 17 00:00:00 2001 From: Wesley Shann Date: Wed, 10 Jun 2020 16:23:15 +0200 Subject: [PATCH 26/27] Define the parameter classType type Issue: S2-group/NAPPA#43 Raw types should not be used. Generic types shouldn't be used raw (without type parameters) in variable declarations or return values. Doing so bypasses generic type checking, and defers the catch of unsafe code to runtime. --- .../java/nl/vu/cs/s2group/nappa/plugin/util/InstrumentUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/util/InstrumentUtil.java b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/util/InstrumentUtil.java index b3832419..4af91fef 100644 --- a/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/util/InstrumentUtil.java +++ b/Android-Studio-Plugin/src/main/java/nl/vu/cs/s2group/nappa/plugin/util/InstrumentUtil.java @@ -132,7 +132,7 @@ private static void runFullScanOnJavaClass(@NotNull PsiClass psiClass, String[] * @return The Psi representation of the first {@code element} of the type {@code classType}. {@code null} if no Java class is found. */ public static @Nullable - PsiElement getAncestorPsiElementFromElement(PsiElement element, Class classType) { + PsiElement getAncestorPsiElementFromElement(PsiElement element, Class classType) { PsiElement el = element; while (true) { if (el == null || el instanceof PsiDirectory) return null; From 9c7d981193a3904e3feecce149989f0bf87d024a Mon Sep 17 00:00:00 2001 From: Wesley Shann Date: Wed, 10 Jun 2020 16:34:58 +0200 Subject: [PATCH 27/27] Set correct version upgrade Issue: S2-group/NAPPA#43 From the Semantic Versioning 2.0 * MINOR version when you add functionality in a backwards compatible manner, and * PATCH version when you make backwards compatible bug fixes. The new changes refactor the existing action, fixing a issues and improving the source code. However, it do not add new functionalities. As such, the patch version should be upgraded, not the minor version --- Android-Studio-Plugin/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Android-Studio-Plugin/build.gradle b/Android-Studio-Plugin/build.gradle index de27e8b7..84995637 100644 --- a/Android-Studio-Plugin/build.gradle +++ b/Android-Studio-Plugin/build.gradle @@ -5,7 +5,7 @@ plugins { } group 'nl.vu.cs.s2group.nappa.plugin' -version '1.2.0' +version '1.1.1' sourceCompatibility = 1.8