Parcelable: Add Parcelable functionality to Android Extensions plugin
This commit is contained in:
@@ -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">
|
||||
|
||||
+173
@@ -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++ }
|
||||
}
|
||||
}
|
||||
}
|
||||
+71
@@ -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"))))
|
||||
}
|
||||
}
|
||||
+32
@@ -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")
|
||||
}
|
||||
+74
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user