From 5b58a6f9e80a511e57af9b19564288e81600f66e Mon Sep 17 00:00:00 2001 From: Vyacheslav Gerasimov Date: Tue, 18 Apr 2017 18:53:51 +0300 Subject: [PATCH] Add intentions Add/Remove/Redo parcelable implementation #KT-17465 Fixed #KT-12049 Fixed --- .../kotlin/builtins/KotlinBuiltIns.java | 4 + .../kotlin/generators/tests/GenerateTests.kt | 5 + .../{androidUtil.kt => AndroidUtil.kt} | 0 .../org/jetbrains/kotlin/android/KtPsiUtil.kt | 22 + .../kotlin/android/ParcelableUtil.kt | 470 ++++++++++++++++++ .../org/jetbrains/kotlin/android/TypeUtil.kt | 45 ++ .../intention/ImplementParcelableAction.kt | 43 ++ .../android/intention/RedoParcelableAction.kt | 43 ++ .../intention/RemoveParcelableAction.kt | 43 ++ .../intention/AbstractAndroidIntentionTest.kt | 73 +++ .../AndroidIntentionTestGenerated.java | 197 ++++++++ .../AbstractAndroidLintQuickfixTest.kt | 2 +- .../AndroidLintQuickfixTestGenerated.java | 21 + .../after.kt.template | 27 + .../before.kt.template | 1 + .../description.html | 4 + .../RedoParcelableAction/after.kt.template | 27 + .../RedoParcelableAction/before.kt.template | 23 + .../RedoParcelableAction/description.html | 5 + .../RemoveParcelableAction/after.kt.template | 3 + .../RemoveParcelableAction/before.kt.template | 27 + .../RemoveParcelableAction/description.html | 5 + idea/src/META-INF/android.xml | 14 + .../implementParcelable/allNullableTypes.kt | 86 ++++ .../allNullableTypes.kt.expected | 160 ++++++ .../intention/implementParcelable/allTypes.kt | 74 +++ .../implementParcelable/allTypes.kt.expected | 136 +++++ .../implementParcelable/alreadyImplemented.kt | 25 + .../constructorProperties.kt | 5 + .../constructorProperties.kt.expected | 36 ++ .../constructorPropertiesWithParameters.kt | 3 + ...ructorPropertiesWithParameters.kt.expected | 31 ++ .../implementParcelable/missingConstructor.kt | 23 + .../missingConstructor.kt.expected | 26 + .../missingDescribeContents.kt | 20 + .../missingDescribeContents.kt.expected | 24 + .../missingWriteToParcel.kt | 19 + .../missingWriteToParcel.kt.expected | 23 + .../intention/implementParcelable/simple.kt | 3 + .../implementParcelable/simple.kt.expected | 27 + .../implementParcelable/withTransient.kt | 7 + .../withTransient.kt.expected | 32 ++ .../redoParcelable/indirectParcelable.kt | 44 ++ .../indirectParcelable.kt.expected | 48 ++ .../android/intention/redoParcelable/init.kt | 35 ++ .../intention/redoParcelable/init.kt.expected | 36 ++ .../redoParcelable/initWithExtraStatements.kt | 40 ++ .../initWithExtraStatements.kt.expected | 44 ++ .../intention/redoParcelable/noParcelable.kt | 6 + .../intention/redoParcelable/oldField.kt | 37 ++ .../redoParcelable/oldField.kt.expected | 30 ++ .../redoParcelable/oldFieldSecondary.kt | 28 ++ .../oldFieldSecondary.kt.expected | 32 ++ .../intention/redoParcelable/simple.kt | 4 + .../redoParcelable/simple.kt.expected | 26 + .../removeParcelable/inderectParcelable.kt | 44 ++ .../inderectParcelable.kt.expected | 28 ++ .../intention/removeParcelable/init.kt | 35 ++ .../removeParcelable/init.kt.expected | 12 + .../initWithExtraStatements.kt | 40 ++ .../initWithExtraStatements.kt.expected | 20 + .../removeParcelable/noParcelable.kt | 6 + .../intention/removeParcelable/simple.kt | 4 + .../removeParcelable/simple.kt.expected | 4 + .../lintQuickfix/parcelable/missingCreator.kt | 14 + .../parcelable/missingCreator.kt.expected | 27 + .../parcelable/noImplementation.kt | 5 + .../parcelable/noImplementation.kt.expected | 27 + plugins/lint/lint-idea/lint-idea.iml | 1 + .../AndroidLintInspectionToolProvider.java | 6 +- .../inspections/klint/ParcelableQuickFix.kt | 38 ++ 71 files changed, 2583 insertions(+), 2 deletions(-) rename idea/idea-android/src/org/jetbrains/kotlin/android/{androidUtil.kt => AndroidUtil.kt} (100%) create mode 100644 idea/idea-android/src/org/jetbrains/kotlin/android/KtPsiUtil.kt create mode 100644 idea/idea-android/src/org/jetbrains/kotlin/android/ParcelableUtil.kt create mode 100644 idea/idea-android/src/org/jetbrains/kotlin/android/TypeUtil.kt create mode 100644 idea/idea-android/src/org/jetbrains/kotlin/android/intention/ImplementParcelableAction.kt create mode 100644 idea/idea-android/src/org/jetbrains/kotlin/android/intention/RedoParcelableAction.kt create mode 100644 idea/idea-android/src/org/jetbrains/kotlin/android/intention/RemoveParcelableAction.kt create mode 100644 idea/idea-android/tests/org/jetbrains/kotlin/android/intention/AbstractAndroidIntentionTest.kt create mode 100644 idea/idea-android/tests/org/jetbrains/kotlin/android/intention/AndroidIntentionTestGenerated.java create mode 100644 idea/resources/intentionDescriptions/ImplementParcelableAction/after.kt.template create mode 100644 idea/resources/intentionDescriptions/ImplementParcelableAction/before.kt.template create mode 100644 idea/resources/intentionDescriptions/ImplementParcelableAction/description.html create mode 100644 idea/resources/intentionDescriptions/RedoParcelableAction/after.kt.template create mode 100644 idea/resources/intentionDescriptions/RedoParcelableAction/before.kt.template create mode 100644 idea/resources/intentionDescriptions/RedoParcelableAction/description.html create mode 100644 idea/resources/intentionDescriptions/RemoveParcelableAction/after.kt.template create mode 100644 idea/resources/intentionDescriptions/RemoveParcelableAction/before.kt.template create mode 100644 idea/resources/intentionDescriptions/RemoveParcelableAction/description.html create mode 100644 idea/testData/android/intention/implementParcelable/allNullableTypes.kt create mode 100644 idea/testData/android/intention/implementParcelable/allNullableTypes.kt.expected create mode 100644 idea/testData/android/intention/implementParcelable/allTypes.kt create mode 100644 idea/testData/android/intention/implementParcelable/allTypes.kt.expected create mode 100644 idea/testData/android/intention/implementParcelable/alreadyImplemented.kt create mode 100644 idea/testData/android/intention/implementParcelable/constructorProperties.kt create mode 100644 idea/testData/android/intention/implementParcelable/constructorProperties.kt.expected create mode 100644 idea/testData/android/intention/implementParcelable/constructorPropertiesWithParameters.kt create mode 100644 idea/testData/android/intention/implementParcelable/constructorPropertiesWithParameters.kt.expected create mode 100644 idea/testData/android/intention/implementParcelable/missingConstructor.kt create mode 100644 idea/testData/android/intention/implementParcelable/missingConstructor.kt.expected create mode 100644 idea/testData/android/intention/implementParcelable/missingDescribeContents.kt create mode 100644 idea/testData/android/intention/implementParcelable/missingDescribeContents.kt.expected create mode 100644 idea/testData/android/intention/implementParcelable/missingWriteToParcel.kt create mode 100644 idea/testData/android/intention/implementParcelable/missingWriteToParcel.kt.expected create mode 100644 idea/testData/android/intention/implementParcelable/simple.kt create mode 100644 idea/testData/android/intention/implementParcelable/simple.kt.expected create mode 100644 idea/testData/android/intention/implementParcelable/withTransient.kt create mode 100644 idea/testData/android/intention/implementParcelable/withTransient.kt.expected create mode 100644 idea/testData/android/intention/redoParcelable/indirectParcelable.kt create mode 100644 idea/testData/android/intention/redoParcelable/indirectParcelable.kt.expected create mode 100644 idea/testData/android/intention/redoParcelable/init.kt create mode 100644 idea/testData/android/intention/redoParcelable/init.kt.expected create mode 100644 idea/testData/android/intention/redoParcelable/initWithExtraStatements.kt create mode 100644 idea/testData/android/intention/redoParcelable/initWithExtraStatements.kt.expected create mode 100644 idea/testData/android/intention/redoParcelable/noParcelable.kt create mode 100644 idea/testData/android/intention/redoParcelable/oldField.kt create mode 100644 idea/testData/android/intention/redoParcelable/oldField.kt.expected create mode 100644 idea/testData/android/intention/redoParcelable/oldFieldSecondary.kt create mode 100644 idea/testData/android/intention/redoParcelable/oldFieldSecondary.kt.expected create mode 100644 idea/testData/android/intention/redoParcelable/simple.kt create mode 100644 idea/testData/android/intention/redoParcelable/simple.kt.expected create mode 100644 idea/testData/android/intention/removeParcelable/inderectParcelable.kt create mode 100644 idea/testData/android/intention/removeParcelable/inderectParcelable.kt.expected create mode 100644 idea/testData/android/intention/removeParcelable/init.kt create mode 100644 idea/testData/android/intention/removeParcelable/init.kt.expected create mode 100644 idea/testData/android/intention/removeParcelable/initWithExtraStatements.kt create mode 100644 idea/testData/android/intention/removeParcelable/initWithExtraStatements.kt.expected create mode 100644 idea/testData/android/intention/removeParcelable/noParcelable.kt create mode 100644 idea/testData/android/intention/removeParcelable/simple.kt create mode 100644 idea/testData/android/intention/removeParcelable/simple.kt.expected create mode 100644 idea/testData/android/lintQuickfix/parcelable/missingCreator.kt create mode 100644 idea/testData/android/lintQuickfix/parcelable/missingCreator.kt.expected create mode 100644 idea/testData/android/lintQuickfix/parcelable/noImplementation.kt create mode 100644 idea/testData/android/lintQuickfix/parcelable/noImplementation.kt.expected create mode 100644 plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/ParcelableQuickFix.kt diff --git a/core/descriptors/src/org/jetbrains/kotlin/builtins/KotlinBuiltIns.java b/core/descriptors/src/org/jetbrains/kotlin/builtins/KotlinBuiltIns.java index 5ce96259240..f00a7013cc7 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/builtins/KotlinBuiltIns.java +++ b/core/descriptors/src/org/jetbrains/kotlin/builtins/KotlinBuiltIns.java @@ -1040,6 +1040,10 @@ public abstract class KotlinBuiltIns { return descriptor.getContainingDeclaration() == getAny(); } + public static boolean isCharSequence(@Nullable KotlinType type) { + return type != null && isNotNullConstructedFromGivenClass(type, FQ_NAMES.charSequence); + } + public static boolean isString(@Nullable KotlinType type) { return type != null && isNotNullConstructedFromGivenClass(type, FQ_NAMES.string); } diff --git a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index 1a8de7f9d49..e7f749d1e5e 100755 --- a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -24,6 +24,7 @@ import org.jetbrains.kotlin.allopen.AbstractBytecodeListingTestForAllOpen import org.jetbrains.kotlin.android.* import org.jetbrains.kotlin.android.configure.AbstractConfigureProjectTest import org.jetbrains.kotlin.android.folding.AbstractAndroidResourceFoldingTest +import org.jetbrains.kotlin.android.intention.AbstractAndroidIntentionTest import org.jetbrains.kotlin.android.intention.AbstractAndroidResourceIntentionTest import org.jetbrains.kotlin.android.lint.AbstractKotlinLintTest import org.jetbrains.kotlin.android.quickfix.AbstractAndroidLintQuickfixTest @@ -1232,6 +1233,10 @@ fun main(args: Array) { model("configuration/android-gradle", pattern = """(\w+)_before\.gradle$""", testMethod = "doTestAndroidGradle") } + testClass { + model("android/intention", pattern = "^([\\w\\-_]+)\\.kt$") + } + testClass { model("android/resourceIntention", extension = "test", singleClass = true) } diff --git a/idea/idea-android/src/org/jetbrains/kotlin/android/androidUtil.kt b/idea/idea-android/src/org/jetbrains/kotlin/android/AndroidUtil.kt similarity index 100% rename from idea/idea-android/src/org/jetbrains/kotlin/android/androidUtil.kt rename to idea/idea-android/src/org/jetbrains/kotlin/android/AndroidUtil.kt diff --git a/idea/idea-android/src/org/jetbrains/kotlin/android/KtPsiUtil.kt b/idea/idea-android/src/org/jetbrains/kotlin/android/KtPsiUtil.kt new file mode 100644 index 00000000000..82a63d88cb8 --- /dev/null +++ b/idea/idea-android/src/org/jetbrains/kotlin/android/KtPsiUtil.kt @@ -0,0 +1,22 @@ +/* + * 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 + +import org.jetbrains.kotlin.idea.editor.fixers.range +import org.jetbrains.kotlin.psi.KtClass + +internal fun KtClass.insideBody(offset: Int): Boolean = getBody()?.range?.contains(offset) ?: false diff --git a/idea/idea-android/src/org/jetbrains/kotlin/android/ParcelableUtil.kt b/idea/idea-android/src/org/jetbrains/kotlin/android/ParcelableUtil.kt new file mode 100644 index 00000000000..b57327dca8b --- /dev/null +++ b/idea/idea-android/src/org/jetbrains/kotlin/android/ParcelableUtil.kt @@ -0,0 +1,470 @@ +/* + * 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 + +import com.android.SdkConstants.CLASS_PARCEL +import com.android.SdkConstants.CLASS_PARCELABLE +import com.intellij.openapi.fileEditor.FileDocumentManager +import com.intellij.psi.PsiDocumentManager +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.builtins.PrimitiveType +import org.jetbrains.kotlin.descriptors.ParameterDescriptor +import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet +import org.jetbrains.kotlin.idea.codeInsight.shorten.performDelayedRefactoringRequests +import org.jetbrains.kotlin.idea.intentions.getLeftMostReceiverExpression +import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor +import org.jetbrains.kotlin.idea.search.usagesSearch.propertyDescriptor +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.types.KotlinType + + +private val CREATOR_NAME = "CREATOR" +private val PARCEL_NAME = "parcel" +private val CREATOR_TEXT = + "companion object $CREATOR_NAME : android.os.Parcelable.Creator<%1\$s> {\n" + + " override fun createFromParcel($PARCEL_NAME: $CLASS_PARCEL): %1\$s {\n" + + " return %1\$s($PARCEL_NAME)\n" + + " }\n\n" + + " override fun newArray(size: Int): Array<%1\$s?> {\n" + + " return arrayOfNulls(size)\n" + + " }\n" + + "}" +private val WRITE_TO_PARCEL_TEXT = "override fun writeToParcel($PARCEL_NAME: $CLASS_PARCEL, flags: Int) {\n}" +private val WRITE_TO_PARCEL_SUPER_CALL_TEXT = "super.writeToParcel($PARCEL_NAME, flags)" +private val WRITE_TO_PARCEL_WITH_SUPER_TEXT = + "override fun writeToParcel($PARCEL_NAME: $CLASS_PARCEL, flags: Int) {\n$WRITE_TO_PARCEL_SUPER_CALL_TEXT\n}" +private val DESCRIBE_CONTENTS_TEXT = "override fun describeContents(): Int {\nreturn 0\n}" +private val CONSTRUCTOR_TEXT = "constructor($PARCEL_NAME: $CLASS_PARCEL)" + + +fun KtClass.canAddParcelable(): Boolean = + findParcelableSupertype() == null + || findCreator() == null + || findConstructorFromParcel() == null + || findWriteToParcel() == null + || findDescribeContents() == null + +fun KtClass.canRedoParcelable(): Boolean = canRemoveParcelable() + +fun KtClass.canRemoveParcelable(): Boolean = + findParcelableSupertype()?.takeIf { it.typeReference?.isParcelableReference() ?: false } + ?: findCreator() + ?: findConstructorFromParcel() + ?: findWriteToParcel() + ?: findDescribeContents() != null + +fun KtClass.implementParcelable() { + val factory = KtPsiFactory(this) + val superExtendsParcelable = superExtendsParcelable() + + findOrCreateParcelableSupertype(factory) + val constructor = findOrCreateConstructor(factory) + + addFieldReads(constructor, factory) + + val writeToParcel = findOrCreateWriteToParcel(factory, superExtendsParcelable) + addFieldWrites(writeToParcel, factory, superExtendsParcelable) + + findOrCreateDescribeContents(factory) + findOrCreateCreator(factory) + + performDelayedRefactoringRequests(project) + save() +} + +fun KtClass.removeParcelableImplementation() { + findParcelableSupertype()?.takeIf { it.typeReference?.isParcelableReference() ?: false }?.let { + removeSuperTypeListEntry(it) + } + + findConstructorFromParcel()?.let { + if (it is KtPrimaryConstructor) { + tryDeleteInitBlock() + } + it.delete() + } + + findWriteToParcel()?.delete() + findDescribeContents()?.delete() + findCreator()?.delete() + save() +} + +fun KtClass.reimplementParcelable() { + removeParcelableImplementation() + implementParcelable() +} + +private fun KtClass.findCreator(): KtObjectDeclaration? = companionObjects.find { it.name == CREATOR_NAME } + +private fun KtClass.findParcelableSupertype(): KtSuperTypeListEntry? = getSuperTypeList()?.findParcelable() + +private fun KtSuperTypeList.findParcelable() = entries?.find { it.typeReference?.isParcelableSuccessorReference() ?: false } + +private fun KtTypeReference.isParcelableSuccessorReference() = + analyze(BodyResolveMode.PARTIAL)[BindingContext.TYPE, this]?.isSubclassOfParcelable() ?: false + +private fun KtClass.superExtendsParcelable() = superTypeListEntries.find { it.typeReference?.extendsParcelable() ?: false } != null + +private fun KtClass.tryDeleteInitBlock() { + getAnonymousInitializers().forEach { initializer -> + val block = initializer.body as? KtBlockExpression + if (block != null) { + block.statements.forEach { statement -> + if (statement.isReadFromParcelPropertyAssignment()) { + statement.delete() + } + } + + if (block.statements.isEmpty()) { + initializer.delete() + } + } + } +} + +private fun KtExpression.isReadFromParcelPropertyAssignment(): Boolean { + if (this !is KtBinaryExpression || this.operationToken != KtTokens.EQ) { + return false + } + + return this.right?.isReadFromParcel() ?: false +} + +private fun KtExpression.isReadFromParcel(): Boolean { + val reference = firstChild as? KtReferenceExpression + ?: (firstChild as? KtDotQualifiedExpression)?.getLeftMostReceiverExpression() as? KtReferenceExpression ?: return false + val target = reference.analyze(BodyResolveMode.PARTIAL)[BindingContext.REFERENCE_TARGET, reference] ?: return false + return (target as? ParameterDescriptor)?.type?.fqNameEquals(CLASS_PARCEL) ?: false +} + +private fun KtClass.addFieldWrites(function: KtFunction, factory: KtPsiFactory, callSuper: Boolean) { + val bodyExpression = function.bodyExpression + if (bodyExpression !is KtBlockExpression || !bodyExpression.isEmptyWriteToParcel(callSuper)) { + return + } + + val propertyParameterDescriptors = primaryConstructor?.valueParameters?.mapNotNull { it.propertyDescriptor } ?: emptyList() + + val propertyDescriptors = declarations + .filter { it.isParcelableProperty() } + .mapNotNull { it.descriptor as? PropertyDescriptor } + + val parcelName = function.valueParameters[0].name ?: return + val flagsName = function.valueParameters[1].name ?: return + val blockText = + (propertyParameterDescriptors + propertyDescriptors) + .mapNotNull { it.formatWriteToParcel(parcelName, flagsName) } + .joinToString(separator = "\n") + + val block = factory.createBlock( + if (callSuper) + WRITE_TO_PARCEL_SUPER_CALL_TEXT + if (blockText.isNotBlank()) "\n$blockText" else "" + else blockText + ) + + bodyExpression.replace(block) +} + +private fun KtClass.addFieldReads(constructor: KtConstructor<*>, factory: KtPsiFactory) { + val bodyExpression = constructor.getBodyExpression() + if (bodyExpression != null && bodyExpression.statements.isNotEmpty()) { + return + } + + val parcelName = constructor.getValueParameters().firstOrNull()?.name ?: return + val parcelableProperties = declarations + .filter { it.isParcelableProperty() } + .mapNotNull { it.descriptor as? PropertyDescriptor } + + if (parcelableProperties.isEmpty()) { + return + } + + val blockText = parcelableProperties + .mapNotNull { descriptor -> descriptor.formatReadFromParcel(parcelName)?.let { "${descriptor.name} = $it" } } + .joinToString(separator = "\n") + + val block = factory.createBlock(blockText) + + if (constructor is KtPrimaryConstructor) { + val initializer = factory.createAnonymousInitializer() + initializer.body?.replace(block) + addDeclaration(initializer).apply { + addNewLineBeforeDeclaration() + addToShorteningWaitSet() + } + } + else { + bodyExpression?.replace(block) ?: constructor.add(block) + } +} + +private fun KtDeclaration.isParcelableProperty(): Boolean = + this is KtProperty && isVar && !hasDelegate() && !isTransient() && getter == null && setter == null + +private fun KtProperty.isTransient() = annotationEntries.find { it.isTransientAnnotation() } != null + +private fun KtAnnotationEntry.isTransientAnnotation(): Boolean = + typeReference?.analyze(BodyResolveMode.PARTIAL)?.get(BindingContext.TYPE, typeReference)?.fqNameEquals("kotlin.jvm.Transient") ?: false + +private fun KtExpression.isCallToSuperWriteToParcel() = + this is KtDotQualifiedExpression + && receiverExpression is KtSuperExpression + && (selectorExpression as? KtCallExpression)?.calleeExpression?.text == "writeToParcel" + +private fun KtBlockExpression.isEmptyWriteToParcel(callSuper: Boolean): Boolean = + if (callSuper) { + statements.isEmpty() || statements.size == 1 && statements.first().isCallToSuperWriteToParcel() + } + else { + statements.isEmpty() + } + +private fun PropertyDescriptor.formatReadFromParcel(parcelName: String): String? { + val type = returnType ?: return null + + fun KotlinType.formatJavaClassloader() = "${this.constructor.declarationDescriptor?.fqNameSafe}::class.java.classLoader" + + // There is no read/write short array methods + if (KotlinBuiltIns.isPrimitiveArray(type) && KotlinBuiltIns.getPrimitiveArrayElementType(type) != PrimitiveType.SHORT) { + return "$parcelName.create${type.getName()}()" + } + + + if (type.isMarkedNullable && KotlinBuiltIns.isPrimitiveTypeOrNullablePrimitiveType(type)) { + return "$parcelName.readValue(${type.formatJavaClassloader()}) as? ${type.getName()}" + } + + if (KotlinBuiltIns.isPrimitiveType(type)) { + return when { + KotlinBuiltIns.isBoolean(type) -> "$parcelName.readByte() != 0.toByte()" + KotlinBuiltIns.isShort(type) -> "$parcelName.readInt().toShort()" + KotlinBuiltIns.isChar(type) -> "$parcelName.readInt().toChar()" + else -> "$parcelName.read${type.getName()}()" + } + } + + return when { + KotlinBuiltIns.isCharSequenceOrNullableCharSequence(type) -> "$parcelName.readString()" + KotlinBuiltIns.isStringOrNullableString(type) -> "$parcelName.readString()" + type.isArrayOfParcelable() -> "$parcelName.createTypedArray(${type.arguments.single().type.getName()}.CREATOR)" + type.isListOfParcelable() -> "$parcelName.createTypedArrayList(${type.arguments.single().type.getName()}.CREATOR)" + type.isArrayOfIBinder() -> "$parcelName.createBinderArray()" + type.isListOfIBinder() -> "$parcelName.createBinderArrayList()" + type.isArrayOfString() -> "$parcelName.createStringArray()" + type.isListOfString() -> "$parcelName.createStringArrayList()" + type.isSparseBooleanArray() -> "$parcelName.readSparseBooleanArray()" + type.isBundle() -> "$parcelName.readBundle(${type.formatJavaClassloader()})" + type.isIBinder() -> "$parcelName.readStrongBinder()" + type.isSubclassOfParcelable(true) -> "$parcelName.readParcelable(${type.formatJavaClassloader()})" // This one should go last + else -> null + } +} + +private fun PropertyDescriptor.formatWriteToParcel(parcelName: String, flagsName: String): String? { + val type = returnType ?: return null + + // There is no read/write short array methods + if (KotlinBuiltIns.isPrimitiveArray(type) && KotlinBuiltIns.getPrimitiveArrayElementType(type) != PrimitiveType.SHORT) { + return "$parcelName.write${type.getName()}($name)" + } + + if (type.isMarkedNullable) { + if (KotlinBuiltIns.isPrimitiveTypeOrNullablePrimitiveType(type)) { + return "$parcelName.writeValue($name)" + } + else if (KotlinBuiltIns.isCharSequenceOrNullableCharSequence(type)) { + return "$parcelName.writeString($name?.toString())" + } + } + + if (KotlinBuiltIns.isPrimitiveType(type)) { + return when { + KotlinBuiltIns.isBoolean(type) -> "$parcelName.writeByte(if ($name) 1 else 0)" + KotlinBuiltIns.isShort(type) -> "$parcelName.writeInt($name.toInt())" + KotlinBuiltIns.isChar(type) -> "$parcelName.writeInt($name.toInt())" + else -> "$parcelName.write${type.getName()}($name)" + } + } + + return when { + KotlinBuiltIns.isCharSequence(type) -> "$parcelName.writeString($name.toString())" + KotlinBuiltIns.isStringOrNullableString(type) -> "$parcelName.writeString($name)" + type.isArrayOfParcelable() -> "$parcelName.writeTypedArray($name, $flagsName)" + type.isListOfParcelable() -> "$parcelName.writeTypedList($name)" + type.isArrayOfIBinder() -> "$parcelName.writeBinderArray($name)" + type.isListOfIBinder() -> "$parcelName.writeBinderList($name)" + type.isArrayOfString() -> "$parcelName.writeStringArray($name)" + type.isListOfString() -> "$parcelName.writeStringList($name)" + type.isSparseBooleanArray() -> "$parcelName.writeSparseBooleanArray($name)" + type.isBundle() -> "$parcelName.writeBundle($name)" + type.isIBinder() -> "$parcelName.writeStrongBinder($name)" + type.isSubclassOfParcelable(true) -> "$parcelName.writeParcelable($name, $flagsName)" // This one should go last + else -> null + } +} + +private fun KtClass.findOrCreateCreator(factory: KtPsiFactory): KtClassOrObject { + findCreator()?.let { + return it + } + + val creator = factory.createObject(CREATOR_TEXT.format(name)) + return addDeclaration(creator).apply { addToShorteningWaitSet() } +} + +private fun KtClass.findOrCreateParcelableSupertype(factory: KtPsiFactory): KtSuperTypeListEntry? { + findParcelableSupertype()?.let { + return it + } + + val supertypeEntry = factory.createSuperTypeEntry(CLASS_PARCELABLE) + return addSuperTypeListEntry(supertypeEntry).apply { addToShorteningWaitSet() } +} + +private fun KtClass.save() = FileDocumentManager.getInstance().getDocument(containingFile.virtualFile)?.let { + PsiDocumentManager.getInstance(project).commitDocument(it) +} + +private fun KtClass.findOrCreateConstructor(factory: KtPsiFactory): KtConstructor<*> { + findConstructorFromParcel()?.let { + return it + } + + createPrimaryConstructorIfAbsent() + return createSecondaryConstructor(factory).apply { + addToShorteningWaitSet() + } +} + +private fun KtClass.createSecondaryConstructor(factory: KtPsiFactory): KtConstructor<*> { + val constructorText = primaryConstructor?.let { constructor -> + val arguments = constructor.valueParameters.map { + it?.propertyDescriptor?.formatReadFromParcel(PARCEL_NAME) ?: "TODO(\"${it?.name}\")" + } + + val argumentList = arguments.joinToString( + prefix = if (arguments.size > 1) "(\n" else "(", + postfix = ")", + separator = if (arguments.size > 1) ",\n" else ", ") + + "$CONSTRUCTOR_TEXT :this$argumentList {\n}" + } ?: "$CONSTRUCTOR_TEXT {\n}" + + val constructor = factory.createSecondaryConstructor(constructorText) + val lastProperty = declarations.findLast { it is KtProperty } + if (lastProperty != null) { + return addDeclarationAfter(constructor, lastProperty).apply { addNewLineBeforeDeclaration() } + } + else { + val firstFunction = declarations.find { it is KtFunction } + return addDeclarationBefore(constructor, firstFunction).apply { addNewLineBeforeDeclaration() } + } +} + +private fun KtTypeReference.extendsParcelable(): Boolean = + analyze(BodyResolveMode.PARTIAL)[BindingContext.TYPE, this]?.isSubclassOfParcelable(true) ?: false + +private fun KtClass.findWriteToParcel() = declarations.find { it.isWriteToParcel() } + +private fun KtDeclaration.isWriteToParcel(): Boolean = this is KtFunction && name == "writeToParcel" && valueParameters.let { + it.size == 2 && it[0].isParcelParameter() && (it[1].typeReference?.fqNameEquals("kotlin.Int") ?: false) +} + +private fun KtClass.findOrCreateWriteToParcel(factory: KtPsiFactory, callSuper: Boolean): KtFunction { + findWriteToParcel()?.let { + return it as KtFunction + } + + val writeToParcel = factory.createFunction(if (callSuper) WRITE_TO_PARCEL_WITH_SUPER_TEXT else WRITE_TO_PARCEL_TEXT) + return addDeclaration(writeToParcel).apply { + addNewLineBeforeDeclaration() + addToShorteningWaitSet() + } +} + +private fun KtClass.findDescribeContents() = declarations.find { it.isDescribeContents() } + +private fun KtDeclaration.isDescribeContents(): Boolean = this is KtFunction && + name == "describeContents" && + valueParameters.isEmpty() + +private fun KtClass.findOrCreateDescribeContents(factory: KtPsiFactory): KtFunction { + findDescribeContents()?.let { + return it as KtFunction + } + + val describeContents = factory.createFunction(DESCRIBE_CONTENTS_TEXT) + return addDeclaration(describeContents).apply { + addNewLineBeforeDeclaration() + addToShorteningWaitSet() + } +} + +private fun KtClass.findConstructorFromParcel(): KtConstructor<*>? = + primaryConstructor?.takeIf { it.isConstructorFromParcel() } ?: secondaryConstructors.find { it.isConstructorFromParcel() } + +private fun KtConstructor<*>.isConstructorFromParcel(): Boolean = getValueParameters().let { + it.size == 1 && it.single().isParcelParameter() +} + +private fun KtParameter.isParcelParameter(): Boolean = typeReference?.fqNameEquals(CLASS_PARCEL) ?: false + +private fun KtTypeReference.isParcelableReference() = fqNameEquals(CLASS_PARCELABLE) + +private fun KtTypeReference.fqNameEquals(fqName: String) = + analyze(BodyResolveMode.PARTIAL)[BindingContext.TYPE, this]?.fqNameEquals(fqName) ?: false + +private fun KotlinType.getName() = constructor.declarationDescriptor?.name + +private fun KotlinType.fqNameEquals(fqName: String) = constructor.declarationDescriptor?.fqNameSafe?.asString() == fqName + +private fun KotlinType.isSubclassOfParcelable(strict: Boolean = false): Boolean = isSubclassOf(FqName(CLASS_PARCELABLE), strict) + +private fun KotlinType.isIBinder(): Boolean = fqNameEquals("android.os.IBinder") + +private fun KotlinType.isArrayOfParcelable(): Boolean = + KotlinBuiltIns.isArray(this) && arguments.singleOrNull()?.type?.isSubclassOfParcelable(true) ?: false + +private fun KotlinType.isArrayOfIBinder(): Boolean = + KotlinBuiltIns.isArray(this) && arguments.singleOrNull()?.type?.isIBinder() ?: false + +private fun KotlinType.isArrayOfString(): Boolean = + KotlinBuiltIns.isArray(this) && KotlinBuiltIns.isStringOrNullableString(arguments.singleOrNull()?.type) + +private fun KotlinType.isListOfString(): Boolean = + KotlinBuiltIns.isListOrNullableList(this) && KotlinBuiltIns.isStringOrNullableString(arguments.singleOrNull()?.type) + +private fun KotlinType.isListOfParcelable(): Boolean = + KotlinBuiltIns.isListOrNullableList(this) && arguments.singleOrNull()?.type?.isSubclassOfParcelable(true) ?: false + +private fun KotlinType.isListOfIBinder(): Boolean = + KotlinBuiltIns.isListOrNullableList(this) && arguments.singleOrNull()?.type?.isIBinder() ?: false + +private fun KotlinType.isSparseBooleanArray(): Boolean = fqNameEquals("android.util.SparseBooleanArray") + +private fun KotlinType.isBundle(): Boolean = fqNameEquals("android.os.Bundle") + +private fun T.addNewLineBeforeDeclaration() = parent.addBefore(KtPsiFactory(this).createNewLine(), this) \ No newline at end of file diff --git a/idea/idea-android/src/org/jetbrains/kotlin/android/TypeUtil.kt b/idea/idea-android/src/org/jetbrains/kotlin/android/TypeUtil.kt new file mode 100644 index 00000000000..be5b58765d4 --- /dev/null +++ b/idea/idea-android/src/org/jetbrains/kotlin/android/TypeUtil.kt @@ -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 + +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe +import org.jetbrains.kotlin.types.KotlinType + +/* + * 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. + */ + + +internal fun KotlinType.fqNameSafe() = constructor.declarationDescriptor?.fqNameSafe + +internal fun KotlinType.isSubclassOf(className: FqName, strict: Boolean = false): Boolean = + (!strict && fqNameSafe() == className) || constructor.supertypes.any { + className == it.fqNameSafe() || it.isSubclassOf(className, true) + } diff --git a/idea/idea-android/src/org/jetbrains/kotlin/android/intention/ImplementParcelableAction.kt b/idea/idea-android/src/org/jetbrains/kotlin/android/intention/ImplementParcelableAction.kt new file mode 100644 index 00000000000..375adfc40c4 --- /dev/null +++ b/idea/idea-android/src/org/jetbrains/kotlin/android/intention/ImplementParcelableAction.kt @@ -0,0 +1,43 @@ +/* + * 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.intention + +import com.intellij.codeInsight.intention.HighPriorityAction +import com.intellij.openapi.editor.Editor +import org.jetbrains.android.facet.AndroidFacet +import org.jetbrains.android.util.AndroidBundle +import org.jetbrains.kotlin.android.canAddParcelable +import org.jetbrains.kotlin.android.implementParcelable +import org.jetbrains.kotlin.android.insideBody +import org.jetbrains.kotlin.idea.intentions.SelfTargetingIntention +import org.jetbrains.kotlin.psi.KtClass + + +class ImplementParcelableAction : + SelfTargetingIntention(KtClass::class.java, AndroidBundle.message("implement.parcelable.intention.text")), + HighPriorityAction { + override fun isApplicableTo(element: KtClass, caretOffset: Int): Boolean = + AndroidFacet.getInstance(element) != null && + !element.insideBody(caretOffset) && + element.canAddParcelable() + + override fun applyTo(element: KtClass, editor: Editor?) { + element.implementParcelable() + } + + override fun startInWriteAction(): Boolean = true +} \ No newline at end of file diff --git a/idea/idea-android/src/org/jetbrains/kotlin/android/intention/RedoParcelableAction.kt b/idea/idea-android/src/org/jetbrains/kotlin/android/intention/RedoParcelableAction.kt new file mode 100644 index 00000000000..22365e859e9 --- /dev/null +++ b/idea/idea-android/src/org/jetbrains/kotlin/android/intention/RedoParcelableAction.kt @@ -0,0 +1,43 @@ +/* + * 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.intention + +import com.intellij.codeInsight.intention.HighPriorityAction +import com.intellij.openapi.editor.Editor +import org.jetbrains.android.facet.AndroidFacet +import org.jetbrains.android.util.AndroidBundle +import org.jetbrains.kotlin.android.canRedoParcelable +import org.jetbrains.kotlin.android.insideBody +import org.jetbrains.kotlin.android.reimplementParcelable +import org.jetbrains.kotlin.idea.intentions.SelfTargetingIntention +import org.jetbrains.kotlin.psi.KtClass + + +class RedoParcelableAction : + SelfTargetingIntention(KtClass::class.java, AndroidBundle.message("redo.parcelable.intention.text")), + HighPriorityAction { + override fun isApplicableTo(element: KtClass, caretOffset: Int): Boolean = + AndroidFacet.getInstance(element) != null && + !element.insideBody(caretOffset) && + element.canRedoParcelable() + + override fun applyTo(element: KtClass, editor: Editor?) { + element.reimplementParcelable() + } + + override fun startInWriteAction(): Boolean = true +} \ No newline at end of file diff --git a/idea/idea-android/src/org/jetbrains/kotlin/android/intention/RemoveParcelableAction.kt b/idea/idea-android/src/org/jetbrains/kotlin/android/intention/RemoveParcelableAction.kt new file mode 100644 index 00000000000..59c6d119cd7 --- /dev/null +++ b/idea/idea-android/src/org/jetbrains/kotlin/android/intention/RemoveParcelableAction.kt @@ -0,0 +1,43 @@ +/* + * 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.intention + +import com.intellij.codeInsight.intention.HighPriorityAction +import com.intellij.openapi.editor.Editor +import org.jetbrains.android.facet.AndroidFacet +import org.jetbrains.android.util.AndroidBundle +import org.jetbrains.kotlin.android.canRemoveParcelable +import org.jetbrains.kotlin.android.insideBody +import org.jetbrains.kotlin.android.removeParcelableImplementation +import org.jetbrains.kotlin.idea.intentions.SelfTargetingIntention +import org.jetbrains.kotlin.psi.KtClass + + +class RemoveParcelableAction : + SelfTargetingIntention(KtClass::class.java, AndroidBundle.message("remove.parcelable.intention.text")), + HighPriorityAction { + override fun isApplicableTo(element: KtClass, caretOffset: Int): Boolean = + AndroidFacet.getInstance(element) != null && + !element.insideBody(caretOffset) && + element.canRemoveParcelable() + + override fun applyTo(element: KtClass, editor: Editor?) { + element.removeParcelableImplementation() + } + + override fun startInWriteAction(): Boolean = true +} \ No newline at end of file diff --git a/idea/idea-android/tests/org/jetbrains/kotlin/android/intention/AbstractAndroidIntentionTest.kt b/idea/idea-android/tests/org/jetbrains/kotlin/android/intention/AbstractAndroidIntentionTest.kt new file mode 100644 index 00000000000..382b4003960 --- /dev/null +++ b/idea/idea-android/tests/org/jetbrains/kotlin/android/intention/AbstractAndroidIntentionTest.kt @@ -0,0 +1,73 @@ +/* + * 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.intention + +import com.android.SdkConstants +import com.intellij.codeInsight.intention.IntentionAction +import com.intellij.openapi.util.io.FileUtil +import com.intellij.util.PathUtil +import org.jetbrains.kotlin.android.KotlinAndroidTestCase +import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil +import org.jetbrains.kotlin.idea.test.PluginTestCaseBase +import org.jetbrains.kotlin.test.InTextDirectivesUtils +import org.jetbrains.kotlin.test.KotlinTestUtils +import java.io.File + + +abstract class AbstractAndroidIntentionTest : KotlinAndroidTestCase() { + fun doTest(path: String) { + val testFile = File(path) + val testFileText = FileUtil.loadFile(testFile) + val intentionClassName = InTextDirectivesUtils.findStringWithPrefixes(testFileText, "// INTENTION_CLASS: ") + ?: error("Intention class not found!") + + val notAvailable = InTextDirectivesUtils.isDirectiveDefined(testFileText, "// NOT_AVAILABLE") + val withRuntime = InTextDirectivesUtils.isDirectiveDefined(testFileText, "// WITH_RUNTIME") + + try { + if (withRuntime) { + ConfigLibraryUtil.configureKotlinRuntime(myFixture.module) + } + + val sourceFile = myFixture.copyFileToProject(path, "src/${PathUtil.getFileName(path)}") + myFixture.configureFromExistingVirtualFile(sourceFile) + + val intention = Class.forName(intentionClassName).newInstance() as? IntentionAction ?: error("Failed to create intention!") + if (!intention.isAvailable(myFixture.project, myFixture.editor, myFixture.file)) { + if (notAvailable) { + return + } + + error("Intention is not available!") + } + + myFixture.launchAction(intention) + myFixture.checkResultByFile("$path.expected") + } + finally { + if (withRuntime) { + ConfigLibraryUtil.unConfigureKotlinRuntime(myFixture.module) + } + } + } + + override fun createManifest() { + myFixture.copyFileToProject("idea/testData/android/AndroidManifest.xml", SdkConstants.FN_ANDROID_MANIFEST_XML) + } + + override fun getTestDataPath() = KotlinTestUtils.getHomeDirectory() +} diff --git a/idea/idea-android/tests/org/jetbrains/kotlin/android/intention/AndroidIntentionTestGenerated.java b/idea/idea-android/tests/org/jetbrains/kotlin/android/intention/AndroidIntentionTestGenerated.java new file mode 100644 index 00000000000..408662396fd --- /dev/null +++ b/idea/idea-android/tests/org/jetbrains/kotlin/android/intention/AndroidIntentionTestGenerated.java @@ -0,0 +1,197 @@ +/* + * 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.intention; + +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("idea/testData/android/intention") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class AndroidIntentionTestGenerated extends AbstractAndroidIntentionTest { + public void testAllFilesPresentInIntention() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/android/intention"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("idea/testData/android/intention/implementParcelable") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ImplementParcelable extends AbstractAndroidIntentionTest { + public void testAllFilesPresentInImplementParcelable() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/android/intention/implementParcelable"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("allNullableTypes.kt") + public void testAllNullableTypes() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/implementParcelable/allNullableTypes.kt"); + doTest(fileName); + } + + @TestMetadata("allTypes.kt") + public void testAllTypes() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/implementParcelable/allTypes.kt"); + doTest(fileName); + } + + @TestMetadata("alreadyImplemented.kt") + public void testAlreadyImplemented() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/implementParcelable/alreadyImplemented.kt"); + doTest(fileName); + } + + @TestMetadata("constructorProperties.kt") + public void testConstructorProperties() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/implementParcelable/constructorProperties.kt"); + doTest(fileName); + } + + @TestMetadata("constructorPropertiesWithParameters.kt") + public void testConstructorPropertiesWithParameters() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/implementParcelable/constructorPropertiesWithParameters.kt"); + doTest(fileName); + } + + @TestMetadata("missingConstructor.kt") + public void testMissingConstructor() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/implementParcelable/missingConstructor.kt"); + doTest(fileName); + } + + @TestMetadata("missingDescribeContents.kt") + public void testMissingDescribeContents() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/implementParcelable/missingDescribeContents.kt"); + doTest(fileName); + } + + @TestMetadata("missingWriteToParcel.kt") + public void testMissingWriteToParcel() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/implementParcelable/missingWriteToParcel.kt"); + doTest(fileName); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/implementParcelable/simple.kt"); + doTest(fileName); + } + + @TestMetadata("withTransient.kt") + public void testWithTransient() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/implementParcelable/withTransient.kt"); + doTest(fileName); + } + } + + @TestMetadata("idea/testData/android/intention/redoParcelable") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class RedoParcelable extends AbstractAndroidIntentionTest { + public void testAllFilesPresentInRedoParcelable() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/android/intention/redoParcelable"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("indirectParcelable.kt") + public void testIndirectParcelable() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/redoParcelable/indirectParcelable.kt"); + doTest(fileName); + } + + @TestMetadata("init.kt") + public void testInit() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/redoParcelable/init.kt"); + doTest(fileName); + } + + @TestMetadata("initWithExtraStatements.kt") + public void testInitWithExtraStatements() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/redoParcelable/initWithExtraStatements.kt"); + doTest(fileName); + } + + @TestMetadata("noParcelable.kt") + public void testNoParcelable() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/redoParcelable/noParcelable.kt"); + doTest(fileName); + } + + @TestMetadata("oldField.kt") + public void testOldField() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/redoParcelable/oldField.kt"); + doTest(fileName); + } + + @TestMetadata("oldFieldSecondary.kt") + public void testOldFieldSecondary() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/redoParcelable/oldFieldSecondary.kt"); + doTest(fileName); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/redoParcelable/simple.kt"); + doTest(fileName); + } + } + + @TestMetadata("idea/testData/android/intention/removeParcelable") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class RemoveParcelable extends AbstractAndroidIntentionTest { + public void testAllFilesPresentInRemoveParcelable() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/android/intention/removeParcelable"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("inderectParcelable.kt") + public void testInderectParcelable() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/removeParcelable/inderectParcelable.kt"); + doTest(fileName); + } + + @TestMetadata("init.kt") + public void testInit() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/removeParcelable/init.kt"); + doTest(fileName); + } + + @TestMetadata("initWithExtraStatements.kt") + public void testInitWithExtraStatements() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/removeParcelable/initWithExtraStatements.kt"); + doTest(fileName); + } + + @TestMetadata("noParcelable.kt") + public void testNoParcelable() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/removeParcelable/noParcelable.kt"); + doTest(fileName); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/intention/removeParcelable/simple.kt"); + doTest(fileName); + } + } +} diff --git a/idea/idea-android/tests/org/jetbrains/kotlin/android/quickfix/AbstractAndroidLintQuickfixTest.kt b/idea/idea-android/tests/org/jetbrains/kotlin/android/quickfix/AbstractAndroidLintQuickfixTest.kt index d77c09079e6..a6c2f029f17 100644 --- a/idea/idea-android/tests/org/jetbrains/kotlin/android/quickfix/AbstractAndroidLintQuickfixTest.kt +++ b/idea/idea-android/tests/org/jetbrains/kotlin/android/quickfix/AbstractAndroidLintQuickfixTest.kt @@ -58,7 +58,7 @@ abstract class AbstractAndroidLintQuickfixTest : KotlinAndroidTestCase() { myFixture.checkResultByFile(path + ".expected") } else { - assertNull("Intention should not be availabale", myFixture.availableIntentions.find { it.text == intentionText }) + assertNull("Intention should not be available", myFixture.availableIntentions.find { it.text == intentionText }) } } diff --git a/idea/idea-android/tests/org/jetbrains/kotlin/android/quickfix/AndroidLintQuickfixTestGenerated.java b/idea/idea-android/tests/org/jetbrains/kotlin/android/quickfix/AndroidLintQuickfixTestGenerated.java index 00a1db76a16..91dc9e6013c 100644 --- a/idea/idea-android/tests/org/jetbrains/kotlin/android/quickfix/AndroidLintQuickfixTestGenerated.java +++ b/idea/idea-android/tests/org/jetbrains/kotlin/android/quickfix/AndroidLintQuickfixTestGenerated.java @@ -36,6 +36,27 @@ public class AndroidLintQuickfixTestGenerated extends AbstractAndroidLintQuickfi KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/android/lintQuickfix"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); } + @TestMetadata("idea/testData/android/lintQuickfix/parcelable") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Parcelable extends AbstractAndroidLintQuickfixTest { + public void testAllFilesPresentInParcelable() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/android/lintQuickfix/parcelable"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("missingCreator.kt") + public void testMissingCreator() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/lintQuickfix/parcelable/missingCreator.kt"); + doTest(fileName); + } + + @TestMetadata("noImplementation.kt") + public void testNoImplementation() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/android/lintQuickfix/parcelable/noImplementation.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/android/lintQuickfix/requiresApi") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/idea/resources/intentionDescriptions/ImplementParcelableAction/after.kt.template b/idea/resources/intentionDescriptions/ImplementParcelableAction/after.kt.template new file mode 100644 index 00000000000..bf39c45f351 --- /dev/null +++ b/idea/resources/intentionDescriptions/ImplementParcelableAction/after.kt.template @@ -0,0 +1,27 @@ +class SomeData(val number: Int, val text: String, val flag: Boolean) : Parcelable { + constructor(parcel: Parcel) : this( + parcel.readInt(), + parcel.readString(), + parcel.readByte() != 0.toByte()) { + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeInt(number) + parcel.writeString(text) + parcel.writeByte(if (flag) 1 else 0) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): SomeData { + return SomeData(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/ImplementParcelableAction/before.kt.template b/idea/resources/intentionDescriptions/ImplementParcelableAction/before.kt.template new file mode 100644 index 00000000000..e56fb340dd0 --- /dev/null +++ b/idea/resources/intentionDescriptions/ImplementParcelableAction/before.kt.template @@ -0,0 +1 @@ +class SomeData(val number: Int, val text: String, val flag: Boolean) \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/ImplementParcelableAction/description.html b/idea/resources/intentionDescriptions/ImplementParcelableAction/description.html new file mode 100644 index 00000000000..ecc64ea1948 --- /dev/null +++ b/idea/resources/intentionDescriptions/ImplementParcelableAction/description.html @@ -0,0 +1,4 @@ + + +This intention implements the methods and the CREATOR object needed for Parcelable. + \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/RedoParcelableAction/after.kt.template b/idea/resources/intentionDescriptions/RedoParcelableAction/after.kt.template new file mode 100644 index 00000000000..bf39c45f351 --- /dev/null +++ b/idea/resources/intentionDescriptions/RedoParcelableAction/after.kt.template @@ -0,0 +1,27 @@ +class SomeData(val number: Int, val text: String, val flag: Boolean) : Parcelable { + constructor(parcel: Parcel) : this( + parcel.readInt(), + parcel.readString(), + parcel.readByte() != 0.toByte()) { + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeInt(number) + parcel.writeString(text) + parcel.writeByte(if (flag) 1 else 0) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): SomeData { + return SomeData(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/RedoParcelableAction/before.kt.template b/idea/resources/intentionDescriptions/RedoParcelableAction/before.kt.template new file mode 100644 index 00000000000..cea85f63724 --- /dev/null +++ b/idea/resources/intentionDescriptions/RedoParcelableAction/before.kt.template @@ -0,0 +1,23 @@ +class SomeData(val number: Int, val text: String, val flag: Boolean) : Parcelable { + constructor(parcel: Parcel) : this( + parcel.readString()) { + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeInt(oldField) + } + + override fun describeContents(): Int { + return 42 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): SomeData { + return SomeData(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/RedoParcelableAction/description.html b/idea/resources/intentionDescriptions/RedoParcelableAction/description.html new file mode 100644 index 00000000000..6e1f7bb969a --- /dev/null +++ b/idea/resources/intentionDescriptions/RedoParcelableAction/description.html @@ -0,0 +1,5 @@ + + +This intention regenerates the methods and the CREATOR object which implements Parcelable. Warning: Your custom code will be removed. + + \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/RemoveParcelableAction/after.kt.template b/idea/resources/intentionDescriptions/RemoveParcelableAction/after.kt.template new file mode 100644 index 00000000000..8e563e0214f --- /dev/null +++ b/idea/resources/intentionDescriptions/RemoveParcelableAction/after.kt.template @@ -0,0 +1,3 @@ +class SomeData(val number: Int, val text: String, val flag: Boolean) { + +} \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/RemoveParcelableAction/before.kt.template b/idea/resources/intentionDescriptions/RemoveParcelableAction/before.kt.template new file mode 100644 index 00000000000..bf39c45f351 --- /dev/null +++ b/idea/resources/intentionDescriptions/RemoveParcelableAction/before.kt.template @@ -0,0 +1,27 @@ +class SomeData(val number: Int, val text: String, val flag: Boolean) : Parcelable { + constructor(parcel: Parcel) : this( + parcel.readInt(), + parcel.readString(), + parcel.readByte() != 0.toByte()) { + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeInt(number) + parcel.writeString(text) + parcel.writeByte(if (flag) 1 else 0) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): SomeData { + return SomeData(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/RemoveParcelableAction/description.html b/idea/resources/intentionDescriptions/RemoveParcelableAction/description.html new file mode 100644 index 00000000000..b45d66ea142 --- /dev/null +++ b/idea/resources/intentionDescriptions/RemoveParcelableAction/description.html @@ -0,0 +1,5 @@ + + +This intention removes the methods and the CREATOR object which implements Parcelable. Warning: Your custom code will be removed. + + \ No newline at end of file diff --git a/idea/src/META-INF/android.xml b/idea/src/META-INF/android.xml index 068b816060f..d7f85eb3540 100644 --- a/idea/src/META-INF/android.xml +++ b/idea/src/META-INF/android.xml @@ -23,6 +23,20 @@ Kotlin Android + + org.jetbrains.kotlin.android.intention.ImplementParcelableAction + Kotlin Android + + + + org.jetbrains.kotlin.android.intention.RemoveParcelableAction + Kotlin Android + + + + org.jetbrains.kotlin.android.intention.RedoParcelableAction + Kotlin Android + diff --git a/idea/testData/android/intention/implementParcelable/allNullableTypes.kt b/idea/testData/android/intention/implementParcelable/allNullableTypes.kt new file mode 100644 index 00000000000..fd76323f9e4 --- /dev/null +++ b/idea/testData/android/intention/implementParcelable/allNullableTypes.kt @@ -0,0 +1,86 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.ImplementParcelableAction + +import android.os.Bundle +import android.os.IBinder +import android.os.Parcel +import android.os.Parcelable +import android.util.SparseBooleanArray +import android.util.SparseIntArray + +open class BaseParcelable(parcel: Parcel) : Parcelable { + override fun writeToParcel(parcel: Parcel, flags: Int) { + + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): BaseParcelable { + return BaseParcelable(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} + +class MyData { + + val delegated by lazy { 0 } + val readonlyField = true + + var listOfStrings: List? = null + var listOfIBinder: List? = null + + var arrayfield1: IntArray? = null + var arrayfield2: Array? = null + var arrayfield3: LongArray? = null + var arrayfield4: ByteArray? = null + var arrayfield5: CharArray? = null + var arrayfield6: ShortArray? = null // No method for Short + var arrayfield7: FloatArray? = null + var arrayfield8: DoubleArray? = null + var arrayfield9: BooleanArray? = null + var arrayfield10: Array? = null // No method for CharSequence + var arrayfield11: Array? = null + + var field1: Int? = 0 + var field2: String? = "" + var field3: Long? = 0 + var field4: Byte? = 0 + var field5: Char? = '\u0000' + var field6: Short? = 0 + var field7: Float? = 0.0f + var field8: Double? = 0.0 + var field9: Boolean? = false + var field10: CharSequence? = "" + var field11: SparseBooleanArray? = null + var field12: SparseIntArray? = null // read/write methods will be available starting from android O + var field13: Bundle? = null + var field14: IBinder? = null + + var goodArray: Array? = emptyArray() + var badArray: Array? = emptyArray() + + var goodList: List? = emptyList() + var badList: List? = emptyList() + + var parcelableProperty: BaseParcelable? + val uninitializedVal: Int? + + val fieldWIthGetter: Int? + get() { + return 0 + } + + var fieldWithCustomGetterAndSetter: Int? = 0 + get() { + return field + } + set(value: Int?) { + field = value + } +} \ No newline at end of file diff --git a/idea/testData/android/intention/implementParcelable/allNullableTypes.kt.expected b/idea/testData/android/intention/implementParcelable/allNullableTypes.kt.expected new file mode 100644 index 00000000000..23a679f01e2 --- /dev/null +++ b/idea/testData/android/intention/implementParcelable/allNullableTypes.kt.expected @@ -0,0 +1,160 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.ImplementParcelableAction + +import android.os.Bundle +import android.os.IBinder +import android.os.Parcel +import android.os.Parcelable +import android.util.SparseBooleanArray +import android.util.SparseIntArray + +open class BaseParcelable(parcel: Parcel) : Parcelable { + override fun writeToParcel(parcel: Parcel, flags: Int) { + + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): BaseParcelable { + return BaseParcelable(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} + +class MyData() : Parcelable { + + val delegated by lazy { 0 } + val readonlyField = true + + var listOfStrings: List? = null + var listOfIBinder: List? = null + + var arrayfield1: IntArray? = null + var arrayfield2: Array? = null + var arrayfield3: LongArray? = null + var arrayfield4: ByteArray? = null + var arrayfield5: CharArray? = null + var arrayfield6: ShortArray? = null // No method for Short + var arrayfield7: FloatArray? = null + var arrayfield8: DoubleArray? = null + var arrayfield9: BooleanArray? = null + var arrayfield10: Array? = null // No method for CharSequence + var arrayfield11: Array? = null + + var field1: Int? = 0 + var field2: String? = "" + var field3: Long? = 0 + var field4: Byte? = 0 + var field5: Char? = '\u0000' + var field6: Short? = 0 + var field7: Float? = 0.0f + var field8: Double? = 0.0 + var field9: Boolean? = false + var field10: CharSequence? = "" + var field11: SparseBooleanArray? = null + var field12: SparseIntArray? = null // read/write methods will be available starting from android O + var field13: Bundle? = null + var field14: IBinder? = null + + var goodArray: Array? = emptyArray() + var badArray: Array? = emptyArray() + + var goodList: List? = emptyList() + var badList: List? = emptyList() + + var parcelableProperty: BaseParcelable? + val uninitializedVal: Int? + + val fieldWIthGetter: Int? + get() { + return 0 + } + + var fieldWithCustomGetterAndSetter: Int? = 0 + get() { + return field + } + set(value: Int?) { + field = value + } + + constructor(parcel: Parcel) : this() { + listOfStrings = parcel.createStringArrayList() + listOfIBinder = parcel.createBinderArrayList() + arrayfield1 = parcel.createIntArray() + arrayfield2 = parcel.createStringArray() + arrayfield3 = parcel.createLongArray() + arrayfield4 = parcel.createByteArray() + arrayfield5 = parcel.createCharArray() + arrayfield7 = parcel.createFloatArray() + arrayfield8 = parcel.createDoubleArray() + arrayfield9 = parcel.createBooleanArray() + arrayfield11 = parcel.createBinderArray() + field1 = parcel.readValue(Int::class.java.classLoader) as? Int + field2 = parcel.readString() + field3 = parcel.readValue(Long::class.java.classLoader) as? Long + field4 = parcel.readValue(Byte::class.java.classLoader) as? Byte + field5 = parcel.readValue(Char::class.java.classLoader) as? Char + field6 = parcel.readValue(Short::class.java.classLoader) as? Short + field7 = parcel.readValue(Float::class.java.classLoader) as? Float + field8 = parcel.readValue(Double::class.java.classLoader) as? Double + field9 = parcel.readValue(Boolean::class.java.classLoader) as? Boolean + field10 = parcel.readString() + field11 = parcel.readSparseBooleanArray() + field13 = parcel.readBundle(Bundle::class.java.classLoader) + field14 = parcel.readStrongBinder() + goodArray = parcel.createTypedArray(BaseParcelable) + goodList = parcel.createTypedArrayList(BaseParcelable) + parcelableProperty = parcel.readParcelable(BaseParcelable::class.java.classLoader) + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeStringList(listOfStrings) + parcel.writeBinderList(listOfIBinder) + parcel.writeIntArray(arrayfield1) + parcel.writeStringArray(arrayfield2) + parcel.writeLongArray(arrayfield3) + parcel.writeByteArray(arrayfield4) + parcel.writeCharArray(arrayfield5) + parcel.writeFloatArray(arrayfield7) + parcel.writeDoubleArray(arrayfield8) + parcel.writeBooleanArray(arrayfield9) + parcel.writeBinderArray(arrayfield11) + parcel.writeValue(field1) + parcel.writeString(field2) + parcel.writeValue(field3) + parcel.writeValue(field4) + parcel.writeValue(field5) + parcel.writeValue(field6) + parcel.writeValue(field7) + parcel.writeValue(field8) + parcel.writeValue(field9) + parcel.writeString(field10?.toString()) + parcel.writeSparseBooleanArray(field11) + parcel.writeBundle(field13) + parcel.writeStrongBinder(field14) + parcel.writeTypedArray(goodArray, flags) + parcel.writeTypedList(goodList) + parcel.writeParcelable(parcelableProperty, flags) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): MyData { + return MyData(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/idea/testData/android/intention/implementParcelable/allTypes.kt b/idea/testData/android/intention/implementParcelable/allTypes.kt new file mode 100644 index 00000000000..12aee2930ab --- /dev/null +++ b/idea/testData/android/intention/implementParcelable/allTypes.kt @@ -0,0 +1,74 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.ImplementParcelableAction + +import android.os.Parcel +import android.os.Parcelable + +open class BaseParcelable(parcel: Parcel) : Parcelable { + override fun writeToParcel(parcel: Parcel, flags: Int) { + + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): BaseParcelable { + return BaseParcelable(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} + +class MyData { + + val delegated by lazy { 0 } + val readonlyField = true + + var field1: Int = 0 + var field2: String = "" + var field3: Long = 0 + var field4: Byte = 0 + var field5: Char = '\u0000' + var field6: Short = 0 + var field7: Float = 0.0f + var field8: Double = 0.0 + var field9: Boolean = false + var field10: CharSequence = "" + + var arrayfield1: IntArray + var arrayfield2: Array + var arrayfield3: LongArray + var arrayfield4: ByteArray + var arrayfield5: CharArray + var arrayfield6: ShortArray // No method for Short + var arrayfield7: FloatArray + var arrayfield8: DoubleArray + var arrayfield9: BooleanArray + var arrayfield10: Array // No method for CharSequence + + var goodArray: Array = emptyArray() + var badArray: Array = emptyArray() + + var goodList: List = emptyList() + var badList: List = emptyList() + + var parcelableProperty: BaseParcelable + val uninitializedVal: Int + + val fieldWIthGetter: Int + get() { + return 0 + } + + var fieldWithCustomGetterAndSetter: Int = 0 + get() { + return field + } + set(value: Int) { + field = value + } +} \ No newline at end of file diff --git a/idea/testData/android/intention/implementParcelable/allTypes.kt.expected b/idea/testData/android/intention/implementParcelable/allTypes.kt.expected new file mode 100644 index 00000000000..a436ed23704 --- /dev/null +++ b/idea/testData/android/intention/implementParcelable/allTypes.kt.expected @@ -0,0 +1,136 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.ImplementParcelableAction + +import android.os.Parcel +import android.os.Parcelable + +open class BaseParcelable(parcel: Parcel) : Parcelable { + override fun writeToParcel(parcel: Parcel, flags: Int) { + + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): BaseParcelable { + return BaseParcelable(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} + +class MyData() : Parcelable { + + val delegated by lazy { 0 } + val readonlyField = true + + var field1: Int = 0 + var field2: String = "" + var field3: Long = 0 + var field4: Byte = 0 + var field5: Char = '\u0000' + var field6: Short = 0 + var field7: Float = 0.0f + var field8: Double = 0.0 + var field9: Boolean = false + var field10: CharSequence = "" + + var arrayfield1: IntArray + var arrayfield2: Array + var arrayfield3: LongArray + var arrayfield4: ByteArray + var arrayfield5: CharArray + var arrayfield6: ShortArray // No method for Short + var arrayfield7: FloatArray + var arrayfield8: DoubleArray + var arrayfield9: BooleanArray + var arrayfield10: Array // No method for CharSequence + + var goodArray: Array = emptyArray() + var badArray: Array = emptyArray() + + var goodList: List = emptyList() + var badList: List = emptyList() + + var parcelableProperty: BaseParcelable + val uninitializedVal: Int + + val fieldWIthGetter: Int + get() { + return 0 + } + + var fieldWithCustomGetterAndSetter: Int = 0 + get() { + return field + } + set(value: Int) { + field = value + } + + constructor(parcel: Parcel) : this() { + field1 = parcel.readInt() + field2 = parcel.readString() + field3 = parcel.readLong() + field4 = parcel.readByte() + field5 = parcel.readInt().toChar() + field6 = parcel.readInt().toShort() + field7 = parcel.readFloat() + field8 = parcel.readDouble() + field9 = parcel.readByte() != 0.toByte() + field10 = parcel.readString() + arrayfield1 = parcel.createIntArray() + arrayfield2 = parcel.createStringArray() + arrayfield3 = parcel.createLongArray() + arrayfield4 = parcel.createByteArray() + arrayfield5 = parcel.createCharArray() + arrayfield7 = parcel.createFloatArray() + arrayfield8 = parcel.createDoubleArray() + arrayfield9 = parcel.createBooleanArray() + goodArray = parcel.createTypedArray(BaseParcelable) + goodList = parcel.createTypedArrayList(BaseParcelable) + parcelableProperty = parcel.readParcelable(BaseParcelable::class.java.classLoader) + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeInt(field1) + parcel.writeString(field2) + parcel.writeLong(field3) + parcel.writeByte(field4) + parcel.writeInt(field5.toInt()) + parcel.writeInt(field6.toInt()) + parcel.writeFloat(field7) + parcel.writeDouble(field8) + parcel.writeByte(if (field9) 1 else 0) + parcel.writeString(field10.toString()) + parcel.writeIntArray(arrayfield1) + parcel.writeStringArray(arrayfield2) + parcel.writeLongArray(arrayfield3) + parcel.writeByteArray(arrayfield4) + parcel.writeCharArray(arrayfield5) + parcel.writeFloatArray(arrayfield7) + parcel.writeDoubleArray(arrayfield8) + parcel.writeBooleanArray(arrayfield9) + parcel.writeTypedArray(goodArray, flags) + parcel.writeTypedList(goodList) + parcel.writeParcelable(parcelableProperty, flags) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): MyData { + return MyData(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/idea/testData/android/intention/implementParcelable/alreadyImplemented.kt b/idea/testData/android/intention/implementParcelable/alreadyImplemented.kt new file mode 100644 index 00000000000..7b59f7169e6 --- /dev/null +++ b/idea/testData/android/intention/implementParcelable/alreadyImplemented.kt @@ -0,0 +1,25 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.ImplementParcelableAction +// NOT_AVAILABLE +import android.os.Parcel +import android.os.Parcelable + + +class Simple(parcel: Parcel) : Parcelable { + override fun writeToParcel(parcel: Parcel, flags: Int) { + + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): Simple { + return Simple(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/idea/testData/android/intention/implementParcelable/constructorProperties.kt b/idea/testData/android/intention/implementParcelable/constructorProperties.kt new file mode 100644 index 00000000000..6ca436645e6 --- /dev/null +++ b/idea/testData/android/intention/implementParcelable/constructorProperties.kt @@ -0,0 +1,5 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.ImplementParcelableAction + +class MyData(val number: Int, val text: String, val flag: Boolean) { + var count = 0 +} diff --git a/idea/testData/android/intention/implementParcelable/constructorProperties.kt.expected b/idea/testData/android/intention/implementParcelable/constructorProperties.kt.expected new file mode 100644 index 00000000000..24cd18bdd20 --- /dev/null +++ b/idea/testData/android/intention/implementParcelable/constructorProperties.kt.expected @@ -0,0 +1,36 @@ +import android.os.Parcel +import android.os.Parcelable + +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.ImplementParcelableAction + +class MyData(val number: Int, val text: String, val flag: Boolean) : Parcelable { + var count = 0 + + constructor(parcel: Parcel) : this( + parcel.readInt(), + parcel.readString(), + parcel.readByte() != 0.toByte()) { + count = parcel.readInt() + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeInt(number) + parcel.writeString(text) + parcel.writeByte(if (flag) 1 else 0) + parcel.writeInt(count) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): MyData { + return MyData(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} diff --git a/idea/testData/android/intention/implementParcelable/constructorPropertiesWithParameters.kt b/idea/testData/android/intention/implementParcelable/constructorPropertiesWithParameters.kt new file mode 100644 index 00000000000..865d41a77a9 --- /dev/null +++ b/idea/testData/android/intention/implementParcelable/constructorPropertiesWithParameters.kt @@ -0,0 +1,3 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.ImplementParcelableAction + +class MyData(val number: Int, val text: String, flag: Boolean) diff --git a/idea/testData/android/intention/implementParcelable/constructorPropertiesWithParameters.kt.expected b/idea/testData/android/intention/implementParcelable/constructorPropertiesWithParameters.kt.expected new file mode 100644 index 00000000000..169d8cc6224 --- /dev/null +++ b/idea/testData/android/intention/implementParcelable/constructorPropertiesWithParameters.kt.expected @@ -0,0 +1,31 @@ +import android.os.Parcel +import android.os.Parcelable + +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.ImplementParcelableAction + +class MyData(val number: Int, val text: String, flag: Boolean) : Parcelable { + constructor(parcel: Parcel) : this( + parcel.readInt(), + parcel.readString(), + TODO("flag")) { + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeInt(number) + parcel.writeString(text) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): MyData { + return MyData(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} diff --git a/idea/testData/android/intention/implementParcelable/missingConstructor.kt b/idea/testData/android/intention/implementParcelable/missingConstructor.kt new file mode 100644 index 00000000000..d8000233f76 --- /dev/null +++ b/idea/testData/android/intention/implementParcelable/missingConstructor.kt @@ -0,0 +1,23 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.ImplementParcelableAction +import android.os.Parcel +import android.os.Parcelable + +class Simple : Parcelable { + override fun writeToParcel(parcel: Parcel, flags: Int) { + + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): Simple { + return Simple(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/idea/testData/android/intention/implementParcelable/missingConstructor.kt.expected b/idea/testData/android/intention/implementParcelable/missingConstructor.kt.expected new file mode 100644 index 00000000000..4ef429dddaa --- /dev/null +++ b/idea/testData/android/intention/implementParcelable/missingConstructor.kt.expected @@ -0,0 +1,26 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.ImplementParcelableAction +import android.os.Parcel +import android.os.Parcelable + +class Simple() : Parcelable { + constructor(parcel: Parcel) : this() { + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): Simple { + return Simple(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/idea/testData/android/intention/implementParcelable/missingDescribeContents.kt b/idea/testData/android/intention/implementParcelable/missingDescribeContents.kt new file mode 100644 index 00000000000..07362514c04 --- /dev/null +++ b/idea/testData/android/intention/implementParcelable/missingDescribeContents.kt @@ -0,0 +1,20 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.ImplementParcelableAction +import android.os.Parcel +import android.os.Parcelable + + +class Simple(parcel: Parcel) : Parcelable { + override fun writeToParcel(parcel: Parcel, flags: Int) { + + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): Simple { + return Simple(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/idea/testData/android/intention/implementParcelable/missingDescribeContents.kt.expected b/idea/testData/android/intention/implementParcelable/missingDescribeContents.kt.expected new file mode 100644 index 00000000000..c4852bdbeb7 --- /dev/null +++ b/idea/testData/android/intention/implementParcelable/missingDescribeContents.kt.expected @@ -0,0 +1,24 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.ImplementParcelableAction +import android.os.Parcel +import android.os.Parcelable + + +class Simple(parcel: Parcel) : Parcelable { + override fun writeToParcel(parcel: Parcel, flags: Int) { + + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): Simple { + return Simple(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + + override fun describeContents(): Int { + return 0 + } +} \ No newline at end of file diff --git a/idea/testData/android/intention/implementParcelable/missingWriteToParcel.kt b/idea/testData/android/intention/implementParcelable/missingWriteToParcel.kt new file mode 100644 index 00000000000..6407c5a3a58 --- /dev/null +++ b/idea/testData/android/intention/implementParcelable/missingWriteToParcel.kt @@ -0,0 +1,19 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.ImplementParcelableAction +import android.os.Parcel +import android.os.Parcelable + +class Simple(parcel: Parcel) : Parcelable { + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): Simple { + return Simple(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/idea/testData/android/intention/implementParcelable/missingWriteToParcel.kt.expected b/idea/testData/android/intention/implementParcelable/missingWriteToParcel.kt.expected new file mode 100644 index 00000000000..0f1c93bffbc --- /dev/null +++ b/idea/testData/android/intention/implementParcelable/missingWriteToParcel.kt.expected @@ -0,0 +1,23 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.ImplementParcelableAction +import android.os.Parcel +import android.os.Parcelable + +class Simple(parcel: Parcel) : Parcelable { + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): Simple { + return Simple(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + + } +} \ No newline at end of file diff --git a/idea/testData/android/intention/implementParcelable/simple.kt b/idea/testData/android/intention/implementParcelable/simple.kt new file mode 100644 index 00000000000..709d1b63682 --- /dev/null +++ b/idea/testData/android/intention/implementParcelable/simple.kt @@ -0,0 +1,3 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.ImplementParcelableAction + +class Simple \ No newline at end of file diff --git a/idea/testData/android/intention/implementParcelable/simple.kt.expected b/idea/testData/android/intention/implementParcelable/simple.kt.expected new file mode 100644 index 00000000000..567f6832cf8 --- /dev/null +++ b/idea/testData/android/intention/implementParcelable/simple.kt.expected @@ -0,0 +1,27 @@ +import android.os.Parcel +import android.os.Parcelable + +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.ImplementParcelableAction + +class Simple() : Parcelable { + constructor(parcel: Parcel) : this() { + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): Simple { + return Simple(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/idea/testData/android/intention/implementParcelable/withTransient.kt b/idea/testData/android/intention/implementParcelable/withTransient.kt new file mode 100644 index 00000000000..314e2dea9d3 --- /dev/null +++ b/idea/testData/android/intention/implementParcelable/withTransient.kt @@ -0,0 +1,7 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.ImplementParcelableAction +// WITH_RUNTIME + +class WithTransient() { + @Transient var transientText: String = "" + var text: String = "" +} \ No newline at end of file diff --git a/idea/testData/android/intention/implementParcelable/withTransient.kt.expected b/idea/testData/android/intention/implementParcelable/withTransient.kt.expected new file mode 100644 index 00000000000..1e987b4873f --- /dev/null +++ b/idea/testData/android/intention/implementParcelable/withTransient.kt.expected @@ -0,0 +1,32 @@ +import android.os.Parcel +import android.os.Parcelable + +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.ImplementParcelableAction +// WITH_RUNTIME + +class WithTransient() : Parcelable { + @Transient var transientText: String = "" + var text: String = "" + + constructor(parcel: Parcel) : this() { + text = parcel.readString() + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeString(text) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): WithTransient { + return WithTransient(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/idea/testData/android/intention/redoParcelable/indirectParcelable.kt b/idea/testData/android/intention/redoParcelable/indirectParcelable.kt new file mode 100644 index 00000000000..b6fc56e762f --- /dev/null +++ b/idea/testData/android/intention/redoParcelable/indirectParcelable.kt @@ -0,0 +1,44 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.RedoParcelableAction + +import android.os.Parcel +import android.os.Parcelable + +open class BaseParcelable(parcel: Parcel) : Parcelable { + override fun writeToParcel(parcel: Parcel, flags: Int) { + + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): BaseParcelable { + return BaseParcelable(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} + +class MyData(prcl: Parcel) : BaseParcelable(prcl) { + override fun writeToParcel(parcel: Parcel, flags: Int) { + super.writeToParcel(parcel, flags) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): MyData { + return MyData(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/idea/testData/android/intention/redoParcelable/indirectParcelable.kt.expected b/idea/testData/android/intention/redoParcelable/indirectParcelable.kt.expected new file mode 100644 index 00000000000..fe3f6cdd19f --- /dev/null +++ b/idea/testData/android/intention/redoParcelable/indirectParcelable.kt.expected @@ -0,0 +1,48 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.RedoParcelableAction + +import android.os.Parcel +import android.os.Parcelable + +open class BaseParcelable(parcel: Parcel) : Parcelable { + override fun writeToParcel(parcel: Parcel, flags: Int) { + + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): BaseParcelable { + return BaseParcelable(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} + +class MyData() : BaseParcelable(prcl) { + constructor(parcel: Parcel) : this() { + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + super.writeToParcel(parcel, flags) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): MyData { + return MyData(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + +} \ No newline at end of file diff --git a/idea/testData/android/intention/redoParcelable/init.kt b/idea/testData/android/intention/redoParcelable/init.kt new file mode 100644 index 00000000000..c4a36661b7d --- /dev/null +++ b/idea/testData/android/intention/redoParcelable/init.kt @@ -0,0 +1,35 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.RedoParcelableAction + +import android.os.Parcel +import android.os.Parcelable + + +class MyData(parcel: Parcel) : Parcelable { + + var count: Int = 0 + var text: String = "" + + init { + count = parcel.readInt() + text = parcel.readString() + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeInt(count) + parcel.writeString(text) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): MyData { + return MyData(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/idea/testData/android/intention/redoParcelable/init.kt.expected b/idea/testData/android/intention/redoParcelable/init.kt.expected new file mode 100644 index 00000000000..abbf0ff2d6d --- /dev/null +++ b/idea/testData/android/intention/redoParcelable/init.kt.expected @@ -0,0 +1,36 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.RedoParcelableAction + +import android.os.Parcel +import android.os.Parcelable + + +class MyData() : Parcelable { + + var count: Int = 0 + var text: String = "" + + constructor(parcel: Parcel) : this() { + count = parcel.readInt() + text = parcel.readString() + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeInt(count) + parcel.writeString(text) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): MyData { + return MyData(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + +} \ No newline at end of file diff --git a/idea/testData/android/intention/redoParcelable/initWithExtraStatements.kt b/idea/testData/android/intention/redoParcelable/initWithExtraStatements.kt new file mode 100644 index 00000000000..d1040d6170d --- /dev/null +++ b/idea/testData/android/intention/redoParcelable/initWithExtraStatements.kt @@ -0,0 +1,40 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.RedoParcelableAction + +import android.os.Parcel +import android.os.Parcelable + + +class MyData(parcel: Parcel) : Parcelable { + + var count: Int = 0 + var text: String = "" + + fun initialize() { + + } + + init { + initialize() + count = parcel.readInt() + text = parcel.readString() + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeInt(count) + parcel.writeString(text) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): MyData { + return MyData(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/idea/testData/android/intention/redoParcelable/initWithExtraStatements.kt.expected b/idea/testData/android/intention/redoParcelable/initWithExtraStatements.kt.expected new file mode 100644 index 00000000000..fe957c5db0e --- /dev/null +++ b/idea/testData/android/intention/redoParcelable/initWithExtraStatements.kt.expected @@ -0,0 +1,44 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.RedoParcelableAction + +import android.os.Parcel +import android.os.Parcelable + + +class MyData() : Parcelable { + + var count: Int = 0 + var text: String = "" + + constructor(parcel: Parcel) : this() { + count = parcel.readInt() + text = parcel.readString() + } + + fun initialize() { + + } + + init { + initialize() + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeInt(count) + parcel.writeString(text) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): MyData { + return MyData(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + +} \ No newline at end of file diff --git a/idea/testData/android/intention/redoParcelable/noParcelable.kt b/idea/testData/android/intention/redoParcelable/noParcelable.kt new file mode 100644 index 00000000000..7ce6033f7d0 --- /dev/null +++ b/idea/testData/android/intention/redoParcelable/noParcelable.kt @@ -0,0 +1,6 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.RedoParcelableAction +// NOT_AVAILABLE + +class Simple { + +} \ No newline at end of file diff --git a/idea/testData/android/intention/redoParcelable/oldField.kt b/idea/testData/android/intention/redoParcelable/oldField.kt new file mode 100644 index 00000000000..d63c5cc5c54 --- /dev/null +++ b/idea/testData/android/intention/redoParcelable/oldField.kt @@ -0,0 +1,37 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.RedoParcelableAction +import android.os.Parcel +import android.os.Parcelable + +class SomeData(parcel: Parcel) : Parcelable { + var count = 0 + + init { + oldField = parcel.readInt() + field9 = parcel.readByte() != 0.toByte() + goodArray = parcel.createTypedArray(SuperParcelable) + goodList = parcel.createTypedArrayList(SuperParcelable) + parcelableProperty = parcel.readParcelable(SuperParcelable::class.java.classLoader) + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeInt(oldField) + parcel.writeByte(if (field9) 1 else 0) + parcel.writeTypedArray(goodArray, flags) + parcel.writeTypedList(goodList) + parcel.writeParcelable(parcelableProperty, flags) + } + + override fun describeContents(): Int { + return 42 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): SomeData { + return SomeData(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/idea/testData/android/intention/redoParcelable/oldField.kt.expected b/idea/testData/android/intention/redoParcelable/oldField.kt.expected new file mode 100644 index 00000000000..1b4c4b19718 --- /dev/null +++ b/idea/testData/android/intention/redoParcelable/oldField.kt.expected @@ -0,0 +1,30 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.RedoParcelableAction +import android.os.Parcel +import android.os.Parcelable + +class SomeData() : Parcelable { + var count = 0 + + constructor(parcel: Parcel) : this() { + count = parcel.readInt() + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeInt(count) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): SomeData { + return SomeData(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + +} \ No newline at end of file diff --git a/idea/testData/android/intention/redoParcelable/oldFieldSecondary.kt b/idea/testData/android/intention/redoParcelable/oldFieldSecondary.kt new file mode 100644 index 00000000000..fc17a04d4b5 --- /dev/null +++ b/idea/testData/android/intention/redoParcelable/oldFieldSecondary.kt @@ -0,0 +1,28 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.RedoParcelableAction +import android.os.Parcel +import android.os.Parcelable + +class SomeData(val number: Int, val text: String, val flag: Boolean) : Parcelable { + constructor(parcel: Parcel) : this(parcel.readString()) { + someOtherField = parcel.readInt() + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeString(oldField) + parcel.writeInt(someOtherField) + } + + override fun describeContents(): Int { + return 42 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): SomeData { + return SomeData(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/idea/testData/android/intention/redoParcelable/oldFieldSecondary.kt.expected b/idea/testData/android/intention/redoParcelable/oldFieldSecondary.kt.expected new file mode 100644 index 00000000000..42c70cd519e --- /dev/null +++ b/idea/testData/android/intention/redoParcelable/oldFieldSecondary.kt.expected @@ -0,0 +1,32 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.RedoParcelableAction +import android.os.Parcel +import android.os.Parcelable + +class SomeData(val number: Int, val text: String, val flag: Boolean) : Parcelable { + constructor(parcel: Parcel) : this( + parcel.readInt(), + parcel.readString(), + parcel.readByte() != 0.toByte()) { + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeInt(number) + parcel.writeString(text) + parcel.writeByte(if (flag) 1 else 0) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): SomeData { + return SomeData(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + +} \ No newline at end of file diff --git a/idea/testData/android/intention/redoParcelable/simple.kt b/idea/testData/android/intention/redoParcelable/simple.kt new file mode 100644 index 00000000000..f57b62eefb1 --- /dev/null +++ b/idea/testData/android/intention/redoParcelable/simple.kt @@ -0,0 +1,4 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.RedoParcelableAction +import android.os.Parcelable + +class Simple : Parcelable \ No newline at end of file diff --git a/idea/testData/android/intention/redoParcelable/simple.kt.expected b/idea/testData/android/intention/redoParcelable/simple.kt.expected new file mode 100644 index 00000000000..df0da1b6ddd --- /dev/null +++ b/idea/testData/android/intention/redoParcelable/simple.kt.expected @@ -0,0 +1,26 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.RedoParcelableAction +import android.os.Parcel +import android.os.Parcelable + +class Simple() : Parcelable { + constructor(parcel: Parcel) : this() { + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): Simple { + return Simple(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/idea/testData/android/intention/removeParcelable/inderectParcelable.kt b/idea/testData/android/intention/removeParcelable/inderectParcelable.kt new file mode 100644 index 00000000000..40164150340 --- /dev/null +++ b/idea/testData/android/intention/removeParcelable/inderectParcelable.kt @@ -0,0 +1,44 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.RemoveParcelableAction + +import android.os.Parcel +import android.os.Parcelable + +open class BaseParcelable(parcel: Parcel) : Parcelable { + override fun writeToParcel(parcel: Parcel, flags: Int) { + + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): BaseParcelable { + return BaseParcelable(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} + +class MyData(parcel: Parcel) : BaseParcelable(parcel) { + override fun writeToParcel(parcel: Parcel, flags: Int) { + super.writeToParcel(parcel, flags) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): MyData { + return MyData(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/idea/testData/android/intention/removeParcelable/inderectParcelable.kt.expected b/idea/testData/android/intention/removeParcelable/inderectParcelable.kt.expected new file mode 100644 index 00000000000..209d77db639 --- /dev/null +++ b/idea/testData/android/intention/removeParcelable/inderectParcelable.kt.expected @@ -0,0 +1,28 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.RemoveParcelableAction + +import android.os.Parcel +import android.os.Parcelable + +open class BaseParcelable(parcel: Parcel) : Parcelable { + override fun writeToParcel(parcel: Parcel, flags: Int) { + + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): BaseParcelable { + return BaseParcelable(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} + +class MyData : BaseParcelable(parcel) { + +} \ No newline at end of file diff --git a/idea/testData/android/intention/removeParcelable/init.kt b/idea/testData/android/intention/removeParcelable/init.kt new file mode 100644 index 00000000000..a5f1c4e8141 --- /dev/null +++ b/idea/testData/android/intention/removeParcelable/init.kt @@ -0,0 +1,35 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.RemoveParcelableAction + +import android.os.Parcel +import android.os.Parcelable + + +class MyData(parcel: Parcel) : Parcelable { + + var count: Int = 0 + var text: String = "" + + init { + count = parcel.readInt() + text = parcel.readString() + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeInt(count) + parcel.writeString(text) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): MyData { + return MyData(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/idea/testData/android/intention/removeParcelable/init.kt.expected b/idea/testData/android/intention/removeParcelable/init.kt.expected new file mode 100644 index 00000000000..0048bd8ac0f --- /dev/null +++ b/idea/testData/android/intention/removeParcelable/init.kt.expected @@ -0,0 +1,12 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.RemoveParcelableAction + +import android.os.Parcel +import android.os.Parcelable + + +class MyData { + + var count: Int = 0 + var text: String = "" + +} \ No newline at end of file diff --git a/idea/testData/android/intention/removeParcelable/initWithExtraStatements.kt b/idea/testData/android/intention/removeParcelable/initWithExtraStatements.kt new file mode 100644 index 00000000000..2ae2354540f --- /dev/null +++ b/idea/testData/android/intention/removeParcelable/initWithExtraStatements.kt @@ -0,0 +1,40 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.RemoveParcelableAction + +import android.os.Parcel +import android.os.Parcelable + + +class MyData(parcel: Parcel) : Parcelable { + + var count: Int = 0 + var text: String = "" + + fun initialize() { + + } + + init { + initialize() + count = parcel.readInt() + text = parcel.readString() + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeInt(count) + parcel.writeString(text) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): MyData { + return MyData(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/idea/testData/android/intention/removeParcelable/initWithExtraStatements.kt.expected b/idea/testData/android/intention/removeParcelable/initWithExtraStatements.kt.expected new file mode 100644 index 00000000000..480aaff4fb2 --- /dev/null +++ b/idea/testData/android/intention/removeParcelable/initWithExtraStatements.kt.expected @@ -0,0 +1,20 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.RemoveParcelableAction + +import android.os.Parcel +import android.os.Parcelable + + +class MyData { + + var count: Int = 0 + var text: String = "" + + fun initialize() { + + } + + init { + initialize() + } + +} \ No newline at end of file diff --git a/idea/testData/android/intention/removeParcelable/noParcelable.kt b/idea/testData/android/intention/removeParcelable/noParcelable.kt new file mode 100644 index 00000000000..edd65b03ff1 --- /dev/null +++ b/idea/testData/android/intention/removeParcelable/noParcelable.kt @@ -0,0 +1,6 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.RemoveParcelableAction +// NOT_AVAILABLE + +class Simple { + +} \ No newline at end of file diff --git a/idea/testData/android/intention/removeParcelable/simple.kt b/idea/testData/android/intention/removeParcelable/simple.kt new file mode 100644 index 00000000000..e262a822f9b --- /dev/null +++ b/idea/testData/android/intention/removeParcelable/simple.kt @@ -0,0 +1,4 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.RemoveParcelableAction +import android.os.Parcelable + +class Simple : Parcelable \ No newline at end of file diff --git a/idea/testData/android/intention/removeParcelable/simple.kt.expected b/idea/testData/android/intention/removeParcelable/simple.kt.expected new file mode 100644 index 00000000000..7f38b59c5ca --- /dev/null +++ b/idea/testData/android/intention/removeParcelable/simple.kt.expected @@ -0,0 +1,4 @@ +// INTENTION_CLASS: org.jetbrains.kotlin.android.intention.RemoveParcelableAction +import android.os.Parcelable + +class Simple \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/parcelable/missingCreator.kt b/idea/testData/android/lintQuickfix/parcelable/missingCreator.kt new file mode 100644 index 00000000000..6274bc4d340 --- /dev/null +++ b/idea/testData/android/lintQuickfix/parcelable/missingCreator.kt @@ -0,0 +1,14 @@ +// INTENTION_TEXT: Add Parcelable Implementation +// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintParcelCreatorInspection +import android.os.Parcel +import android.os.Parcelable + +class MissingCreator : Parcelable { + override fun writeToParcel(dest: Parcel?, flags: Int) { + TODO("not implemented") + } + + override fun describeContents(): Int { + TODO("not implemented") + } +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/parcelable/missingCreator.kt.expected b/idea/testData/android/lintQuickfix/parcelable/missingCreator.kt.expected new file mode 100644 index 00000000000..c4ccf101b42 --- /dev/null +++ b/idea/testData/android/lintQuickfix/parcelable/missingCreator.kt.expected @@ -0,0 +1,27 @@ +// INTENTION_TEXT: Add Parcelable Implementation +// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintParcelCreatorInspection +import android.os.Parcel +import android.os.Parcelable + +class MissingCreator() : Parcelable { + constructor(parcel: Parcel) : this() { + } + + override fun writeToParcel(dest: Parcel?, flags: Int) { + TODO("not implemented") + } + + override fun describeContents(): Int { + TODO("not implemented") + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): MissingCreator { + return MissingCreator(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/parcelable/noImplementation.kt b/idea/testData/android/lintQuickfix/parcelable/noImplementation.kt new file mode 100644 index 00000000000..e045a311575 --- /dev/null +++ b/idea/testData/android/lintQuickfix/parcelable/noImplementation.kt @@ -0,0 +1,5 @@ +// INTENTION_TEXT: Add Parcelable Implementation +// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintParcelCreatorInspection +import android.os.Parcelable + +class NoImplementation : Parcelable \ No newline at end of file diff --git a/idea/testData/android/lintQuickfix/parcelable/noImplementation.kt.expected b/idea/testData/android/lintQuickfix/parcelable/noImplementation.kt.expected new file mode 100644 index 00000000000..6ebc78fbe7e --- /dev/null +++ b/idea/testData/android/lintQuickfix/parcelable/noImplementation.kt.expected @@ -0,0 +1,27 @@ +// INTENTION_TEXT: Add Parcelable Implementation +// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintParcelCreatorInspection +import android.os.Parcel +import android.os.Parcelable + +class NoImplementation() : Parcelable { + constructor(parcel: Parcel) : this() { + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): NoImplementation { + return NoImplementation(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/plugins/lint/lint-idea/lint-idea.iml b/plugins/lint/lint-idea/lint-idea.iml index 9089c2f83be..55aac4b2efd 100644 --- a/plugins/lint/lint-idea/lint-idea.iml +++ b/plugins/lint/lint-idea/lint-idea.iml @@ -19,5 +19,6 @@ + \ No newline at end of file diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintInspectionToolProvider.java b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintInspectionToolProvider.java index b23aea18ce3..a74bd026391 100644 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintInspectionToolProvider.java +++ b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintInspectionToolProvider.java @@ -255,7 +255,11 @@ public class AndroidLintInspectionToolProvider { public AndroidKLintParcelCreatorInspection() { super(AndroidBundle.message("android.lint.inspections.parcel.creator"), ParcelDetector.ISSUE); } - + @NotNull + @Override + public AndroidLintQuickFix[] getQuickFixes(@NotNull String message) { + return new AndroidLintQuickFix[]{ new ParcelableQuickFix() }; + } } public static class AndroidKLintPluralsCandidateInspection extends AndroidLintInspectionBase { diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/ParcelableQuickFix.kt b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/ParcelableQuickFix.kt new file mode 100644 index 00000000000..527bf641dae --- /dev/null +++ b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/ParcelableQuickFix.kt @@ -0,0 +1,38 @@ +/* + * 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.android.inspections.klint + +import com.intellij.psi.PsiElement +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.android.util.AndroidBundle +import org.jetbrains.kotlin.android.canAddParcelable +import org.jetbrains.kotlin.android.implementParcelable +import org.jetbrains.kotlin.psi.KtClass + + +class ParcelableQuickFix : AndroidLintQuickFix { + override fun apply(startElement: PsiElement, endElement: PsiElement, context: AndroidQuickfixContexts.Context) { + startElement.getTargetClass()?.implementParcelable() + } + + override fun isApplicable(startElement: PsiElement, endElement: PsiElement, contextType: AndroidQuickfixContexts.ContextType): Boolean = + startElement.getTargetClass()?.canAddParcelable() ?: false + + override fun getName(): String = AndroidBundle.message("implement.parcelable.intention.text") + + private fun PsiElement.getTargetClass(): KtClass? = PsiTreeUtil.getParentOfType(this, KtClass::class.java, false) +} \ No newline at end of file