Parcelable: Add Parcelable functionality to Android Extensions plugin

This commit is contained in:
Yan Zhulanow
2017-06-16 16:52:29 +03:00
parent edd8a0a64e
commit c23bca6afe
47 changed files with 2979 additions and 3 deletions
+2
View File
@@ -16,6 +16,8 @@
<orderEntry type="module" module-name="util" />
<orderEntry type="library" scope="TEST" name="idea-full" level="project" />
<orderEntry type="library" name="jps-test" level="project" />
<orderEntry type="library" scope="TEST" name="robolectric" level="project" />
<orderEntry type="library" scope="TEST" name="ant" level="project" />
<orderEntry type="library" name="android-plugin" level="project" />
<orderEntry type="module-library" scope="TEST">
<library name="idea-gradle-plugin">
@@ -0,0 +1,173 @@
/*
* Copyright 2010-2017 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.kotlin.android.parcel
import org.jetbrains.kotlin.codegen.CodegenTestCase
import org.jetbrains.kotlin.codegen.getClassFiles
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.org.objectweb.asm.ClassReader
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.Opcodes.*
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.tree.*
import org.jetbrains.org.objectweb.asm.util.Printer
import java.io.File
private val LINE_SEPARATOR = System.getProperty("line.separator")
abstract class AbstractAsmLikeInstructionListingTest : CodegenTestCase() {
private companion object {
val CURIOUS_ABOUT_DIRECTIVE = "// CURIOUS_ABOUT "
}
override fun doMultiFileTest(wholeFile: File, files: List<TestFile>, javaFilesDir: File?) {
val txtFile = File(wholeFile.parentFile, wholeFile.nameWithoutExtension + ".txt")
compile(files, javaFilesDir)
val classes = classFileFactory
.getClassFiles()
.sortedBy { it.relativePath }
.map { file -> ClassNode().also { ClassReader(file.asByteArray()).accept(it, ClassReader.EXPAND_FRAMES) } }
val printBytecodeForTheseMethods = wholeFile.readLines()
.filter { it.startsWith(CURIOUS_ABOUT_DIRECTIVE) }
.map { it.substring(CURIOUS_ABOUT_DIRECTIVE.length) }
.flatMap { it.split(',').map { it.trim() } }
KotlinTestUtils.assertEqualsToFile(txtFile, classes.joinToString(LINE_SEPARATOR.repeat(2)) {
renderClassNode(it, printBytecodeForTheseMethods)
})
}
private fun renderClassNode(clazz: ClassNode, printBytecodeForTheseMethods: List<String>): String {
val fields = (clazz.fields ?: emptyList()).sortedBy { it.name }
val methods = (clazz.methods ?: emptyList()).sortedBy { it.name }
val superTypes = (listOf(clazz.superName) + clazz.interfaces).filterNotNull()
return buildString {
renderVisibilityModifiers(clazz.access)
renderModalityModifiers(clazz.access)
append(if ((clazz.access and ACC_INTERFACE) != 0) "interface " else "class ")
append(clazz.name)
if (superTypes.isNotEmpty()) {
append(" : " + superTypes.joinToString())
}
appendln(" {")
fields.joinTo(this, LINE_SEPARATOR.repeat(2)) { renderField(it).withMargin() }
if (fields.isNotEmpty()) {
appendln().appendln()
}
methods.joinTo(this, LINE_SEPARATOR.repeat(2)) {
val printBytecode = printBytecodeForTheseMethods.contains(it.name)
renderMethod(it, printBytecode).withMargin()
}
appendln().append("}")
}
}
private fun renderField(field: FieldNode) = buildString {
renderVisibilityModifiers(field.access)
renderModalityModifiers(field.access)
append(Type.getType(field.desc).className).append(' ')
append(field.name)
}
private fun renderMethod(method: MethodNode, printBytecode: Boolean) = buildString {
renderVisibilityModifiers(method.access)
renderModalityModifiers(method.access)
val (returnType, parameterTypes) = with(Type.getMethodType(method.desc)) { returnType to argumentTypes }
append(returnType.className).append(' ')
append(method.name)
parameterTypes.mapIndexed { index, type -> "${type.className} p$index" }.joinTo(this, prefix = "(", postfix = ")")
if (printBytecode && (method.access and ACC_ABSTRACT) == 0) {
appendln(" {")
append(renderBytecodeInstructions(method.instructions).trimEnd().withMargin())
appendln().append("}")
}
}
private fun renderBytecodeInstructions(instructions: InsnList) = buildString {
val labelMappings = LabelMappings()
var currentInsn = instructions.first
while (currentInsn != null) {
renderInstruction(currentInsn, labelMappings)
currentInsn = currentInsn.next
}
}
private fun StringBuilder.renderInstruction(node: AbstractInsnNode, labelMappings: LabelMappings) {
if (node is LabelNode) {
appendln("LABEL (L" + labelMappings[node.label] + ")")
return
}
if (node is LineNumberNode) {
appendln("LINENUMBER (" + node.line + ")")
return
}
if (node is FrameNode) return
append(" ").append(Printer.OPCODES[node.opcode] ?: error("Invalid opcode ${node.opcode}"))
when (node) {
is FieldInsnNode -> append(" (" + node.name + ", " + node.desc + ")")
is JumpInsnNode -> append(" (L" + labelMappings[node.label.label] + ")")
is IntInsnNode -> append(" (" + node.operand + ")")
is MethodInsnNode -> append(" (" + node.owner + ", "+ node.name + ", " + node.desc + ")")
is VarInsnNode -> append(" (" + node.`var` + ")")
is LdcInsnNode -> append(" (" + node.cst + ")")
}
appendln()
}
private fun String.withMargin(margin: String = " "): String {
return lineSequence().map { margin + it }.joinToString(LINE_SEPARATOR)
}
private fun StringBuilder.renderVisibilityModifiers(access: Int) {
if ((access and ACC_PUBLIC) != 0) append("public ")
if ((access and ACC_PRIVATE) != 0) append("private ")
if ((access and ACC_PROTECTED) != 0) append("protected ")
}
private fun StringBuilder.renderModalityModifiers(access: Int) {
if ((access and ACC_FINAL) != 0) append("final ")
if ((access and ACC_ABSTRACT) != 0) append("abstract ")
if ((access and ACC_STATIC) != 0) append("static ")
}
private class LabelMappings {
private var mappings = hashMapOf<Int, Int>()
private var currentIndex = 0
operator fun get(label: Label): Int {
val hashCode = System.identityHashCode(label)
return mappings.getOrPut(hashCode) { currentIndex++ }
}
}
}
@@ -0,0 +1,71 @@
/*
* Copyright 2010-2017 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.kotlin.android.parcel
import org.jetbrains.kotlin.android.synthetic.AndroidComponentRegistrar
import org.jetbrains.kotlin.android.synthetic.test.addAndroidExtensionsRuntimeLibrary
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
import org.jetbrains.kotlin.codegen.CodegenTestCase
import org.jetbrains.kotlin.codegen.getClassFiles
import org.jetbrains.org.objectweb.asm.ClassReader
import org.jetbrains.org.objectweb.asm.tree.ClassNode
import java.io.File
import java.net.URLClassLoader
import java.nio.ByteBuffer
import java.security.ProtectionDomain
abstract class AbstractParcelBoxTest : CodegenTestCase() {
protected companion object {
val BASE_DIR = "plugins/android-extensions/android-extensions-compiler/testData/parcel/box"
val LIBRARY_KT = File(File(BASE_DIR).parentFile, "boxLib.kt")
}
override fun doTest(filePath: String) {
super.doTest(File(BASE_DIR, filePath + ".kt").absolutePath)
}
open protected fun getClassLoaderWithGeneratedFiles(): ClassLoader {
return object : URLClassLoader(arrayOf(), this::class.java.classLoader) {
init {
for (classFile in classFileFactory.getClassFiles()) {
val bytes = classFile.asByteArray()
val className = ClassNode().also { ClassReader(bytes).accept(it, ClassReader.EXPAND_FRAMES) }.name
defineClass(className.replace('/', '.'), ByteBuffer.wrap(bytes), null as ProtectionDomain?)
}
}
}
}
override fun doMultiFileTest(wholeFile: File, files: List<TestFile>, javaFilesDir: File?) {
compile(files + TestFile(LIBRARY_KT.name, LIBRARY_KT.readText()), javaFilesDir)
val testClass = Class.forName("test.TestKt", false, getClassLoaderWithGeneratedFiles())
try {
testClass.getDeclaredMethod("box").invoke(testClass)
} catch (e: Throwable) {
throw AssertionError(classFileFactory.createText(), e)
}
}
override fun setupEnvironment(environment: KotlinCoreEnvironment) {
AndroidComponentRegistrar.registerParcelExtensions(environment.project)
addAndroidExtensionsRuntimeLibrary(environment)
environment.updateClasspath(listOf(JvmClasspathRoot(File("ideaSDK/plugins/android/lib/layoutlib.jar"))))
}
}
@@ -0,0 +1,32 @@
/*
* Copyright 2010-2017 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.kotlin.android.parcel
import org.jetbrains.kotlin.android.synthetic.AndroidComponentRegistrar
import org.jetbrains.kotlin.android.synthetic.test.addAndroidExtensionsRuntimeLibrary
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
import java.io.File
abstract class AbstractParcelBytecodeListingTest : AbstractAsmLikeInstructionListingTest() {
override fun setupEnvironment(environment: KotlinCoreEnvironment) {
AndroidComponentRegistrar.registerParcelExtensions(environment.project)
addAndroidExtensionsRuntimeLibrary(environment)
environment.updateClasspath(listOf(JvmClasspathRoot(File("ideaSDK/plugins/android/lib/layoutlib.jar"))))
}
}
@@ -0,0 +1,45 @@
/*
* Copyright 2010-2017 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.kotlin.android.parcel
import android.os.Bundle
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
// This class is not generated because it uses the custom test runner
@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.NONE)
class ParcelBoxTest : AbstractParcelBoxTest() {
@Test fun simple() = doTest("simple")
@Test fun primitiveTypes() = doTest("primitiveTypes")
@Test fun boxedTypes() = doTest("boxedTypes")
@Test fun nullableTypesSimple() = doTest("nullableTypesSimple")
@Test fun nullableTypes() = doTest("nullableTypes")
@Test fun listSimple() = doTest("listSimple")
@Test fun lists() = doTest("lists")
@Test fun listKinds() = doTest("listKinds")
@Test fun arraySimple() = doTest("arraySimple")
@Test fun arrays() = doTest("arrays")
@Test fun mapSimple() = doTest("mapSimple")
@Test fun maps() = doTest("maps")
@Test fun mapKinds() = doTest("mapKinds")
@Test fun sparseBooleanArray() = doTest("sparseBooleanArray")
@Test fun bundle() = doTest("bundle")
@Test fun sparseArrays() = doTest("sparseArrays")
}
@@ -0,0 +1,74 @@
/*
* Copyright 2010-2017 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.kotlin.android.parcel;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TargetBackend;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("plugins/android-extensions/android-extensions-compiler/testData/parcel/codegen")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class ParcelBytecodeListingTestGenerated extends AbstractParcelBytecodeListingTest {
public void testAllFilesPresentInCodegen() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("plugins/android-extensions/android-extensions-compiler/testData/parcel/codegen"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("listInsideList.kt")
public void testListInsideList() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("plugins/android-extensions/android-extensions-compiler/testData/parcel/codegen/listInsideList.kt");
doTest(fileName);
}
@TestMetadata("nullableNotNullSize.kt")
public void testNullableNotNullSize() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("plugins/android-extensions/android-extensions-compiler/testData/parcel/codegen/nullableNotNullSize.kt");
doTest(fileName);
}
@TestMetadata("serializable.kt")
public void testSerializable() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("plugins/android-extensions/android-extensions-compiler/testData/parcel/codegen/serializable.kt");
doTest(fileName);
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("plugins/android-extensions/android-extensions-compiler/testData/parcel/codegen/simple.kt");
doTest(fileName);
}
@TestMetadata("simpleList.kt")
public void testSimpleList() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("plugins/android-extensions/android-extensions-compiler/testData/parcel/codegen/simpleList.kt");
doTest(fileName);
}
@TestMetadata("size.kt")
public void testSize() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("plugins/android-extensions/android-extensions-compiler/testData/parcel/codegen/size.kt");
doTest(fileName);
}
}