diff --git a/java/java-psi-api/src/com/intellij/psi/util/ClassUtil.java b/java/java-psi-api/src/com/intellij/psi/util/ClassUtil.java index b5b72565da19..9024e6e7f585 100644 --- a/java/java-psi-api/src/com/intellij/psi/util/ClassUtil.java +++ b/java/java-psi-api/src/com/intellij/psi/util/ClassUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2015 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. @@ -22,13 +22,12 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class ClassUtil { - private ClassUtil() {} + private ClassUtil() { } public static String extractPackageName(String className) { if (className != null) { int i = className.lastIndexOf('.'); - return i == -1 ? "" : className.substring(0, i); - + return i == -1 ? "" : className.substring(0, i); } return null; } @@ -40,10 +39,10 @@ public class ClassUtil { } public static String createNewClassQualifiedName(String qualifiedName, String className) { - if (className == null){ + if (className == null) { return null; } - if (qualifiedName == null || qualifiedName.isEmpty()){ + if (qualifiedName == null || qualifiedName.isEmpty()) { return className; } return qualifiedName + "." + extractClassName(className); @@ -65,7 +64,7 @@ public class ClassUtil { buf.append(qName); } else { - final PsiClass parentClass = getContainerClass(aClass); + final PsiClass parentClass = getContainingClass(aClass); if (parentClass != null) { formatClassName(parentClass, buf); buf.append("$"); @@ -79,7 +78,7 @@ public class ClassUtil { } @Nullable - private static PsiClass getContainerClass(@NotNull PsiClass aClass) { + private static PsiClass getContainingClass(@NotNull PsiClass aClass) { PsiElement parent = aClass.getContext(); while (parent != null && !(parent instanceof PsiClass)) { parent = parent.getContext(); @@ -89,18 +88,20 @@ public class ClassUtil { public static int getNonQualifiedClassIdx(@NotNull final PsiClass psiClass) { final int[] result = {-1}; - final PsiClass containingClass = getContainerClass(psiClass); + final PsiClass containingClass = getContainingClass(psiClass); if (containingClass != null) { containingClass.accept(new JavaRecursiveElementVisitor() { private int myCurrentIdx = 0; - @Override public void visitElement(PsiElement element) { + @Override + public void visitElement(PsiElement element) { if (result[0] == -1) { super.visitElement(element); } } - @Override public void visitClass(PsiClass aClass) { + @Override + public void visitClass(PsiClass aClass) { super.visitClass(aClass); if (aClass.getQualifiedName() == null) { myCurrentIdx++; @@ -114,11 +115,13 @@ public class ClassUtil { return result[0]; } - public static PsiClass findNonQualifiedClassByIndex(@NotNull String indexName, @NotNull final PsiClass containingClass) { + @SuppressWarnings("unused") + public static PsiClass findNonQualifiedClassByIndex(@NotNull String indexName, @NotNull PsiClass containingClass) { return findNonQualifiedClassByIndex(indexName, containingClass, false); } - public static PsiClass findNonQualifiedClassByIndex(@NotNull String indexName, @NotNull final PsiClass containingClass, + public static PsiClass findNonQualifiedClassByIndex(@NotNull String indexName, + @NotNull final PsiClass containingClass, final boolean jvmCompatible) { String prefix = getDigitPrefix(indexName); final int idx = !prefix.isEmpty() ? Integer.parseInt(prefix) : -1; @@ -127,13 +130,15 @@ public class ClassUtil { containingClass.accept(new JavaRecursiveElementVisitor() { private int myCurrentIdx = 0; - @Override public void visitElement(PsiElement element) { + @Override + public void visitElement(PsiElement element) { if (result[0] == null) { super.visitElement(element); } } - @Override public void visitClass(PsiClass aClass) { + @Override + public void visitClass(PsiClass aClass) { if (!jvmCompatible) { super.visitClass(aClass); if (aClass.getQualifiedName() == null) { @@ -156,7 +161,8 @@ public class ClassUtil { } } - @Override public void visitTypeParameter(final PsiTypeParameter classParameter) { + @Override + public void visitTypeParameter(final PsiTypeParameter classParameter) { if (!jvmCompatible) { super.visitTypeParameter(classParameter); } @@ -180,61 +186,71 @@ public class ClassUtil { return i == 0 ? "" : indexName.substring(0, i); } - /** - * Finds anonymous classes. Uses javac notation. - * @param psiManager project to search - * @param externalName class qualified name - * @return found psiClass + * Looks for inner and anonymous classes by FQN in a javac notation ('pkg.Top$Inner'). */ @Nullable - public static PsiClass findPsiClass(@NotNull PsiManager psiManager, @NotNull String externalName){ - return findPsiClass(psiManager, externalName, null, false); + public static PsiClass findPsiClass(@NotNull PsiManager manager, @NotNull String name) { + return findPsiClass(manager, name, null, false); } @Nullable - public static PsiClass findPsiClass(@NotNull PsiManager psiManager, - @NotNull String externalName, - PsiClass psiClass, + public static PsiClass findPsiClass(@NotNull PsiManager manager, + @NotNull String name, + @Nullable PsiClass parent, boolean jvmCompatible) { - return findPsiClass(psiManager, externalName, psiClass, jvmCompatible, GlobalSearchScope.allScope(psiManager.getProject())); + GlobalSearchScope scope = GlobalSearchScope.allScope(manager.getProject()); + return findPsiClass(manager, name, parent, jvmCompatible, scope); } @Nullable - public static PsiClass findPsiClass(@NotNull PsiManager psiManager, - @NotNull String externalName, - @Nullable PsiClass psiClass, - boolean jvmCompatible, + public static PsiClass findPsiClass(@NotNull PsiManager manager, + @NotNull String name, + @Nullable PsiClass parent, + boolean jvmCompatible, @NotNull GlobalSearchScope scope) { - for (int pos = 0; pos < externalName.length(); pos++) { - if (externalName.charAt(pos) == '$') { - PsiClass parentClass = psiClass; - if (parentClass == null) { - parentClass = JavaPsiFacade.getInstance(psiManager.getProject()) - .findClass(externalName.substring(0, pos), scope); - } - if (parentClass == null) continue; - PsiClass res = findSubclass(psiManager, externalName.substring(pos + 1), parentClass, jvmCompatible); - if (res != null) return res; + if (parent != null) { + return findSubClass(name, parent, jvmCompatible); + } + + PsiClass result = JavaPsiFacade.getInstance(manager.getProject()).findClass(name, scope); + if (result != null) return result; + + int p = 0; + while ((p = name.indexOf('$', p + 1)) > 0 && p < name.length() - 1) { + String prefix = name.substring(0, p); + parent = JavaPsiFacade.getInstance(manager.getProject()).findClass(prefix, scope); + if (parent != null) { + String suffix = name.substring(p + 1); + result = findSubClass(suffix, parent, jvmCompatible); + if (result != null) return result; } } - return JavaPsiFacade.getInstance(psiManager.getProject()).findClass(externalName, scope); + + return null; } @Nullable - private static PsiClass findSubclass(@NotNull PsiManager psiManager, - @NotNull String externalName, - final PsiClass psiClass, - final boolean jvmCompatible) { - for (int pos = 0; pos < externalName.length(); pos++) { - if (externalName.charAt(pos) == '$') { - PsiClass anonymousClass = findNonQualifiedClassByIndex(externalName.substring(0, pos), psiClass, jvmCompatible); - if (anonymousClass == null) return null; - PsiClass res = findPsiClass(psiManager, externalName.substring(pos), anonymousClass, jvmCompatible); - if (res != null) return res; + private static PsiClass findSubClass(@NotNull String name, @NotNull PsiClass parent, boolean jvmCompatible) { + PsiClass result = isIndexed(name) ? findNonQualifiedClassByIndex(name, parent, jvmCompatible) : parent.findInnerClassByName(name, false); + if (result != null) return result; + + int p = 0; + while ((p = name.indexOf('$', p + 1)) > 0 && p < name.length() - 1) { + String prefix = name.substring(0, p); + PsiClass subClass = isIndexed(prefix) ? findNonQualifiedClassByIndex(prefix, parent, jvmCompatible) : parent.findInnerClassByName(prefix, false); + if (subClass != null) { + String suffix = name.substring(p + 1); + result = findSubClass(suffix, subClass, jvmCompatible); + if (result != null) return result; } } - return findNonQualifiedClassByIndex(externalName, psiClass, jvmCompatible); + + return null; + } + + private static boolean isIndexed(String name) { + return Character.isDigit(name.charAt(0)); } @Nullable @@ -245,13 +261,14 @@ public class ClassUtil { if (parentName == null) { return null; } - return parentName + "$" + aClass.getName(); } return aClass.getQualifiedName(); } - + /** + * Looks for inner and anonymous classes by internal name ('pkg/Top$Inner'). + */ @Nullable public static PsiClass findPsiClassByJVMName(@NotNull PsiManager manager, @NotNull String jvmClassName) { return findPsiClass(manager, jvmClassName.replace('/', '.'), null, true); diff --git a/java/java-tests/testData/psi/classUtil/ManyClasses.java b/java/java-tests/testData/psi/classUtil/ManyClasses.java index d16e2282c1e7..18d98be51244 100644 --- a/java/java-tests/testData/psi/classUtil/ManyClasses.java +++ b/java/java-tests/testData/psi/classUtil/ManyClasses.java @@ -28,6 +28,18 @@ public class ManyClasses { } public class Child { } + + public class Child$ { } + + public class Ma$ked { + public class Ne$ted { } + } + + public class Edge { } + + public class Edge$ { + public class $tu_pid_ne$s { } + } } class Local { diff --git a/java/java-tests/testSrc/com/intellij/psi/util/ClassUtilTest.java b/java/java-tests/testSrc/com/intellij/psi/util/ClassUtilTest.java index d53d92e0005a..c9f77a09217b 100644 --- a/java/java-tests/testSrc/com/intellij/psi/util/ClassUtilTest.java +++ b/java/java-tests/testSrc/com/intellij/psi/util/ClassUtilTest.java @@ -23,6 +23,11 @@ import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase; * @author yole */ public class ClassUtilTest extends LightCodeInsightFixtureTestCase { + @Override + protected String getBasePath() { + return JavaTestUtil.getRelativeJavaTestDataPath() + "/psi/classUtil/"; + } + public void testFindPsiClassByJvmName() { myFixture.configureByFile("ManyClasses.java"); @@ -32,6 +37,12 @@ public class ClassUtilTest extends LightCodeInsightFixtureTestCase { assertNotNull(ClassUtil.findPsiClassByJVMName(getPsiManager(), "ManyClasses$1FooLocal")); assertNotNull(ClassUtil.findPsiClassByJVMName(getPsiManager(), "ManyClasses$1FooLocal$1")); assertNotNull(ClassUtil.findPsiClassByJVMName(getPsiManager(), "ManyClasses$Child")); + assertNotNull(ClassUtil.findPsiClassByJVMName(getPsiManager(), "ManyClasses$Child$")); + assertNotNull(ClassUtil.findPsiClassByJVMName(getPsiManager(), "ManyClasses$Ma$ked")); + assertNotNull(ClassUtil.findPsiClassByJVMName(getPsiManager(), "ManyClasses$Ma$ked$Ne$ted")); + assertNotNull(ClassUtil.findPsiClassByJVMName(getPsiManager(), "ManyClasses$Edge")); + assertNotNull(ClassUtil.findPsiClassByJVMName(getPsiManager(), "ManyClasses$Edge$")); + assertNotNull(ClassUtil.findPsiClassByJVMName(getPsiManager(), "ManyClasses$Edge$$$tu_pid_ne$s")); assertNotNull(ClassUtil.findPsiClassByJVMName(getPsiManager(), "Local")); assertNotNull(ClassUtil.findPsiClassByJVMName(getPsiManager(), "Local$Sub")); @@ -47,9 +58,4 @@ public class ClassUtilTest extends LightCodeInsightFixtureTestCase { assertNotNull(fooLocal2); assertEquals("Runnable", fooLocal2.getImplementsListTypes()[0].getClassName()); } - - @Override - protected String getBasePath() { - return JavaTestUtil.getRelativeJavaTestDataPath() + "/psi/classUtil/"; - } } diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/psi/GrClassUtilTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/psi/GrClassUtilTest.groovy new file mode 100644 index 000000000000..36c46f1b02ff --- /dev/null +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/psi/GrClassUtilTest.groovy @@ -0,0 +1,35 @@ +/* + * Copyright 2000-2015 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. + */ +package org.jetbrains.plugins.groovy.lang.psi + +import com.intellij.psi.util.ClassUtil +import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase + +class GrClassUtilTest extends LightCodeInsightFixtureTestCase { + public void testFindClassByName() { + myFixture.configureByText("a.groovy", '''\ + public class InnerClasses { + static class Bar { } + static class Bar$ { + static class $Foo { } + } + }'''.stripIndent()) + + assertNotNull(ClassUtil.findPsiClassByJVMName(psiManager, 'InnerClasses$Bar')) + assertNotNull(ClassUtil.findPsiClassByJVMName(psiManager, 'InnerClasses$Bar$')) + assertNotNull(ClassUtil.findPsiClassByJVMName(psiManager, 'InnerClasses$Bar$$$Foo')) + } +} \ No newline at end of file