Skip to content

Commit 3c1dc66

Browse files
committed
Add Annotation for java
1 parent d6da88e commit 3c1dc66

File tree

10 files changed

+303
-61
lines changed

10 files changed

+303
-61
lines changed

ide-common/src/main/java/org/digma/intellij/plugin/insights/view/InsightsViewBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ protected void buildNoObservabilityPanelIfNeed(Project project, MethodInfo metho
105105
if (hasInsightOfErrors || hasInsightOfHotSpot) {
106106
boolean onlyErrorInsights = insightTypes.isEmpty();
107107
if (onlyErrorInsights) {
108-
ListViewItem<NoObservability> itemOfNoObservability = new ListViewItem<>(new NoObservability(), SORT_INDEX_HIGHEST_IRRELEVANT);
108+
ListViewItem<NoObservability> itemOfNoObservability = new ListViewItem<>(new NoObservability(methodInfo.getId()), SORT_INDEX_HIGHEST_IRRELEVANT);
109109
dest.add(itemOfNoObservability);
110110
}
111111
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package org.digma.intellij.plugin.psi;
2+
3+
4+
public class CanInstrumentMethodResult{
5+
6+
private final IFailureCause failureCause;
7+
8+
public CanInstrumentMethodResult(){
9+
this.failureCause = null;
10+
}
11+
12+
public CanInstrumentMethodResult(IFailureCause failureCause){
13+
14+
this.failureCause = failureCause;
15+
}
16+
17+
public static CanInstrumentMethodResult Failure(){
18+
return new CanInstrumentMethodResult(new GenericFailureCause());
19+
}
20+
21+
public boolean wasSucceeded() { return failureCause == null; }
22+
23+
public IFailureCause getFailureCause(){ return failureCause; }
24+
25+
26+
public interface IFailureCause {}
27+
28+
public record MissingDependencyCause(String dependency) implements IFailureCause {}
29+
30+
public record GenericFailureCause() implements IFailureCause {}
31+
}

ide-common/src/main/java/org/digma/intellij/plugin/psi/LanguageService.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,4 +278,12 @@ private static Language findLanguageByMethodCodeObjectId(@NotNull Project projec
278278
boolean isCodeVisionSupported();
279279

280280
@NotNull List<Pair<TextRange, CodeVisionEntry>> getCodeLens(@NotNull PsiFile psiFile);
281+
282+
default CanInstrumentMethodResult canInstrumentMethod(@NotNull Project project, String methodId){
283+
return CanInstrumentMethodResult.Failure();
284+
}
285+
286+
default boolean instrumentMethod(@NotNull CanInstrumentMethodResult result){
287+
return false;
288+
}
281289
}

java/src/main/java/org/digma/intellij/plugin/idea/psi/java/JavaLanguageService.java

Lines changed: 111 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
import com.intellij.lang.Language;
55
import com.intellij.lang.java.JavaLanguage;
66
import com.intellij.openapi.application.ReadAction;
7+
import com.intellij.openapi.command.WriteCommandAction;
78
import com.intellij.openapi.diagnostic.Logger;
89
import com.intellij.openapi.editor.Editor;
910
import com.intellij.openapi.fileEditor.FileEditor;
1011
import com.intellij.openapi.fileEditor.FileEditorManager;
12+
import com.intellij.openapi.module.ModuleUtilCore;
1113
import com.intellij.openapi.project.DumbService;
1214
import com.intellij.openapi.project.Project;
1315
import com.intellij.openapi.roots.ProjectFileIndex;
@@ -22,6 +24,7 @@
2224
import org.digma.intellij.plugin.log.Log;
2325
import org.digma.intellij.plugin.model.discovery.DocumentInfo;
2426
import org.digma.intellij.plugin.model.discovery.MethodUnderCaret;
27+
import org.digma.intellij.plugin.psi.CanInstrumentMethodResult;
2528
import org.digma.intellij.plugin.psi.LanguageService;
2629
import org.digma.intellij.plugin.psi.PsiUtils;
2730
import org.digma.intellij.plugin.ui.CaretContextService;
@@ -138,6 +141,68 @@ public MethodUnderCaret detectMethodUnderCaret(@NotNull Project project, @NotNul
138141
return new MethodUnderCaret("", "", "", PsiUtils.psiFileToUri(psiFile), true);
139142
}
140143

144+
public CanInstrumentMethodResult canInstrumentMethod(@NotNull Project project, String methodId){
145+
146+
var psiMethod = findPsiMethodByMethodCodeObjectId(methodId);
147+
if (psiMethod == null) {
148+
Log.log(LOGGER::warn, "Failed to get PsiMethod from method id '{}'", methodId);
149+
return CanInstrumentMethodResult.Failure();
150+
}
151+
152+
var psiFile = psiMethod.getContainingFile();
153+
if (!(psiFile instanceof PsiJavaFile psiJavaFile)) {
154+
Log.log(LOGGER::warn, "PsiMethod's file is not java file (methodId: {})", methodId);
155+
return CanInstrumentMethodResult.Failure();
156+
}
157+
158+
var module = ModuleUtilCore.findModuleForPsiElement(psiMethod);
159+
if (module == null) {
160+
Log.log(LOGGER::warn, "Failed to get module from PsiMethod '{}'", methodId);
161+
return CanInstrumentMethodResult.Failure();
162+
}
163+
164+
var withSpanClass = JavaPsiFacade.getInstance(project).findClass(
165+
"io.opentelemetry.instrumentation.annotations.WithSpan",
166+
GlobalSearchScope.allScope(project));
167+
if (withSpanClass == null) {
168+
Log.log(LOGGER::warn, "Failed to get WithSpan PsiClass (methodId: {})", methodId);
169+
return new CanInstrumentMethodResult(new CanInstrumentMethodResult.MissingDependencyCause("io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations"));
170+
}
171+
172+
return new JavaCanInstrumentMethodResult(methodId, psiMethod, withSpanClass, psiJavaFile);
173+
}
174+
175+
public boolean instrumentMethod(@NotNull CanInstrumentMethodResult result){
176+
177+
if (!(result instanceof JavaCanInstrumentMethodResult goodResult)) {
178+
Log.log(LOGGER::warn, "instrumentMethod was called with failing result from canInstrumentMethod");
179+
return false;
180+
}
181+
182+
var psiJavaFile = goodResult.psiJavaFile;
183+
var psiMethod = goodResult.psiMethod;
184+
var methodId = goodResult.methodId;
185+
var withSpanClass = goodResult.withSpanClass;
186+
187+
var importList = psiJavaFile.getImportList();
188+
if (importList == null) {
189+
Log.log(LOGGER::warn, "Failed to get ImportList from PsiFile (methodId: {})", methodId);
190+
return false;
191+
}
192+
193+
WriteCommandAction.runWriteCommandAction(project, () -> {
194+
var psiFactory = PsiElementFactory.getInstance(project);
195+
196+
psiMethod.getModifierList().addAnnotation("WithSpan");
197+
198+
var existing = importList.findSingleClassImportStatement(withSpanClass.getQualifiedName());
199+
if (existing == null) {
200+
var importStatement = psiFactory.createImportStatement(withSpanClass);
201+
importList.add(importStatement);
202+
}
203+
});
204+
return true;
205+
}
141206

142207
/**
143208
* Navigate to any method in the project even if the file is not opened
@@ -253,37 +318,45 @@ public Map<String, Pair<String, Integer>> findWorkspaceUrisForMethodCodeObjectId
253318

254319
methodCodeObjectIds.forEach(methodId -> {
255320

256-
if (methodId.contains("$_$")) {
257-
var className = methodId.substring(0, methodId.indexOf("$_$"));
321+
var psiMethod = findPsiMethodByMethodCodeObjectId(methodId);
322+
if (psiMethod != null) {
323+
String url = PsiUtils.psiFileToUri(psiMethod.getContainingFile());
324+
workspaceUrls.put(methodId, new Pair<>(url, psiMethod.getTextOffset()));
325+
}
326+
});
258327

259-
//the code object id for inner classes separates inner classes name with $, but intellij index them with a dot
260-
className = className.replace('$', '.');
328+
return workspaceUrls;
329+
}
261330

262-
//searching in project scope will find only project classes
263-
Collection<PsiClass> psiClasses =
264-
JavaFullClassNameIndex.getInstance().get(className, project, GlobalSearchScope.projectScope(project));
265-
if (!psiClasses.isEmpty()) {
266-
//hopefully there is only one class by that name in the project
267-
PsiClass psiClass = psiClasses.stream().findAny().get();
268-
PsiFile psiFile = PsiTreeUtil.getParentOfType(psiClass, PsiFile.class);
269-
for (PsiMethod method : psiClass.getMethods()) {
270-
String javaMethodCodeObjectId = createJavaMethodCodeObjectId(method);
271-
if (javaMethodCodeObjectId.equals(methodId) && psiFile != null) {
272-
String url = PsiUtils.psiFileToUri(psiFile);
273-
workspaceUrls.put(methodId, new Pair<>(url, method.getTextOffset()));
331+
private @Nullable PsiMethod findPsiMethodByMethodCodeObjectId(String methodId){
332+
if (methodId.contains("$_$")) {
333+
var className = methodId.substring(0, methodId.indexOf("$_$"));
334+
335+
//the code object id for inner classes separates inner classes name with $, but intellij index them with a dot
336+
className = className.replace('$', '.');
274337

275-
}
338+
//searching in project scope will find only project classes
339+
Collection<PsiClass> psiClasses =
340+
JavaFullClassNameIndex.getInstance().get(className, project, GlobalSearchScope.projectScope(project));
341+
if (!psiClasses.isEmpty()) {
342+
//hopefully there is only one class by that name in the project
343+
PsiClass psiClass = psiClasses.stream().findAny().get();
344+
PsiFile psiFile = PsiTreeUtil.getParentOfType(psiClass, PsiFile.class);
345+
for (PsiMethod method : psiClass.getMethods()) {
346+
String javaMethodCodeObjectId = createJavaMethodCodeObjectId(method);
347+
if (javaMethodCodeObjectId.equals(methodId) && psiFile != null) {
348+
// String url = PsiUtils.psiFileToUri(psiFile);
349+
// workspaceUrls.put(methodId, new Pair<>(url, method.getTextOffset()));
350+
return method;
276351
}
277352
}
278-
}else{
279-
Log.log(LOGGER::debug, "method id in findWorkspaceUrisForMethodCodeObjectIds does not contain $_$ {}", methodId);
280353
}
281-
});
282-
283-
return workspaceUrls;
354+
}else{
355+
Log.log(LOGGER::debug, "method id in findWorkspaceUrisForMethodCodeObjectIds does not contain $_$ {}", methodId);
356+
}
357+
return null;
284358
}
285359

286-
287360
@NotNull
288361
@Override
289362
public Map<String, Pair<String, Integer>> findWorkspaceUrisForSpanIds(@NotNull List<String> spanIds) {
@@ -375,4 +448,19 @@ public boolean isCodeVisionSupported() {
375448
public @NotNull List<Pair<TextRange, CodeVisionEntry>> getCodeLens(@NotNull PsiFile psiFile) {
376449
return JavaCodeLensService.getInstance(project).getCodeLens(psiFile);
377450
}
451+
452+
private static final class JavaCanInstrumentMethodResult extends CanInstrumentMethodResult {
453+
private final String methodId;
454+
private final PsiMethod psiMethod;
455+
private final PsiClass withSpanClass;
456+
private final PsiJavaFile psiJavaFile;
457+
458+
private JavaCanInstrumentMethodResult(String methodId, PsiMethod psiMethod, PsiClass withSpanClass,
459+
PsiJavaFile psiJavaFile) {
460+
this.methodId = methodId;
461+
this.psiMethod = psiMethod;
462+
this.withSpanClass = withSpanClass;
463+
this.psiJavaFile = psiJavaFile;
464+
}
465+
}
378466
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
package org.digma.intellij.plugin.ui.model.insights
22

3-
class NoObservability
3+
4+
class NoObservability(val methodId: String?)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package org.digma.intellij.plugin.ui.common
2+
3+
import com.intellij.openapi.project.Project
4+
import org.digma.intellij.plugin.psi.CanInstrumentMethodResult
5+
import org.digma.intellij.plugin.psi.LanguageService
6+
7+
class MethodInstrumentationPresenter(private val project: Project) {
8+
9+
private var languageService: LanguageService? = null
10+
11+
private var canInstrumentMethodResult: CanInstrumentMethodResult? = null
12+
13+
val canInstrumentMethod: Boolean
14+
get() = canInstrumentMethodResult?.wasSucceeded() ?: false
15+
16+
val cannotBecauseMissingDependency: Boolean
17+
get() = canInstrumentMethodResult?.failureCause is CanInstrumentMethodResult.MissingDependencyCause
18+
19+
val missingDependency: String?
20+
get() = (canInstrumentMethodResult?.failureCause as? CanInstrumentMethodResult.MissingDependencyCause)?.dependency
21+
22+
fun instrumentMethod(): Boolean {
23+
return if(canInstrumentMethodResult != null) languageService?.instrumentMethod(canInstrumentMethodResult!!) ?: false
24+
else false
25+
}
26+
27+
fun update(methodId: String?){
28+
languageService = LanguageService.findLanguageServiceByMethodCodeObjectId(project, methodId)
29+
canInstrumentMethodResult = languageService!!.canInstrumentMethod(project, methodId)
30+
}
31+
32+
}

src/main/kotlin/org/digma/intellij/plugin/ui/common/Panels.kt

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,24 @@ package org.digma.intellij.plugin.ui.common
33
import com.intellij.icons.AllIcons
44
import com.intellij.openapi.project.Project
55
import com.intellij.openapi.ui.DialogPanel
6-
import com.intellij.ui.dsl.builder.BottomGap
7-
import com.intellij.ui.dsl.builder.MutableProperty
8-
import com.intellij.ui.dsl.builder.TopGap
9-
import com.intellij.ui.dsl.builder.panel
6+
import com.intellij.ui.components.JBTextArea
7+
import com.intellij.ui.dsl.builder.*
108
import com.intellij.ui.dsl.gridLayout.HorizontalAlign
119
import com.intellij.util.ui.JBUI
10+
import org.digma.intellij.plugin.notifications.NotificationUtil
11+
import org.digma.intellij.plugin.ui.model.MethodScope
1212
import org.digma.intellij.plugin.ui.model.NOT_SUPPORTED_OBJECT_MSG
1313
import org.digma.intellij.plugin.ui.model.PanelModel
1414
import org.digma.intellij.plugin.ui.model.insights.InsightsModel
1515
import org.digma.intellij.plugin.ui.panels.DigmaTabPanel
16+
import javax.swing.JButton
1617
import javax.swing.JLabel
1718

1819

19-
private const val NO_DATA_YET_DETAIL_DESCRIPTION = "Trigger actions that call this code object to learn more about its runtime behavior"
20-
private const val NO_OBSERVABILITY_DETAIL_DESCRIPTION = "Add an annotation to observe this method and collect data about its runtime behavior"
20+
const val NO_DATA_YET_DETAIL_DESCRIPTION = "Trigger actions that call this code object to learn more about its runtime behavior"
21+
const val NO_OBSERVABILITY_DETAIL_DESCRIPTION = "Add an annotation to observe this method and collect data about its runtime behavior"
22+
const val NO_OBSERVABILITY_MISSING_DEPENDENCY_DESCRIPTION = "Before adding annotations, please add the following dependency:";
23+
2124

2225
fun noCodeObjectWarningPanel(model: PanelModel): DialogPanel {
2326
return panel {
@@ -83,7 +86,14 @@ fun createNoDataYetPanel(): DialogPanel {
8386
}.andTransparent().withBorder(JBUI.Borders.empty())
8487
}
8588

86-
fun createNoObservabilityPanel(): DialogPanel {
89+
fun createNoObservabilityPanel(project: Project, insightsModel: InsightsModel): DialogPanel {
90+
91+
val model = MethodInstrumentationPresenter(project)
92+
93+
lateinit var addButton: Cell<JButton>
94+
lateinit var autoFixRow: Row
95+
lateinit var dependencyName: Cell<JBTextArea>
96+
8797
return panel {
8898
row {
8999
icon(Laf.Icons.Common.NoObservability)
@@ -97,9 +107,45 @@ fun createNoObservabilityPanel(): DialogPanel {
97107
label(asHtml(NO_OBSERVABILITY_DETAIL_DESCRIPTION))
98108
.horizontalAlign(HorizontalAlign.CENTER)
99109
}.bottomGap(BottomGap.MEDIUM).topGap(TopGap.MEDIUM)
110+
autoFixRow = row {
111+
panel {
112+
row {
113+
label(asHtml(NO_OBSERVABILITY_MISSING_DEPENDENCY_DESCRIPTION))
114+
}
115+
row {
116+
dependencyName = textArea()
117+
dependencyName.component.isEditable = false
118+
dependencyName.component.background = Laf.Colors.EDITOR_BACKGROUND
119+
dependencyName.component.lineWrap = true
120+
dependencyName.horizontalAlign(HorizontalAlign.FILL)
121+
}
122+
}
123+
}.visible(false)
124+
row {
125+
addButton = button("Add Annotation"){
126+
val succeeded = model.instrumentMethod()
127+
if(succeeded){
128+
NotificationUtil.notifyError(project, "Failed to add annotation")
129+
}
130+
}.horizontalAlign(HorizontalAlign.CENTER)
131+
}
132+
onReset {
133+
model.update((insightsModel.scope as? MethodScope)?.getMethodInfo()?.id)
134+
if(model.canInstrumentMethod){
135+
addButton.component.isEnabled = true
136+
autoFixRow.visible(false)
137+
}
138+
else {
139+
addButton.component.isEnabled = false
140+
autoFixRow.visible(model.cannotBecauseMissingDependency)
141+
dependencyName.text(model.missingDependency ?: "")
142+
}
143+
}
100144
}.andTransparent().withBorder(JBUI.Borders.empty())
101145
}
102146

147+
148+
103149
private fun getNoInfoMessage(model: PanelModel): String {
104150
var msg = if (model is InsightsModel) "No insights" else "No errors"
105151

src/main/kotlin/org/digma/intellij/plugin/ui/insights/InsightsTab.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ fun insightsPanel(project: Project): DigmaTabPanel {
8080
val pendingInsightsPanel = createPendingInsightsPanel()
8181
val loadingInsightsPanel = createLoadingInsightsPanel()
8282
val noDataYetPanel = createNoDataYetPanel()
83-
val noObservabilityPanel = createNoObservabilityPanel()
83+
val noObservabilityPanel = createNoObservabilityPanel(project, insightsModel)
8484

8585
val cardLayout = CardLayout()
8686
val cardsPanel = JPanel(cardLayout)
@@ -121,7 +121,10 @@ fun insightsPanel(project: Project): DigmaTabPanel {
121121
UiInsightStatus.Unknown -> LOADING_INSIGHTS_CARD_NAME
122122
UiInsightStatus.InsightPending -> UiInsightStatus.InsightPending.name
123123
UiInsightStatus.NoSpanData -> UiInsightStatus.NoSpanData.name
124-
UiInsightStatus.NoObservability -> UiInsightStatus.NoObservability.name
124+
UiInsightStatus.NoObservability ->{
125+
noObservabilityPanel.reset()
126+
UiInsightStatus.NoObservability.name
127+
}
125128
else -> NO_INFO_CARD_NAME
126129
}
127130
cardLayout.show(cardsPanel, cardName)

0 commit comments

Comments
 (0)