diff --git a/java/java-psi-impl/src/com/intellij/psi/ClassFileViewProvider.java b/java/java-psi-impl/src/com/intellij/psi/ClassFileViewProvider.java index 29cfa37f719d..0ccfc4f2c4e9 100644 --- a/java/java-psi-impl/src/com/intellij/psi/ClassFileViewProvider.java +++ b/java/java-psi-impl/src/com/intellij/psi/ClassFileViewProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2016 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. @@ -17,19 +17,29 @@ package com.intellij.psi; import com.intellij.ide.highlighter.JavaClassFileType; import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.FileIndexFacade; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.Ref; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.impl.PsiManagerImpl; import com.intellij.psi.impl.compiled.ClsFileImpl; import com.intellij.psi.impl.file.PsiBinaryFileImpl; import org.jetbrains.annotations.NotNull; +import org.jetbrains.org.objectweb.asm.ClassReader; +import org.jetbrains.org.objectweb.asm.ClassVisitor; +import org.jetbrains.org.objectweb.asm.Opcodes; + +import java.io.IOException; /** * @author max */ public class ClassFileViewProvider extends SingleRootFileViewProvider { + private static final Key IS_INNER_CLASS = Key.create("java.is.inner.class.key"); + public ClassFileViewProvider(@NotNull PsiManager manager, @NotNull VirtualFile file) { super(manager, file); } @@ -45,7 +55,7 @@ public class ClassFileViewProvider extends SingleRootFileViewProvider { return new PsiBinaryFileImpl((PsiManagerImpl)getManager(), this); } - // skip inners & anonymous + // skip inner & anonymous if (isInnerClass(file)) return null; return new ClsFileImpl(this); @@ -53,16 +63,45 @@ public class ClassFileViewProvider extends SingleRootFileViewProvider { public static boolean isInnerClass(@NotNull VirtualFile file) { String name = file.getNameWithoutExtension(); - int index = name.lastIndexOf('$', name.length()); + int index = name.lastIndexOf('$'); if (index > 0 && index < name.length() - 1) { - String supposedParentName = name.substring(0, index) + ".class"; - if (file.getParent().findChild(supposedParentName) != null) { - return true; + String parentName = name.substring(0, index), childName = name.substring(index + 1); + if (file.getParent().findChild(parentName + ".class") != null) { + return isInnerClass(file, parentName, childName); } } return false; } + private static boolean isInnerClass(VirtualFile file, final String parentName, final String childName) { + Boolean isInner = IS_INNER_CLASS.get(file); + if (isInner != null) return isInner; + + final Ref ref = Ref.create(Boolean.FALSE); + try { + new ClassReader(file.contentsToByteArray(false)).accept(new ClassVisitor(Opcodes.ASM5) { + @Override + public void visitOuterClass(String owner, String name, String desc) { + ref.set(Boolean.TRUE); + } + + @Override + public void visitInnerClass(String name, String outer, String inner, int access) { + if (inner == null || childName.equals(inner) && outer != null && parentName.equals(outer.substring(outer.lastIndexOf('/') + 1))) { + ref.set(Boolean.TRUE); + } + } + }, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + } + catch (IOException e) { + Logger.getInstance(ClassFileViewProvider.class).info(e); + } + + isInner = ref.get(); + IS_INNER_CLASS.set(file, isInner); + return isInner; + } + @NotNull @Override public SingleRootFileViewProvider createCopy(@NotNull VirtualFile copy) { diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/compiled/ClassFileStubBuilder.java b/java/java-psi-impl/src/com/intellij/psi/impl/compiled/ClassFileStubBuilder.java index 4dae8766dc8b..40295b255eeb 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/compiled/ClassFileStubBuilder.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/compiled/ClassFileStubBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2016 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. @@ -51,30 +51,32 @@ public class ClassFileStubBuilder implements BinaryFileStubBuilder { byte[] content = fileContent.getContent(); try { - file.setPreloadedContentHint(content); - ClassFileDecompilers.Decompiler decompiler = ClassFileDecompilers.find(file); - if (decompiler instanceof Full) { - return ((Full)decompiler).getStubBuilder().buildFileStub(fileContent); + try { + file.setPreloadedContentHint(content); + ClassFileDecompilers.Decompiler decompiler = ClassFileDecompilers.find(file); + if (decompiler instanceof Full) { + return ((Full)decompiler).getStubBuilder().buildFileStub(fileContent); + } + } + catch (ClsFormatException e) { + LOG.debug(e); + } + + try { + PsiFileStub stub = ClsFileImpl.buildFileStub(file, content); + if (stub == null && fileContent.getFileName().indexOf('$') >= 0) { + LOG.info("No stub built for file " + fileContent); + } + return stub; + } + catch (ClsFormatException e) { + LOG.debug(e); } - } - catch (ClsFormatException e) { - LOG.debug(e); } finally { file.setPreloadedContentHint(null); } - try { - PsiFileStub stub = ClsFileImpl.buildFileStub(file, content); - if (stub == null && !fileContent.getFileName().contains("$")) { - LOG.info("No stub built for file " + fileContent); - } - return stub; - } - catch (ClsFormatException e) { - LOG.debug(e); - } - return null; } @@ -99,4 +101,4 @@ public class ClassFileStubBuilder implements BinaryFileStubBuilder { return version; } -} +} \ No newline at end of file diff --git a/java/java-tests/testData/psi/cls/mirror/pkg/KindaInner$Class.class b/java/java-tests/testData/psi/cls/mirror/pkg/KindaInner$Class.class new file mode 100644 index 000000000000..ba7b88b931aa Binary files /dev/null and b/java/java-tests/testData/psi/cls/mirror/pkg/KindaInner$Class.class differ diff --git a/java/java-tests/testData/psi/cls/mirror/pkg/KindaInner.class b/java/java-tests/testData/psi/cls/mirror/pkg/KindaInner.class new file mode 100644 index 000000000000..3379de74b299 Binary files /dev/null and b/java/java-tests/testData/psi/cls/mirror/pkg/KindaInner.class differ diff --git a/java/java-tests/testData/psi/cls/mirror/src/pkg/KindaInner$Class.java b/java/java-tests/testData/psi/cls/mirror/src/pkg/KindaInner$Class.java new file mode 100644 index 000000000000..84a0dde32da6 --- /dev/null +++ b/java/java-tests/testData/psi/cls/mirror/src/pkg/KindaInner$Class.java @@ -0,0 +1,3 @@ +package pkg; + +class KindaInner$Class { } \ No newline at end of file diff --git a/java/java-tests/testData/psi/cls/mirror/src/pkg/KindaInner.java b/java/java-tests/testData/psi/cls/mirror/src/pkg/KindaInner.java new file mode 100644 index 000000000000..7c01c26695c3 --- /dev/null +++ b/java/java-tests/testData/psi/cls/mirror/src/pkg/KindaInner.java @@ -0,0 +1,3 @@ +package pkg; + +class KindaInner { } \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/psi/ClsMirrorBuildingTest.java b/java/java-tests/testSrc/com/intellij/psi/ClsMirrorBuildingTest.java index 6c2506f9b38c..ba52a13cdb23 100644 --- a/java/java-tests/testSrc/com/intellij/psi/ClsMirrorBuildingTest.java +++ b/java/java-tests/testSrc/com/intellij/psi/ClsMirrorBuildingTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2016 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. @@ -161,6 +161,14 @@ public class ClsMirrorBuildingTest extends LightIdeaTestCase { } } + public void testInnerClassDetection() { + assertTrue(isInner("Nested$Inner1")); + assertTrue(isInner("Nested$Inner1$Inner2")); + assertTrue(isInner("NormalClass$1")); + assertTrue(isInner("LocalClass$1MyRunnable")); + assertFalse(isInner("KindaInner$Class")); + } + private static String getTestDataDir() { return JavaTestUtil.getJavaTestDataPath() + "/psi/cls/mirror/"; } @@ -188,4 +196,10 @@ public class ClsMirrorBuildingTest extends LightIdeaTestCase { assertEquals(expected, ClsFileImpl.decompile(file).toString()); } -} + + private static boolean isInner(String name) { + VirtualFile file = StandardFileSystems.local().findFileByPath(getTestDataDir() + "pkg/" + name + ".class"); + assertNotNull(file); + return ClassFileViewProvider.isInnerClass(file); + } +} \ No newline at end of file