Restructure JetDecompiledData

JetDecompiledData now stores file text and uses descriptors serialized to String to find declarations
This commit is contained in:
Pavel V. Talanov
2013-08-05 19:31:43 +04:00
parent b634792356
commit 385c15d0e2
7 changed files with 134 additions and 160 deletions
@@ -16,74 +16,64 @@
package org.jetbrains.jet.plugin.libraries;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiField;
import com.intellij.psi.impl.compiled.ClsElementImpl;
import com.intellij.psi.impl.compiled.ClsFileImpl;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.di.InjectorForJavaDescriptorResolver;
import org.jetbrains.jet.lang.descriptors.*;
import org.jetbrains.jet.lang.psi.JetDeclaration;
import org.jetbrains.jet.lang.psi.JetFile;
import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.resolve.BindingContextUtils;
import org.jetbrains.jet.lang.resolve.BindingTraceContext;
import org.jetbrains.jet.lang.resolve.MemberComparator;
import org.jetbrains.jet.lang.resolve.java.DescriptorResolverUtils;
import org.jetbrains.jet.lang.resolve.java.DescriptorSearchRule;
import org.jetbrains.jet.lang.resolve.java.JavaDescriptorResolver;
import org.jetbrains.jet.lang.resolve.java.JvmAbi;
import org.jetbrains.jet.lang.resolve.java.resolver.KotlinClassFileHeader;
import org.jetbrains.jet.lang.resolve.name.FqName;
import org.jetbrains.jet.renderer.DescriptorRenderer;
import org.jetbrains.jet.renderer.DescriptorRendererBuilder;
import java.util.*;
public class DecompiledDataFactory {
import static org.jetbrains.jet.plugin.libraries.JetDecompiledData.descriptorToKey;
public final class DecompiledDataFactory {
private static final String DECOMPILED_COMMENT = "/* compiled code */";
private static final DescriptorRenderer DESCRIPTOR_RENDERER =
public static final DescriptorRenderer DESCRIPTOR_RENDERER =
new DescriptorRendererBuilder().setWithDefinedIn(false).setClassWithPrimaryConstructor(true).build();
@NotNull
private final StringBuilder builder = new StringBuilder();
private final ClsFileImpl clsFile;
private final BindingContext bindingContext;
private final Map<PsiElement, TextRange> clsMembersToRanges = new HashMap<PsiElement, TextRange>();
private final Map<ClsElementImpl, JetDeclaration> clsElementsToJetElements = new HashMap<ClsElementImpl, JetDeclaration>();
@NotNull
private final Map<String, TextRange> renderedDescriptorsToRange = new HashMap<String, TextRange>();
@NotNull
private final JavaDescriptorResolver javaDescriptorResolver;
@NotNull
private final KotlinClassFileHeader kotlinClassFileHeader;
@NotNull
private final VirtualFile classFile;
@NotNull
private final Project project;
private DecompiledDataFactory(ClsFileImpl clsFile) {
this.clsFile = clsFile;
BindingTraceContext trace = new BindingTraceContext();
this.bindingContext = trace.getBindingContext();
InjectorForJavaDescriptorResolver injector = new InjectorForJavaDescriptorResolver(clsFile.getProject(), trace);
private DecompiledDataFactory(@NotNull VirtualFile classFile, @NotNull Project project) {
this.classFile = classFile;
this.project = project;
InjectorForJavaDescriptorResolver injector =
new InjectorForJavaDescriptorResolver(project, new BindingTraceContext());
this.javaDescriptorResolver = injector.getJavaDescriptorResolver();
this.kotlinClassFileHeader = KotlinClassFileHeader.readKotlinHeaderFromClassFile(classFile);
}
@NotNull
static JetDecompiledData createDecompiledData(@NotNull ClsFileImpl clsFile) {
return new DecompiledDataFactory(clsFile).build();
static JetDecompiledData createDecompiledData(@NotNull VirtualFile virtualFile, @NotNull Project project) {
return new DecompiledDataFactory(virtualFile, project).build();
}
private JetDecompiledData build() {
builder.append("// IntelliJ API Decompiler stub source generated from a class file\n" +
"// Implementation of methods is not available");
builder.append("\n\n");
String packageName = clsFile.getPackageName();
if (packageName.length() > 0) {
builder.append("package ").append(packageName).append("\n\n");
}
PsiClass psiClass = clsFile.getClasses()[0];
if (DescriptorResolverUtils.isCompiledKotlinPackageClass(psiClass)) {
NamespaceDescriptor nd = javaDescriptorResolver.resolveNamespace(new FqName(packageName), DescriptorSearchRule.INCLUDE_KOTLIN);
FqName packageFqName = kotlinClassFileHeader.getJvmClassName().getFqName().parent();
appendDecompiledTextAndPackageName(packageFqName);
KotlinClassFileHeader.HeaderType type = kotlinClassFileHeader.getType();
if (type == KotlinClassFileHeader.HeaderType.PACKAGE) {
NamespaceDescriptor nd = javaDescriptorResolver.resolveNamespace(packageFqName, DescriptorSearchRule.INCLUDE_KOTLIN);
if (nd != null) {
for (DeclarationDescriptor member : sortDeclarations(nd.getMemberScope().getAllDescriptors())) {
if (member instanceof ClassDescriptor || member instanceof NamespaceDescriptor
@@ -96,29 +86,28 @@ public class DecompiledDataFactory {
}
}
else {
ClassDescriptor cd = javaDescriptorResolver.resolveClass(new FqName(psiClass.getQualifiedName()), DescriptorSearchRule.INCLUDE_KOTLIN);
assert type == KotlinClassFileHeader.HeaderType.CLASS;
ClassDescriptor cd = javaDescriptorResolver.resolveClass(kotlinClassFileHeader.getJvmClassName().getFqName(),
DescriptorSearchRule.INCLUDE_KOTLIN);
if (cd != null) {
appendDescriptor(cd, "");
}
}
JetFile jetFile = JetDummyClassFileViewProvider.createJetFile(clsFile.getManager(), clsFile.getVirtualFile(), builder.toString());
for (Map.Entry<PsiElement, TextRange> clsMemberToRange : clsMembersToRanges.entrySet()) {
PsiElement clsMember = clsMemberToRange.getKey();
assert clsMember instanceof ClsElementImpl;
TextRange range = clsMemberToRange.getValue();
JetDeclaration jetDeclaration = PsiTreeUtil.findElementOfClassAtRange(jetFile, range.getStartOffset(), range.getEndOffset(),
JetDeclaration.class);
assert jetDeclaration != null : "Can't find declaration at " + range + ": "
+ jetFile.getText().substring(range.getStartOffset(), range.getEndOffset());
clsElementsToJetElements.put((ClsElementImpl) clsMember, jetDeclaration);
}
return new JetDecompiledData(jetFile, clsElementsToJetElements);
JetFile jetFile = JetDummyClassFileViewProvider.createJetFile(PsiManager.getInstance(project), classFile, builder.toString());
return new JetDecompiledData(jetFile, renderedDescriptorsToRange);
}
private static List<DeclarationDescriptor> sortDeclarations(Collection<DeclarationDescriptor> input) {
private void appendDecompiledTextAndPackageName(@NotNull FqName packageName) {
builder.append("// IntelliJ API Decompiler stub source generated from a class file\n" +
"// Implementation of methods is not available");
builder.append("\n\n");
if (!packageName.isRoot()) {
builder.append("package ").append(packageName).append("\n\n");
}
}
private static List<DeclarationDescriptor> sortDeclarations(@NotNull Collection<DeclarationDescriptor> input) {
ArrayList<DeclarationDescriptor> r = new ArrayList<DeclarationDescriptor>(input);
Collections.sort(r, MemberComparator.INSTANCE);
return r;
@@ -150,10 +139,11 @@ public class DecompiledDataFactory {
ClassDescriptor classDescriptor = (ClassDescriptor) descriptor;
boolean firstPassed = false;
String subindent = indent + " ";
if (classDescriptor.getClassObjectDescriptor() != null) {
ClassDescriptor classObjectDescriptor = classDescriptor.getClassObjectDescriptor();
if (classObjectDescriptor != null) {
firstPassed = true;
builder.append(subindent);
appendDescriptor(classDescriptor.getClassObjectDescriptor(), subindent);
appendDescriptor(classObjectDescriptor, subindent);
}
for (DeclarationDescriptor member : sortDeclarations(classDescriptor.getDefaultType().getMemberScope().getAllDescriptors())) {
if (member.getContainingDeclaration() != descriptor) {
@@ -180,28 +170,18 @@ public class DecompiledDataFactory {
}
builder.append("\n");
saveClsMemberToRange(descriptor, startOffset, endOffset);
saveDescriptorToRange(descriptor, startOffset, endOffset);
if (descriptor instanceof ClassDescriptor) {
ConstructorDescriptor primaryConstructor = ((ClassDescriptor) descriptor).getUnsubstitutedPrimaryConstructor();
if (primaryConstructor != null) {
saveClsMemberToRange(primaryConstructor, startOffset, endOffset);
saveDescriptorToRange(primaryConstructor, startOffset, endOffset);
}
}
}
private void saveClsMemberToRange(DeclarationDescriptor descriptor, int startOffset, int endOffset) {
PsiElement clsMember = BindingContextUtils.descriptorToDeclaration(bindingContext, descriptor);
if (clsMember != null) {
clsMembersToRanges.put(clsMember, new TextRange(startOffset, endOffset));
if (descriptor instanceof ClassDescriptor && ((ClassDescriptor) descriptor).getKind() == ClassKind.OBJECT) {
assert clsMember instanceof PsiClass;
PsiField instanceField = ((PsiClass) clsMember).findFieldByName(JvmAbi.INSTANCE_FIELD, false);
assert instanceField != null;
clsMembersToRanges.put(instanceField, new TextRange(startOffset, endOffset));
}
}
private void saveDescriptorToRange(DeclarationDescriptor descriptor, int startOffset, int endOffset) {
renderedDescriptorsToRange.put(descriptorToKey(descriptor), new TextRange(startOffset, endOffset));
}
private static boolean isNamedObjectProperty(@NotNull DeclarationDescriptor descriptor) {
@@ -23,12 +23,11 @@ import com.intellij.openapi.fileTypes.SyntaxHighlighter;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.impl.compiled.ClsFileImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.plugin.highlighter.JetHighlighter;
public class JetContentBasedFileSubstitutor implements ContentBasedClassFileProcessor {
public final class JetContentBasedFileSubstitutor implements ContentBasedClassFileProcessor {
@Override
public boolean isApplicable(@Nullable Project project, @NotNull final VirtualFile file) {
@@ -48,16 +47,14 @@ public class JetContentBasedFileSubstitutor implements ContentBasedClassFileProc
return false;
}
return JetDecompiledData.isKotlinFile(project, file);
return DecompiledUtils.isKotlinCompiledFile(file);
}
@NotNull
@Override
public String obtainFileText(Project project, VirtualFile file) {
if (JetDecompiledData.isKotlinFile(project, file)) {
ClsFileImpl clsFile = JetDecompiledData.getClsFile(project, file);
assert clsFile != null;
return JetDecompiledData.getDecompiledData(clsFile).getJetFile().getText();
if (DecompiledUtils.isKotlinCompiledFile(file)) {
return JetDecompiledData.getDecompiledData(file, project).getFileText();
}
return "";
}
@@ -16,85 +16,77 @@
package org.jetbrains.jet.plugin.libraries;
import com.intellij.ide.highlighter.JavaClassFileType;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.impl.compiled.ClsElementImpl;
import com.intellij.psi.impl.compiled.ClsFileImpl;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
import org.jetbrains.jet.lang.psi.JetDeclaration;
import org.jetbrains.jet.lang.psi.JetFile;
import org.jetbrains.jet.lang.resolve.java.DescriptorResolverUtils;
import java.util.Map;
@SuppressWarnings("FieldAccessedSynchronizedAndUnsynchronized")
public class JetDecompiledData {
private final JetFile jetFile;
private final Map<ClsElementImpl, JetDeclaration> clsElementsToJetElements;
private static final Object LOCK = new String("decompiled data lock");
private static final Key<JetDecompiledData> USER_DATA_KEY = new Key<JetDecompiledData>("USER_DATA_KEY");
private static final Object LOCK = new String("decompiled data lock");
JetDecompiledData(JetFile jetFile, Map<ClsElementImpl, JetDeclaration> clsElementJetDeclarationMap) {
this.jetFile = jetFile;
clsElementsToJetElements = clsElementJetDeclarationMap;
@NotNull
private final JetFile file;
@NotNull
private final Map<String, TextRange> renderedDescriptorsToRanges;
JetDecompiledData(@NotNull JetFile file, @NotNull Map<String,TextRange> renderedDescriptorsToRanges) {
this.file = file;
this.renderedDescriptorsToRanges = renderedDescriptorsToRanges;
}
@NotNull
public JetFile getJetFile() {
return jetFile;
}
public JetDeclaration getJetDeclarationByClsElement(ClsElementImpl clsElement) {
return clsElementsToJetElements.get(clsElement);
}
@Nullable
public static ClsFileImpl getClsFile(@NotNull Project project, @NotNull VirtualFile vFile) {
if (!FileTypeManager.getInstance().isFileOfType(vFile, JavaClassFileType.INSTANCE)) {
return null;
}
PsiFile psiFile = PsiManager.getInstance(project).findFile(vFile);
if (!(psiFile instanceof ClsFileImpl)) {
return null;
}
ClsFileImpl clsFile = (ClsFileImpl) psiFile;
if (clsFile.getClasses().length != 1) {
return null;
}
return clsFile;
}
public static boolean isKotlinFile(@NotNull Project project, @NotNull VirtualFile vFile) {
ClsFileImpl clsFile = getClsFile(project, vFile);
return clsFile != null && isKotlinFile(clsFile);
}
public static boolean isKotlinFile(@NotNull ClsFileImpl clsFile) {
return DescriptorResolverUtils.isCompiledKotlinClassOrPackageClass(clsFile.getClasses()[0]);
public String getFileText() {
return file.getText();
}
@NotNull
public static JetDecompiledData getDecompiledData(@NotNull ClsFileImpl clsFile) {
public static JetDecompiledData getDecompiledData(@NotNull VirtualFile virtualFile, @NotNull Project project) {
synchronized (LOCK) {
if (clsFile.getUserData(USER_DATA_KEY) == null) {
clsFile.putUserData(USER_DATA_KEY, DecompiledDataFactory.createDecompiledData(clsFile));
if (virtualFile.getUserData(USER_DATA_KEY) == null) {
virtualFile.putUserData(USER_DATA_KEY, DecompiledDataFactory.createDecompiledData(virtualFile, project));
}
JetDecompiledData decompiledData = clsFile.getUserData(USER_DATA_KEY);
JetDecompiledData decompiledData = virtualFile.getUserData(USER_DATA_KEY);
assert decompiledData != null;
return decompiledData;
}
}
@TestOnly
Map<ClsElementImpl, JetDeclaration> getClsElementsToJetElements() {
return clsElementsToJetElements;
@NotNull
public Map<String, TextRange> getRenderedDescriptorsToRanges() {
return renderedDescriptorsToRanges;
}
@Nullable
public JetDeclaration getDeclarationForDescriptor(@NotNull DeclarationDescriptor descriptor) {
String key = descriptorToKey(descriptor);
TextRange range = renderedDescriptorsToRanges.get(key);
if (range == null) {
return null;
}
return PsiTreeUtil.findElementOfClassAtRange(file, range.getStartOffset(), range.getEndOffset(), JetDeclaration.class);
}
//TODO: should use more accurate way to identify descriptors
@NotNull
static String descriptorToKey(@NotNull DeclarationDescriptor descriptor) {
return DecompiledDataFactory.DESCRIPTOR_RENDERER.render(descriptor);
}
@NotNull
public JetFile getFile() {
return file;
}
}
+4 -4
View File
@@ -4,17 +4,17 @@
package testData.libraries
[[public final enum class Color(rgb: jet.Int) : jet.Enum<testData.libraries.Color> {
public class object {
[[public class object {
[public final val BLUE: testData.libraries.Color] /* compiled code */
[public final val GREEN: testData.libraries.Color] /* compiled code */
[public final val RED: testData.libraries.Color] /* compiled code */
public final fun valueOf(value: jet.String): testData.libraries.Color { /* compiled code */ }
[public final fun valueOf(value: jet.String): testData.libraries.Color { /* compiled code */ }]
public final fun values(): jet.Array<testData.libraries.Color> { /* compiled code */ }
}
[public final fun values(): jet.Array<testData.libraries.Color> { /* compiled code */ }]
}]]
[internal final val rgb: jet.Int] /* compiled code */
}]]
@@ -4,9 +4,9 @@
package testData.libraries
[[public final class WithInnerAndObject() {
public class object {
[[public class object {
[internal final fun foo(): jet.Unit { /* compiled code */ }]
}
}]]
[[internal final class MyInner() {
[internal trait MyInnerInner {
@@ -4,6 +4,6 @@
package testData.libraries
[[public final class WithTraitClassObject() {
public class object : testData.libraries.SimpleTrait {
}
[[public class object : testData.libraries.SimpleTrait {
}]]
}]]
@@ -16,18 +16,21 @@
package org.jetbrains.jet.plugin.libraries;
import com.beust.jcommander.internal.Maps;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.impl.compiled.ClsElementImpl;
import com.intellij.psi.impl.compiled.ClsFileImpl;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.lang.psi.JetDeclaration;
import org.jetbrains.jet.lang.psi.JetFile;
import java.util.Map;
import static org.jetbrains.jet.plugin.libraries.JetDecompiledData.getDecompiledData;
public class NavigateToDecompiledLibraryTest extends AbstractNavigateToLibraryTest {
private VirtualFile classFile;
@@ -77,15 +80,15 @@ public class NavigateToDecompiledLibraryTest extends AbstractNavigateToLibraryTe
private void doTest() {
classFile = getClassFile();
Map<ClsElementImpl, JetDeclaration> map = getDecompiledData(classFile).getClsElementsToJetElements();
checkNavigationElements(map);
JetDecompiledData decompiledData = JetDecompiledData.getDecompiledData(classFile, getProject());
Map<String, JetDeclaration> map = getRenderedDescriptorToKotlinPsiMap(decompiledData.getFile(),
getDecompiledData(classFile, getProject()) .getRenderedDescriptorsToRanges());
String decompiledTextWithMarks = getDecompiledTextWithMarks(map);
assertSameLinesWithFile(TEST_DATA_PATH + "/decompiled/" + getTestName(false) + ".kt", decompiledTextWithMarks);
}
private String getDecompiledTextWithMarks(Map<ClsElementImpl, JetDeclaration> map) {
private String getDecompiledTextWithMarks(Map<String, JetDeclaration> map) {
String decompiledText = getDecompiledText();
int[] openings = new int[decompiledText.length() + 1];
@@ -113,21 +116,6 @@ public class NavigateToDecompiledLibraryTest extends AbstractNavigateToLibraryTe
return document.getText();
}
private JetDecompiledData getDecompiledData(VirtualFile classFile) {
PsiFile classPsiFile = getPsiManager().findFile(classFile);
assertInstanceOf(classPsiFile, ClsFileImpl.class);
ClsFileImpl clsFile = (ClsFileImpl) classPsiFile;
return JetDecompiledData.getDecompiledData(clsFile);
}
private void checkNavigationElements(Map<ClsElementImpl, JetDeclaration> map) {
PsiFile classPsiFile = getPsiManager().findFile(classFile);
for (Map.Entry<ClsElementImpl, JetDeclaration> clsToJet : map.entrySet()) {
assertSame(classPsiFile, clsToJet.getKey().getContainingFile());
assertSame(clsToJet.getValue(), clsToJet.getKey().getNavigationElement());
}
}
private VirtualFile getClassFile() {
VirtualFile packageDir = libraryDir.findFileByRelativePath(PACKAGE.replace(".", "/"));
assertNotNull(packageDir);
@@ -140,4 +128,21 @@ public class NavigateToDecompiledLibraryTest extends AbstractNavigateToLibraryTe
protected boolean isWithSources() {
return false;
}
@NotNull
private static Map<String, JetDeclaration> getRenderedDescriptorToKotlinPsiMap(
@NotNull JetFile file, @NotNull Map<String, TextRange> renderedDescriptorsToRanges
) {
Map<String, JetDeclaration> renderedDescriptorsToJetDeclarations = Maps.newHashMap();
for (Map.Entry<String, TextRange> renderedDescriptorToRange : renderedDescriptorsToRanges.entrySet()) {
String renderedDescriptor = renderedDescriptorToRange.getKey();
TextRange range = renderedDescriptorToRange.getValue();
JetDeclaration jetDeclaration = PsiTreeUtil.findElementOfClassAtRange(file, range.getStartOffset(), range.getEndOffset(),
JetDeclaration.class);
assert jetDeclaration != null : "Can't find declaration at " + range + ": "
+ file.getText().substring(range.getStartOffset(), range.getEndOffset());
renderedDescriptorsToJetDeclarations.put(renderedDescriptor, jetDeclaration);
}
return renderedDescriptorsToJetDeclarations;
}
}