Java: include fields and inner classes when grouping by defining type in structure view (IDEA-169975)

GitOrigin-RevId: 930abc712605034fd916a7dff69a95d8ddb12299
This commit is contained in:
Bas Leijdekkers
2025-10-10 17:00:11 +00:00
committed by intellij-monorepo-bot
parent d247f73910
commit 0244a62fc3
5 changed files with 143 additions and 91 deletions
@@ -1,18 +1,4 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// 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.structureView.impl.java;
import com.intellij.ide.structureView.impl.common.PsiTreeElementBase;
@@ -27,9 +13,9 @@ import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ui.UIUtil;
public abstract class JavaClassTreeElementBase<Value extends PsiElement> extends PsiTreeElementBase<Value> implements AccessLevelProvider,
ColoredItemPresentation,
LocationPresentation {
public abstract class JavaClassTreeElementBase<Value extends PsiElement> extends PsiTreeElementBase<Value>
implements AccessLevelProvider, ColoredItemPresentation, LocationPresentation {
private final boolean myIsInherited;
protected String myLocation;
@@ -44,14 +30,14 @@ public abstract class JavaClassTreeElementBase<Value extends PsiElement> extends
public boolean isPublic() {
Value element = getElement();
return !(element instanceof PsiModifierListOwner) || ((PsiModifierListOwner)element).hasModifierProperty(PsiModifier.PUBLIC);
return !(element instanceof PsiModifierListOwner owner) || owner.hasModifierProperty(PsiModifier.PUBLIC);
}
@Override
public int getAccessLevel() {
Value element = getElement();
if (!(element instanceof PsiModifierListOwner)) return PsiUtil.ACCESS_LEVEL_PUBLIC;
final PsiModifierList modifierList = ((PsiModifierListOwner)element).getModifierList();
if (!(element instanceof PsiModifierListOwner owner)) return PsiUtil.ACCESS_LEVEL_PUBLIC;
final PsiModifierList modifierList = owner.getModifierList();
if (modifierList == null) {
return PsiUtil.ACCESS_LEVEL_PUBLIC;
}
@@ -69,8 +55,8 @@ public abstract class JavaClassTreeElementBase<Value extends PsiElement> extends
if (isInherited()) {
if (myLocation == null) {
final Value element = getElement();
if (element instanceof PsiMember) {
final PsiClass cls = ((PsiMember)element).getContainingClass();
if (element instanceof PsiMember member) {
final PsiClass cls = member.getContainingClass();
if (cls == null) {
myLocation = "";
} else {
@@ -97,17 +83,16 @@ public abstract class JavaClassTreeElementBase<Value extends PsiElement> extends
}
@Override
public boolean equals(final Object o) {
public boolean equals(Object o) {
if (!super.equals(o)) return false;
final JavaClassTreeElementBase that = (JavaClassTreeElementBase)o;
final JavaClassTreeElementBase<?> that = (JavaClassTreeElementBase<?>)o;
if (myIsInherited != that.myIsInherited) return false;
return true;
return myIsInherited == that.myIsInherited;
}
@Override
public TextAttributesKey getTextAttributesKey() {
if (isInherited()) return CodeInsightColors.NOT_USED_ELEMENT_ATTRIBUTES;
try {
return isDeprecated() ? CodeInsightColors.DEPRECATED_ATTRIBUTES : null;
}
@@ -118,6 +103,6 @@ public abstract class JavaClassTreeElementBase<Value extends PsiElement> extends
private boolean isDeprecated(){
final Value element = getElement();
return element instanceof PsiDocCommentOwner && ((PsiDocCommentOwner)element).isDeprecated();
return element instanceof PsiDocCommentOwner owner && owner.isDeprecated();
}
}
@@ -1,10 +1,8 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// 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.structureView.impl.java;
import com.intellij.ide.structureView.StructureViewTreeElement;
import com.intellij.ide.util.treeView.smartTree.SortableTreeElement;
import com.intellij.openapi.editor.colors.CodeInsightColors;
import com.intellij.openapi.editor.colors.TextAttributesKey;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.IndexNotReadyException;
import com.intellij.openapi.util.registry.Registry;
@@ -99,12 +97,6 @@ public class PsiMethodTreeElement extends JavaClassTreeElementBase<PsiMethod> im
return StringUtil.isEmpty(myLocation) ? null : myLocation;
}
@Override
public TextAttributesKey getTextAttributesKey() {
if (isInherited()) return CodeInsightColors.NOT_USED_ELEMENT_ATTRIBUTES;
return super.getTextAttributesKey();
}
public PsiMethod getMethod() {
return getElement();
}
@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// 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.structureView.impl.java;
import com.intellij.icons.AllIcons;
@@ -7,6 +7,7 @@ import com.intellij.ide.util.treeView.AbstractTreeNode;
import com.intellij.ide.util.treeView.smartTree.*;
import com.intellij.openapi.util.Key;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiMember;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiModifier;
import com.intellij.util.ArrayUtil;
@@ -19,7 +20,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public final class SuperTypesGrouper implements Grouper{
public final class SuperTypesGrouper implements Grouper {
public static final Key<WeakReference<PsiMethod>> SUPER_METHOD_KEY = Key.create("StructureTreeBuilder.SUPER_METHOD_KEY");
public static final @NonNls String ID = "SHOW_INTERFACES";
@@ -29,45 +30,49 @@ public final class SuperTypesGrouper implements Grouper{
Map<Group, SuperTypeGroup> groups = new HashMap<>();
for (TreeElement child : children) {
if (child instanceof PsiMethodTreeElement element) {
PsiMethod method = element.getMethod();
if (element.isInherited()) {
PsiClass groupClass = method.getContainingClass();
if (child instanceof JavaClassTreeElementBase<?> treeElement
&& treeElement.isInherited()
&& treeElement.getElement() instanceof PsiMember member) {
PsiClass groupClass = member.getContainingClass();
if (groupClass != null) {
final SuperTypeGroup group = getOrCreateGroup(groupClass, SuperTypeGroup.OwnershipType.INHERITS, groups);
group.addMethod(child);
}
else {
PsiMethod[] superMethods = method.findSuperMethods();
if (superMethods.length > 0) {
//prefer interface, if there are any
for (int i = 1; i < superMethods.length; i++) {
PsiMethod superMethod = superMethods[i];
PsiClass containingClass = superMethod.getContainingClass();
if (containingClass != null && containingClass.isInterface()) {
ArrayUtil.swap(superMethods, 0, i);
break;
}
continue;
}
if (child instanceof PsiMethodTreeElement element) {
PsiMethod method = element.getMethod();
PsiMethod[] superMethods = method.findSuperMethods();
if (superMethods.length > 0) {
//prefer interface, if there are any
for (int i = 1; i < superMethods.length; i++) {
PsiMethod superMethod = superMethods[i];
PsiClass containingClass = superMethod.getContainingClass();
if (containingClass != null && containingClass.isInterface()) {
ArrayUtil.swap(superMethods, 0, i);
break;
}
PsiMethod superMethod = superMethods[0];
method.putUserData(SUPER_METHOD_KEY, new WeakReference<>(superMethod));
PsiClass groupClass = superMethod.getContainingClass();
boolean overrides = methodOverridesSuper(method, superMethod);
final SuperTypeGroup.OwnershipType ownershipType =
overrides ? SuperTypeGroup.OwnershipType.OVERRIDES : SuperTypeGroup.OwnershipType.IMPLEMENTS;
SuperTypeGroup group = getOrCreateGroup(groupClass, ownershipType, groups);
group.addMethod(child);
}
PsiMethod superMethod = superMethods[0];
method.putUserData(SUPER_METHOD_KEY, new WeakReference<>(superMethod));
PsiClass groupClass = superMethod.getContainingClass();
boolean overrides = methodOverridesSuper(method, superMethod);
final SuperTypeGroup.OwnershipType ownershipType =
overrides ? SuperTypeGroup.OwnershipType.OVERRIDES : SuperTypeGroup.OwnershipType.IMPLEMENTS;
SuperTypeGroup group = getOrCreateGroup(groupClass, ownershipType, groups);
group.addMethod(child);
}
}
}
return groups.keySet();
}
private static SuperTypeGroup getOrCreateGroup(final PsiClass groupClass, final SuperTypeGroup.OwnershipType ownershipType, final Map<Group, SuperTypeGroup> groups) {
SuperTypeGroup superTypeGroup =
new SuperTypeGroup(groupClass, ownershipType);
private static SuperTypeGroup getOrCreateGroup(PsiClass groupClass,
SuperTypeGroup.OwnershipType ownershipType,
Map<Group, SuperTypeGroup> groups) {
SuperTypeGroup superTypeGroup = new SuperTypeGroup(groupClass, ownershipType);
SuperTypeGroup existing = groups.get(superTypeGroup);
if (existing == null) {
groups.put(superTypeGroup, superTypeGroup);
@@ -85,12 +90,7 @@ public final class SuperTypesGrouper implements Grouper{
}
private static boolean methodOverridesSuper(PsiMethod method, PsiMethod superMethod) {
boolean overrides = false;
if (method.hasModifierProperty(PsiModifier.ABSTRACT) || !superMethod.hasModifierProperty(PsiModifier.ABSTRACT)){
overrides = true;
}
return overrides;
return method.hasModifierProperty(PsiModifier.ABSTRACT) || !superMethod.hasModifierProperty(PsiModifier.ABSTRACT);
}
@Override
@@ -1,4 +1,4 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// 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.structureView;
import com.intellij.ide.structureView.impl.java.*;
@@ -61,8 +61,8 @@ public class JavaStructureViewTest extends LightJavaStructureViewTestCaseBase {
JBTreeTraverser<AbstractTreeNode<?>> traverser = JBTreeTraverser.from(AbstractTreeNode::getChildren);
List<AbstractTreeNode<?>> roots = new ArrayList<>();
for (Object element : getElements()) {
if (element instanceof AbstractTreeNode) {
roots.add((AbstractTreeNode<?>)element);
if (element instanceof AbstractTreeNode<?> node) {
roots.add(node);
}
}
return traverser.withRoots(roots)
@@ -84,7 +84,7 @@ public class JavaStructureViewTest extends LightJavaStructureViewTestCaseBase {
TreeElementWrapper last = (TreeElementWrapper)elements[elements.length - 1];
Collection<AbstractTreeNode<?>> children = last.getChildren();
assertEquals(1, children.size());
assertEquals(3, ((AbstractTreeNode<?>)((List<?>)children).get(0)).getChildren().size());
assertEquals(3, ((AbstractTreeNode<?>)((List<?>)children).getFirst()).getChildren().size());
}
private Object[] getElements() {
@@ -115,11 +115,13 @@ public class JavaStructureViewTest extends LightJavaStructureViewTestCaseBase {
public void testSuperTypeGrouping() {
doTest("""
abstract class Abstract {
abstract void toImplement();
void toOverride(){}}
abstract void toImplement();
void toOverride(){}
}
class aClass extends Abstract {
void toImplement(){};
void toOverride(){};}""",
void toImplement(){};
void toOverride(){};
}""",
"""
-Test.java
@@ -131,7 +133,77 @@ public class JavaStructureViewTest extends LightJavaStructureViewTestCaseBase {
toImplement(): void
-Abstract
toOverride(): void"""
, true, false);
, true, false, false);
}
public void testSuperTypeGrouping2() {
doTest("""
abstract class Abstract {
int field;
class Inner {}
}
class aClass extends Abstract {
int moreField;
}""",
"""
-Test.java
-Abstract
-Inner
-Object
getClass(): Class<?>
hashCode(): int
equals(Object): boolean
clone(): Object
toString(): String
notify(): void
notifyAll(): void
wait(long): void
wait(long, int): void
wait(): void
finalize(): void
-Object
getClass(): Class<?>
hashCode(): int
equals(Object): boolean
clone(): Object
toString(): String
notify(): void
notifyAll(): void
wait(long): void
wait(long, int): void
wait(): void
finalize(): void
field: int
-aClass
-Abstract
-Inner
getClass(): Class<?>
hashCode(): int
equals(Object): boolean
clone(): Object
toString(): String
notify(): void
notifyAll(): void
wait(long): void
wait(long, int): void
wait(): void
finalize(): void
field: int
-Object
getClass(): Class<?>
hashCode(): int
equals(Object): boolean
clone(): Object
toString(): String
notify(): void
notifyAll(): void
wait(long): void
wait(long, int): void
wait(): void
finalize(): void
moreField: int"""
, true, false, true);
}
public void testPropertiesGrouping1() {
@@ -368,10 +440,11 @@ public class JavaStructureViewTest extends LightJavaStructureViewTestCaseBase {
}
public void testRecursive() {
myFixture.configureByText("I.java", "interface I {" +
" class Impl implements I {" +
" }" +
"};");
myFixture.configureByText("I.java", """
interface I {
class Impl implements I {
}
}""");
myFixture.testStructureView(component -> {
component.setActionActive(InheritedMembersNodeProvider.ID, true);
PlatformTestUtil.assertTreeEqual(component.getTree(),
@@ -690,21 +763,23 @@ public class JavaStructureViewTest extends LightJavaStructureViewTestCaseBase {
}
private void doTest(String classText, @Language("TEXT") String expected) {
doTest(classText, expected, false, false);
doTest(classText, expected, false, false, false);
}
private void doPropertiesTest(String classText, String expected) {
doTest(classText, expected, false, true);
doTest(classText, expected, false, true, false);
}
private void doTest(String classText,
String expected,
boolean showInterfaces,
boolean showProperties) {
boolean showProperties,
boolean showInherited) {
myFixture.configureByText("Test.java", classText);
myFixture.testStructureView(svc -> {
svc.setActionActive(SuperTypesGrouper.ID, showInterfaces);
svc.setActionActive(PropertiesGrouper.ID, showProperties);
svc.setActionActive(InheritedMembersNodeProvider.ID, showInherited);
svc.setActionActive(JavaAnonymousClassesNodeProvider.ID, true);
JTree tree = svc.getTree();
PlatformTestUtil.waitWhileBusy(tree);
@@ -1,5 +1,5 @@
action.sort.by.type=By Type
action.structureview.group.methods.by.defining.type=Methods by Defining Type
action.structureview.group.methods.by.defining.type=Members by Defining Type
action.structureview.show.fields=Fields
action.structureview.show.inherited=Inherited
action.structureview.sort.by.visibility=By Visibility