Decompiler: remove project from decompilation API

Remove JetDecompiledData, DecompiledText is enough
Move JetDecompiled to upper level, rename to JetClsFile
Move caches into JetClsFile and JetClassFileViewProvider
Rewrite JetClassFileViewProvider and JetClsFile to Kotlin
DecompiledDataFactory -> DecompiledTextFactory
This commit is contained in:
Pavel V. Talanov
2014-02-26 19:36:59 +04:00
parent 5bd9955785
commit b5c119200f
9 changed files with 134 additions and 193 deletions
@@ -19,6 +19,8 @@ package org.jetbrains.jet.plugin.libraries;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.descriptors.*;
@@ -57,8 +59,11 @@ public final class DecompiledNavigationUtils {
if (virtualFile == null || !DecompiledUtils.isKotlinCompiledFile(virtualFile)) return null;
JetDecompiledData data = JetDecompiledData.getDecompiledData(virtualFile, project);
JetDeclaration jetDeclaration = data.getDeclarationForDescriptor(effectiveReferencedDescriptor);
PsiFile psiFile = PsiManager.getInstance(project).findFile(virtualFile);
if (!(psiFile instanceof JetClsFile)) {
return null;
}
JetDeclaration jetDeclaration = ((JetClsFile) psiFile).getDeclarationForDescriptor(effectiveReferencedDescriptor);
if (jetDeclaration != null) {
return jetDeclaration;
}
@@ -16,15 +16,11 @@
package org.jetbrains.jet.plugin.libraries
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.TextRange
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiManager
import org.jetbrains.jet.lang.descriptors.*
import org.jetbrains.jet.lang.psi.JetFile
import org.jetbrains.jet.lang.resolve.MemberComparator
import org.jetbrains.jet.lang.resolve.kotlin.KotlinBinaryClassCache
import org.jetbrains.jet.lang.resolve.kotlin.KotlinJvmBinaryClass
import org.jetbrains.jet.lang.resolve.kotlin.header.KotlinClassHeader
import org.jetbrains.jet.lang.resolve.name.FqName
import org.jetbrains.jet.renderer.DescriptorRenderer
@@ -32,21 +28,19 @@ import org.jetbrains.jet.renderer.DescriptorRendererBuilder
import java.util.*
import org.jetbrains.jet.lang.resolve.DescriptorUtils.isEnumEntry
import org.jetbrains.jet.lang.resolve.DescriptorUtils.isSyntheticClassObject
import org.jetbrains.jet.plugin.libraries.JetDecompiledData.descriptorToKey
public fun buildDecompiledData(classFile: VirtualFile, project: Project): JetDecompiledData {
return buildDecompiledData(classFile, project, DeserializerForDecompiler(classFile))
}
public fun buildDecompiledData(classFile: VirtualFile, project: Project, resolver: ResolverForDecompiler): JetDecompiledData {
public fun buildDecompiledText(
classFile: VirtualFile,
resolver: ResolverForDecompiler = DeserializerForDecompiler(classFile)
): DecompiledText {
val kotlinClass = KotlinBinaryClassCache.getKotlinBinaryClass(classFile)
val classFqName = kotlinClass.getClassName().getFqNameForClassNameWithoutDollars()
val classFileHeader = kotlinClass.getClassHeader()
assert(classFileHeader != null) { "Decompiled data factory shouldn't be called on an unsupported file: " + classFile }
val kind = classFileHeader!!.getKind()
val packageFqName = classFqName.parent()
val (text, renderedDescriptorsToRange) =
if (kind == KotlinClassHeader.Kind.PACKAGE_FACADE) {
return if (kind == KotlinClassHeader.Kind.PACKAGE_FACADE) {
buildDecompiledText(packageFqName, ArrayList(resolver.resolveDeclarationsInPackage(packageFqName)))
}
else if (kind == KotlinClassHeader.Kind.CLASS) {
@@ -56,17 +50,19 @@ public fun buildDecompiledData(classFile: VirtualFile, project: Project, resolve
// TODO: support other header kinds: for trait-impl show the trait, for package fragment - the whole package
throw UnsupportedOperationException("Unknown header kind: " + kind)
}
val jetFile = JetDummyClassFileViewProvider.createJetFile(PsiManager.getInstance(project), classFile, text)
return JetDecompiledData(jetFile, renderedDescriptorsToRange)
}
private val DECOMPILED_COMMENT: String = "/* compiled code */"
private val DECOMPILED_COMMENT = "/* compiled code */"
public val descriptorRendererForDecompiler: DescriptorRenderer = DescriptorRendererBuilder()
.setWithDefinedIn(false)
.setClassWithPrimaryConstructor(true)
.build()
//TODO: should use more accurate way to identify descriptors
public fun descriptorToKey(descriptor: DeclarationDescriptor): String {
return descriptorRendererForDecompiler.render(descriptor)
}
private data class DecompiledText(val text: String, val renderedDescriptorsToRange: Map<String, TextRange>)
private fun buildDecompiledText(packageFqName: FqName, descriptors: List<DeclarationDescriptor>): DecompiledText {
@@ -40,11 +40,6 @@ public final class DecompiledUtils {
return header != null && header.getKind() == KotlinClassHeader.Kind.PACKAGE_FRAGMENT;
}
public static CharSequence decompile(@NotNull VirtualFile file) {
Project project = ProjectManager.getInstance().getOpenProjects()[0]; // FIXME: get rid of project usage here
return JetDecompiledData.getDecompiledData(file, project).getFileText();
}
private DecompiledUtils() {
}
}
@@ -1,64 +0,0 @@
/*
* Copyright 2010-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.
*/
package org.jetbrains.jet.plugin.libraries;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.compiled.ClsFileImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.plugin.JetLanguage;
public class JetClassFileViewProvider extends SingleRootFileViewProvider {
private final boolean isInternal;
public JetClassFileViewProvider(@NotNull PsiManager manager, @NotNull VirtualFile file, boolean physical, boolean internal) {
super(manager, file, physical, JetLanguage.INSTANCE);
isInternal = internal;
}
@NotNull
@Override
public CharSequence getContents() {
return isInternal ? "" : DecompiledUtils.decompile(getVirtualFile());
}
@Nullable
@Override
protected PsiFile createFile(@NotNull Project project, @NotNull VirtualFile file, @NotNull FileType fileType) {
return isInternal ? null : new JetDecompiledFile(this);
}
@NotNull
@Override
public SingleRootFileViewProvider createCopy(@NotNull VirtualFile copy) {
return new JetClassFileViewProvider(getManager(), copy, false, isInternal);
}
private static class JetDecompiledFile extends ClsFileImpl {
public JetDecompiledFile(FileViewProvider viewProvider) {
super(viewProvider);
}
@Override
public PsiFile getDecompiledPsiFile() {
return JetDecompiledData.getDecompiledData(getVirtualFile(), getProject()).getFile();
}
}
}
@@ -0,0 +1,50 @@
/*
* Copyright 2010-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.
*/
package org.jetbrains.jet.plugin.libraries
import com.intellij.openapi.fileTypes.FileType
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import com.intellij.psi.SingleRootFileViewProvider
import org.jetbrains.annotations.Nullable
import org.jetbrains.jet.plugin.JetLanguage
import kotlin.properties.Delegates
public class JetClassFileViewProvider(
manager: PsiManager,
file: VirtualFile,
physical: Boolean,
val isInternal: Boolean
) : SingleRootFileViewProvider(manager, file, physical, JetLanguage.INSTANCE) {
val decompiledText by Delegates.blockingLazy(this) {
buildDecompiledText(getVirtualFile())
}
override fun getContents(): CharSequence {
return if (isInternal) "" else decompiledText.text
}
override fun createFile(project: Project, file: VirtualFile, fileType: FileType): PsiFile? {
return if (isInternal) null else JetClsFile(this)
}
override fun createCopy(copy: VirtualFile): SingleRootFileViewProvider {
return JetClassFileViewProvider(getManager(), copy, false, isInternal)
}
}
@@ -0,0 +1,51 @@
/*
* Copyright 2010-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.
*/
package org.jetbrains.jet.plugin.libraries
import com.intellij.openapi.util.TextRange
import com.intellij.psi.impl.compiled.ClsFileImpl
import com.intellij.psi.util.PsiTreeUtil
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 kotlin.properties.Delegates
import com.intellij.psi.PsiFile
public class JetClsFile(val provider: JetClassFileViewProvider) : ClsFileImpl(provider) {
private val decompiledFile by Delegates.blockingLazy(this) {
JetDummyClassFileViewProvider.createJetFile(provider.getManager(), getVirtualFile(), provider.decompiledText.text)
}
override fun getDecompiledPsiFile() = decompiledFile
public fun getDeclarationForDescriptor(descriptor: DeclarationDescriptor): JetDeclaration? {
val key = descriptorToKey(descriptor)
val range = provider.decompiledText.renderedDescriptorsToRange[key]
if (range == null) {
return null
}
return PsiTreeUtil.findElementOfClassAtRange(decompiledFile, range.getStartOffset(), range.getEndOffset(), javaClass<JetDeclaration>())
}
TestOnly
fun getRenderedDescriptorsToRange(): Map<String, TextRange> {
return provider.decompiledText.renderedDescriptorsToRange
}
}
@@ -1,101 +0,0 @@
/*
* Copyright 2010-2013 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.jet.plugin.libraries;
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.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 java.util.Map;
import static org.jetbrains.jet.plugin.libraries.LibrariesPackage.buildDecompiledData;
@SuppressWarnings("FieldAccessedSynchronizedAndUnsynchronized")
public class JetDecompiledData {
private static final Key<JetDecompiledData> USER_DATA_KEY = new Key<JetDecompiledData>("USER_DATA_KEY");
private static final Object LOCK = new String("decompiled data lock");
@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 String getFileText() {
return file.getText();
}
@NotNull
public static JetDecompiledData getDecompiledData(@NotNull VirtualFile virtualFile, @NotNull Project project) {
synchronized (LOCK) {
JetDecompiledData cachedData = virtualFile.getUserData(USER_DATA_KEY);
//TODO: use cached value that keeps modification tracking for this virtual file
if (cachedData == null || cachedData.getProject() != project) {
virtualFile.putUserData(USER_DATA_KEY, buildDecompiledData(virtualFile, project));
}
JetDecompiledData decompiledData = virtualFile.getUserData(USER_DATA_KEY);
assert decompiledData != null;
return decompiledData;
}
}
@TestOnly
@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 LibrariesPackage.getDescriptorRendererForDecompiler().render(descriptor);
}
@NotNull
public JetFile getFile() {
return file;
}
@NotNull
public Project getProject() {
return file.getProject();
}
}
@@ -43,9 +43,9 @@ public class DecompiledTextConsistencyTest : JetLightCodeInsightFixtureTestCase(
val kotlinPackageClass = myFixture!!.getJavaFacade()!!.findClass(packageClassFqName.asString())!!
val kotlinPackageFile = kotlinPackageClass.getContainingFile()!!.getVirtualFile()!!
val project = getProject()!!
val projectBasedText = buildDecompiledData(kotlinPackageFile, project, ProjectBasedResolverForDecompiler(project)).getFileText()
val projectBasedText = buildDecompiledText(kotlinPackageFile, ProjectBasedResolverForDecompiler(project)).text
val deserializerForDecompiler = DeserializerForDecompiler(kotlinPackageFile.getParent()!!, STANDARD_LIBRARY_FQNAME)
val deserializedText = buildDecompiledData(kotlinPackageFile, project, deserializerForDecompiler).getFileText()
val deserializedText = buildDecompiledText(kotlinPackageFile, deserializerForDecompiler).text
Assert.assertEquals(projectBasedText, deserializedText)
// sanity checks
Assert.assertTrue(projectBasedText.contains("linkedListOf"))
@@ -27,6 +27,9 @@ import com.intellij.openapi.roots.OrderRootType;
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.PsiManager;
import com.intellij.psi.impl.compiled.ClsFileImpl;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.testFramework.LightProjectDescriptor;
import org.jetbrains.annotations.NotNull;
@@ -86,9 +89,15 @@ public class NavigateToDecompiledLibraryTest extends AbstractNavigateToLibraryTe
private void doTest() {
classFile = getClassFile(PACKAGE, getTestName(false), myModule);
JetDecompiledData decompiledData = JetDecompiledData.getDecompiledData(classFile, getProject());
Map<String, JetDeclaration> map = getRenderedDescriptorToKotlinPsiMap(decompiledData.getFile(),
decompiledData.getRenderedDescriptorsToRanges());
PsiFile clsFileForClassFile = PsiManager.getInstance(getProject()).findFile(classFile);
assertNotNull(clsFileForClassFile);
assertTrue("Expecting kotlin class file, was: " + clsFileForClassFile.getClass(), clsFileForClassFile instanceof JetClsFile);
PsiFile decompiledPsiFile = ((ClsFileImpl) clsFileForClassFile).getDecompiledPsiFile();
assertNotNull(decompiledPsiFile);
assertTrue("Expecting decompiled Kotlin file, was: " + decompiledPsiFile.getClass(), decompiledPsiFile instanceof JetFile);
Map<String, JetDeclaration> map = getRenderedDescriptorToKotlinPsiMap(
(JetFile) decompiledPsiFile, ((JetClsFile) clsFileForClassFile).getRenderedDescriptorsToRange()
);
String decompiledTextWithMarks = getDecompiledTextWithMarks(map);
assertSameLinesWithFile(TEST_DATA_PATH + "/decompiled/" + getTestName(false) + ".kt", decompiledTextWithMarks);