[java] better inner class detection (IDEA-132606)

This commit is contained in:
Roman Shevchenko
2016-01-05 13:12:14 +01:00
parent 01fc1dc40f
commit d02243ef41
7 changed files with 89 additions and 28 deletions
@@ -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<Boolean> 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<Boolean> 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) {
@@ -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;
}
}
}
@@ -0,0 +1,3 @@
package pkg;
class KindaInner$Class { }
@@ -0,0 +1,3 @@
package pkg;
class KindaInner { }
@@ -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);
}
}