mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-08 15:09:39 +07:00
[ByteCodeViewer] IDEA-372346 Fix local classes not working
Allow local classes to work with bytecode viewer. Add a method in ClassUtil that takes into account anonymous and local classes and move duplicated logic into that method. Presumably, everything that used JavaAnonymousClassesHelper wanted local classes too. Also deprecate ClassUtil#getJVMClassName #IDEA-372346 fixed closes https://github.com/JetBrains/intellij-community/pull/3044 GitOrigin-RevId: 7d300719c079d3de5a7cb589d50431326526faa0
This commit is contained in:
committed by
intellij-monorepo-bot
parent
06a9b9bde5
commit
75ecbf846d
@@ -479,7 +479,7 @@ public final class JVMNameUtil {
|
||||
}
|
||||
|
||||
public static @Nullable String getClassVMName(@Nullable PsiClass containingClass) {
|
||||
// no support for local classes for now
|
||||
// no support for local classes for now. TODO: use JavaLocalClassesHelper
|
||||
if (containingClass == null) return null;
|
||||
if (containingClass instanceof PsiAnonymousClass) {
|
||||
String parentName = getClassVMName(PsiTreeUtil.getParentOfType(containingClass, PsiClass.class));
|
||||
|
||||
@@ -103,6 +103,7 @@ final class DiscoveredTestsTreeModel extends BaseTreeModel<Object> implements In
|
||||
return myInvoker;
|
||||
}
|
||||
|
||||
// TODO: this method can be replaced with ClassUtil.getBinaryClassName so it handles local classes.
|
||||
public static @Nullable String getClassName(@NotNull PsiClass c) {
|
||||
if (c instanceof PsiAnonymousClass) {
|
||||
PsiClass containingClass = PsiTreeUtil.getParentOfType(c, PsiClass.class);
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.ide.util;
|
||||
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.*;
|
||||
import com.intellij.util.containers.ObjectIntHashMap;
|
||||
import com.intellij.util.containers.ObjectIntMap;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class JavaLocalClassesHelper {
|
||||
private static final Key<ParameterizedCachedValue<Map<PsiClass, String>, PsiClass>>
|
||||
LOCAL_CLASS_NAME = Key.create("LOCAL_CLASS_NAME");
|
||||
private static final LocalClassProvider LOCAL_CLASS_PROVIDER = new LocalClassProvider();
|
||||
|
||||
/**
|
||||
* Returns the part of the class name suitable for being appended to the containing class' binary name to produce the local class' binary
|
||||
* name, see {@link ClassUtil#getBinaryClassName} and JLS 13.1.
|
||||
*
|
||||
* <p>For example, if the binary name of a local class is {@code com.example.Foo$1Local}, then this method will return the {@code $1Local}
|
||||
* part.
|
||||
*/
|
||||
public static @Nullable String getName(@NotNull PsiClass cls) {
|
||||
if (!PsiUtil.isLocalClass(cls)) {
|
||||
throw new IllegalArgumentException("class " + cls + " must be a local class");
|
||||
}
|
||||
final PsiClass upper = PsiTreeUtil.getParentOfType(cls, PsiClass.class);
|
||||
if (upper == null) {
|
||||
return null;
|
||||
}
|
||||
ParameterizedCachedValue<Map<PsiClass, String>, PsiClass> value = upper.getUserData(LOCAL_CLASS_NAME);
|
||||
if (value == null) {
|
||||
value = CachedValuesManager.getManager(upper.getProject()).createParameterizedCachedValue(LOCAL_CLASS_PROVIDER, false);
|
||||
upper.putUserData(LOCAL_CLASS_NAME, value);
|
||||
}
|
||||
return value.getValue(upper).get(cls);
|
||||
}
|
||||
|
||||
private static final class LocalClassProvider implements ParameterizedCachedValueProvider<Map<PsiClass, String>, PsiClass> {
|
||||
@Override
|
||||
public CachedValueProvider.Result<Map<PsiClass, String>> compute(final PsiClass upper) {
|
||||
final Map<PsiClass, String> map = new HashMap<>();
|
||||
upper.accept(new JavaRecursiveElementWalkingVisitor() {
|
||||
final ObjectIntMap<String> indexByName = new ObjectIntHashMap<>();
|
||||
|
||||
@Override
|
||||
public void visitClass(@NotNull PsiClass aClass) {
|
||||
if (aClass == upper) {
|
||||
super.visitClass(aClass);
|
||||
} else if (PsiUtil.isLocalClass(aClass)) {
|
||||
String name = aClass.getName();
|
||||
if (name != null) {
|
||||
int index = indexByName.getOrDefault(name, 0) + 1;
|
||||
indexByName.put(name, index);
|
||||
map.put(aClass, "$" + index + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return CachedValueProvider.Result.create(map, upper);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.psi.util;
|
||||
|
||||
import com.intellij.ide.util.JavaAnonymousClassesHelper;
|
||||
import com.intellij.ide.util.JavaLocalClassesHelper;
|
||||
import com.intellij.lang.java.JavaLanguage;
|
||||
import com.intellij.openapi.util.Comparing;
|
||||
import com.intellij.openapi.util.NlsSafe;
|
||||
@@ -213,6 +215,12 @@ public final class ClassUtil {
|
||||
return Character.isDigit(name.charAt(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the binary class name for top-level and nested classes.
|
||||
*
|
||||
* @deprecated Does not work for anonymous classes and local classes. Use {@link #getBinaryClassName} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static @Nullable @NlsSafe String getJVMClassName(@NotNull PsiClass aClass) {
|
||||
final PsiClass containingClass = aClass.getContainingClass();
|
||||
if (containingClass != null) {
|
||||
@@ -225,6 +233,42 @@ public final class ClassUtil {
|
||||
return aClass.getQualifiedName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the binary class name, i.e. the string that would be returned by {@link Class#getName}. See JLS 13.1.
|
||||
*
|
||||
* <ul>
|
||||
* <li><b>Top-level classes</b> return the qualified name of the class, e.g. {@code com.example.Foo}</li>
|
||||
* <li><b>Nested classes</b>, i.e. named classes nested directly within the body of another class, return the binary name of the outer
|
||||
* class, followed by a {@code $} plus the name of the nested class, e.g. {@code com.example.Foo$Inner}</li>
|
||||
* <li><b>Anonymous classes</b>, i.e. unnamed classes created with a {@code new} expression, return the binary name of the class they
|
||||
* are within, followed by a {@code $} plus a number indexing which anonymous class within the outer class we are referring to, e.g.
|
||||
* {@code com.example.Foo$1}</li>
|
||||
* <li><b>Local classes</b>, i.e. named classes within a method, return the binary name of the class they are within, followed by a
|
||||
* {@code $} plus a number and then the local class' name. The number indexes which local class with that same name within the outer
|
||||
* class we are referring to (multiple local classes within the same class may have the same name if they are in different methods).
|
||||
* E.g. {@code com.example.Foo$1Local}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public static @Nullable @NlsSafe String getBinaryClassName(@NotNull PsiClass aClass) {
|
||||
if (PsiUtil.isLocalOrAnonymousClass(aClass)) {
|
||||
PsiClass parentClass = PsiTreeUtil.getParentOfType(aClass, PsiClass.class);
|
||||
if (parentClass == null) {
|
||||
return null;
|
||||
}
|
||||
String parentName = getBinaryClassName(parentClass);
|
||||
if (parentName == null) {
|
||||
return null;
|
||||
}
|
||||
if (aClass instanceof PsiAnonymousClass) {
|
||||
return parentName + JavaAnonymousClassesHelper.getName((PsiAnonymousClass) aClass);
|
||||
} else {
|
||||
return parentName + JavaLocalClassesHelper.getName(aClass);
|
||||
}
|
||||
}
|
||||
|
||||
return getJVMClassName(aClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks for inner and anonymous classes by internal name ('pkg/Top$Inner').
|
||||
*/
|
||||
|
||||
9
java/java-tests/testData/local/MultipleNames.java
Normal file
9
java/java-tests/testData/local/MultipleNames.java
Normal file
@@ -0,0 +1,9 @@
|
||||
class MultipleNames {
|
||||
void method1() {
|
||||
class A {}
|
||||
}
|
||||
|
||||
void method2() {
|
||||
class <caret>B {}
|
||||
}
|
||||
}
|
||||
9
java/java-tests/testData/local/SameName.java
Normal file
9
java/java-tests/testData/local/SameName.java
Normal file
@@ -0,0 +1,9 @@
|
||||
class SameName {
|
||||
void method1() {
|
||||
class Local {}
|
||||
}
|
||||
|
||||
void method2() {
|
||||
class <caret>Local {}
|
||||
}
|
||||
}
|
||||
5
java/java-tests/testData/local/Simple.java
Normal file
5
java/java-tests/testData/local/Simple.java
Normal file
@@ -0,0 +1,5 @@
|
||||
class Simple {
|
||||
void method() {
|
||||
class <caret>Local {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.java.ide.util;
|
||||
|
||||
import com.intellij.JavaTestUtil;
|
||||
import com.intellij.ide.util.JavaLocalClassesHelper;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiDeclarationStatement;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.PsiUtilBase;
|
||||
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
|
||||
public class JavaLocalClassesHelperTest extends LightJavaCodeInsightFixtureTestCase {
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
myFixture.configureByFile(getTestName(false) + ".java");
|
||||
}
|
||||
|
||||
public void testSimple() {
|
||||
doTest("$1Local");
|
||||
}
|
||||
|
||||
public void testMultipleNames() {
|
||||
doTest("$1B");
|
||||
}
|
||||
|
||||
public void testSameName() {
|
||||
doTest("$2Local");
|
||||
}
|
||||
|
||||
private void doTest(String expectedName) {
|
||||
final PsiElement element = PsiUtilBase.getElementAtCaret(myFixture.getEditor()).getParent().getParent();
|
||||
|
||||
PsiDeclarationStatement declaration = ObjectUtils.tryCast(element, PsiDeclarationStatement.class);
|
||||
PsiClass aClass = declaration == null ? null : ObjectUtils.tryCast(declaration.getDeclaredElements()[0], PsiClass.class);
|
||||
assert aClass != null && PsiUtil.isLocalClass(aClass) : "There should be local class at caret but " + element + " found";
|
||||
|
||||
assertEquals(expectedName, JavaLocalClassesHelper.getName(aClass));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBasePath() {
|
||||
return JavaTestUtil.getRelativeJavaTestDataPath() + "/local/";
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
package com.intellij.byteCodeViewer
|
||||
|
||||
import com.intellij.ide.highlighter.JavaClassFileType
|
||||
import com.intellij.ide.util.JavaAnonymousClassesHelper
|
||||
import com.intellij.openapi.extensions.ExtensionPointName
|
||||
import com.intellij.openapi.fileTypes.FileTypeRegistry
|
||||
import com.intellij.openapi.roots.CompilerModuleExtension
|
||||
@@ -30,7 +29,7 @@ object ByteCodeViewerManager {
|
||||
val fileClass = aClass.containingClassFileClass()
|
||||
val file = fileClass.originalElement.containingFile.virtualFile ?: return null
|
||||
val fileIndex = ProjectFileIndex.getInstance(aClass.project)
|
||||
val jvmClassName = getJVMClassName(aClass) ?: return null
|
||||
val jvmClassName = ClassUtil.getBinaryClassName(aClass) ?: return null
|
||||
return if (FileTypeRegistry.getInstance().isFileOfType(file, JavaClassFileType.INSTANCE)) {
|
||||
// compiled class; looking for the right .class file (inner class 'A.B' is "contained" in 'A.class', but we need 'A$B.class')
|
||||
file.parent.findChild(StringUtil.getShortName(jvmClassName) + ".class")
|
||||
@@ -52,13 +51,6 @@ object ByteCodeViewerManager {
|
||||
return findClassFile(aClass)?.contentsToByteArray(false)
|
||||
}
|
||||
|
||||
private fun getJVMClassName(aClass: PsiClass): String? {
|
||||
if (aClass !is PsiAnonymousClass) return ClassUtil.getJVMClassName(aClass)
|
||||
val containingClass = PsiTreeUtil.getParentOfType<PsiClass?>(aClass, PsiClass::class.java)
|
||||
if (containingClass != null) return getJVMClassName(containingClass) + JavaAnonymousClassesHelper.getName(aClass)
|
||||
return null
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getContainingClass(psiElement: PsiElement): PsiClass? {
|
||||
for (searcher in CLASS_SEARCHER_EP.extensionList) {
|
||||
|
||||
Reference in New Issue
Block a user