Support android.support.v4.Fragment classes

This commit is contained in:
Yan Zhulanow
2015-04-07 20:41:00 +03:00
parent cc697d2c1a
commit 514bc74e9c
9 changed files with 74 additions and 16 deletions
@@ -3,5 +3,6 @@ ERROR: Required plugin option not present: org.jetbrains.kotlin.android:androidR
Plugin "org.jetbrains.kotlin.android" usage:
androidRes <path> Android resources path (required)
androidManifest <path> Android manifest file (required)
supportV4 <path> Support android-v4 library
COMPILATION_ERROR
@@ -36,11 +36,16 @@ public class AndroidSubplugin : KotlinGradleSubplugin {
val resourceDir = mainSourceSet.getRes().getSrcDirs().firstOrNull()
val manifestFile = mainSourceSet.getManifest().getSrcFile()
val compileDependencies = project.getConfigurations().getByName("compile").getFiles()
val supportV4 = compileDependencies?.filter { it.name.startsWith("support-v4-") }?.isNotEmpty() ?: false
val supportV4Property = if (supportV4) "true" else "false"
if (resourceDir != null) {
resourceDir.listFiles { it.isDirectory() && it.name.startsWith("layout") }?.forEach { task.source(it) }
return listOf(
SubpluginOption("androidRes", resourceDir.getAbsolutePath()),
SubpluginOption("androidManifest", manifestFile.getAbsolutePath())
SubpluginOption("androidManifest", manifestFile.getAbsolutePath()),
SubpluginOption("supportV4", supportV4Property)
)
}
@@ -38,6 +38,8 @@ public object AndroidConfigurationKeys {
public val ANDROID_RES_PATH: CompilerConfigurationKey<String> = CompilerConfigurationKey.create<String>("android resources search path")
public val ANDROID_MANIFEST: CompilerConfigurationKey<String> = CompilerConfigurationKey.create<String>("android manifest file")
public val SUPPORT_V4: CompilerConfigurationKey<String> = CompilerConfigurationKey.create<String>("'true' if compiled with support-v4 library")
}
public class AndroidCommandLineProcessor : CommandLineProcessor {
@@ -46,16 +48,18 @@ public class AndroidCommandLineProcessor : CommandLineProcessor {
public val RESOURCE_PATH_OPTION: CliOption = CliOption("androidRes", "<path>", "Android resources path")
public val MANIFEST_FILE_OPTION: CliOption = CliOption("androidManifest", "<path>", "Android manifest file")
public val SUPPORT_V4_OPTION: CliOption = CliOption("supportV4", "<path>", "Support android-v4 library", required = false)
}
override val pluginId: String = ANDROID_COMPILER_PLUGIN_ID
override val pluginOptions: Collection<CliOption> = listOf(RESOURCE_PATH_OPTION, MANIFEST_FILE_OPTION)
override val pluginOptions: Collection<CliOption> = listOf(RESOURCE_PATH_OPTION, MANIFEST_FILE_OPTION, SUPPORT_V4_OPTION)
override fun processOption(option: CliOption, value: String, configuration: CompilerConfiguration) {
when (option) {
RESOURCE_PATH_OPTION -> configuration.put(AndroidConfigurationKeys.ANDROID_RES_PATH, value)
MANIFEST_FILE_OPTION -> configuration.put(AndroidConfigurationKeys.ANDROID_MANIFEST, value)
SUPPORT_V4_OPTION -> configuration.put(AndroidConfigurationKeys.SUPPORT_V4, value)
else -> throw CliOptionProcessingException("Unknown option: ${option.name}")
}
}
@@ -73,9 +77,13 @@ public class AndroidComponentRegistrar : ComponentRegistrar {
public override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) {
val androidResPath = configuration.get(AndroidConfigurationKeys.ANDROID_RES_PATH)
val androidManifest = configuration.get(AndroidConfigurationKeys.ANDROID_MANIFEST)
val supportV4 = configuration.get(AndroidConfigurationKeys.SUPPORT_V4) ?: "false"
if (androidResPath != null && androidManifest != null) {
project.registerService(javaClass<AndroidUIXmlProcessor>(), CliAndroidUIXmlProcessor(project, androidManifest, androidResPath))
val xmlProcessor = CliAndroidUIXmlProcessor(project, androidManifest, androidResPath)
if (supportV4 == "true") xmlProcessor.supportV4 = true
project.registerService(javaClass<AndroidUIXmlProcessor>(), xmlProcessor)
project.registerService(javaClass<AndroidResourceManager>(), CliAndroidResourceManager(project, androidManifest, androidResPath))
ExternalDeclarationsProvider.registerExtension(project, CliAndroidDeclarationsProvider(project))
@@ -44,12 +44,16 @@ import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
private enum class AndroidClassType(val internalClassName: String, val supportsCache: Boolean = false) {
ACTIVITY : AndroidClassType("android/app/Activity", true)
FRAGMENT : AndroidClassType("android/app/Fragment", true)
VIEW : AndroidClassType("android/view/View")
ACTIVITY : AndroidClassType(AndroidConst.ACTIVITY_FQNAME.innerName, true)
FRAGMENT : AndroidClassType(AndroidConst.FRAGMENT_FQNAME.innerName, true)
SUPPORT_FRAGMENT : AndroidClassType(AndroidConst.SUPPORT_FRAGMENT_FQNAME.innerName, true)
VIEW : AndroidClassType(AndroidConst.VIEW_FQNAME.innerName)
UNKNOWN : AndroidClassType("")
}
private val String.innerName: String
get() = replace('.', '/')
public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
companion object {
private val PROPERTY_NAME = "_\$_findViewCache"
@@ -145,7 +149,7 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
v.getstatic(androidPackage.replace(".", "/") + "/R\$id", descriptor.getName().asString(), "I")
v.invokevirtual(androidClassType.internalClassName, "findViewById", "(I)Landroid/view/View;", false)
}
AndroidClassType.FRAGMENT -> {
AndroidClassType.FRAGMENT, AndroidClassType.SUPPORT_FRAGMENT -> {
receiver.put(Type.getType("L${androidClassType.internalClassName};"), v)
v.invokevirtual(androidClassType.internalClassName, "getView", "()Landroid/view/View;", false)
v.getstatic(androidPackage.replace(".", "/") + "/R\$id", descriptor.getName().asString(), "I")
@@ -169,9 +173,10 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
private fun getClassType(descriptor: ClassifierDescriptor): AndroidClassType {
fun getClassTypeInternal(name: String): AndroidClassType? = when (name) {
"android.app.Activity" -> AndroidClassType.ACTIVITY
"android.app.Fragment" -> AndroidClassType.FRAGMENT
"android.view.View" -> AndroidClassType.VIEW
AndroidConst.ACTIVITY_FQNAME -> AndroidClassType.ACTIVITY
AndroidConst.FRAGMENT_FQNAME -> AndroidClassType.FRAGMENT
AndroidConst.SUPPORT_FRAGMENT_FQNAME -> AndroidClassType.SUPPORT_FRAGMENT
AndroidConst.VIEW_FQNAME -> AndroidClassType.VIEW
else -> null
}
@@ -291,7 +296,7 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
loadId()
iv.invokevirtual(className, "findViewById", "(I)Landroid/view/View;", false)
}
AndroidClassType.FRAGMENT -> {
AndroidClassType.FRAGMENT, AndroidClassType.SUPPORT_FRAGMENT -> {
iv.invokevirtual(className, "getView", "()Landroid/view/View;", false)
loadId()
iv.invokevirtual("android/view/View", "findViewById", "(I)Landroid/view/View;", false)
@@ -41,6 +41,12 @@ public object AndroidConst {
val CLEAR_FUNCTION_NAME = "clearFindViewByIdCache"
val VIEW_FQNAME = "android.view.View"
val ACTIVITY_FQNAME = "android.app.Activity"
val FRAGMENT_FQNAME = "android.app.Fragment"
val SUPPORT_V4_PACKAGE = "android.support.v4"
val SUPPORT_FRAGMENT_FQNAME = "$SUPPORT_V4_PACKAGE.app.Fragment"
val IGNORED_XML_WIDGET_TYPES = setOf("requestFocus", "merge", "tag", "check", "fragment")
val ESCAPED_IDENTIFIERS = (JetTokens.KEYWORDS.getTypes() + JetTokens.SOFT_KEYWORDS.getTypes())
@@ -66,6 +66,8 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) {
protected abstract val cachedSources: CachedValue<List<AndroidSyntheticFile>>
var supportV4 = false
private val cachedJetFiles: CachedValue<List<JetFile>> by Delegates.lazy {
cachedValue {
val psiManager = PsiManager.getInstance(project)
@@ -90,8 +92,9 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) {
val clearCacheFile = renderSyntheticFile("clearCache") {
writePackage(AndroidConst.SYNTHETIC_PACKAGE)
writeAndroidImports()
writeClearCacheFunction("android.app.Activity")
writeClearCacheFunction("android.app.Fragment")
writeClearCacheFunction(AndroidConst.ACTIVITY_FQNAME)
writeClearCacheFunction(AndroidConst.FRAGMENT_FQNAME)
if (supportV4) writeClearCacheFunction(AndroidConst.SUPPORT_FRAGMENT_FQNAME)
}
listOf(clearCacheFile,
@@ -109,6 +112,7 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) {
val mainLayoutFile = renderMainLayoutFile(layoutName, resources)
val viewLayoutFile = renderViewLayoutFile(layoutName, resources)
listOf(mainLayoutFile, viewLayoutFile)
}.filterNotNull() + commonFiles
}
@@ -138,7 +142,9 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) {
for (res in resources) {
properties(res).forEach {
writeSyntheticProperty(it.first, res, it.second)
if (supportV4 || !it.first.startsWith(AndroidConst.SUPPORT_V4_PACKAGE)) {
writeSyntheticProperty(it.first, res, it.second)
}
}
}
}
@@ -31,7 +31,8 @@ public abstract class AndroidResource(val id: String) {
public class AndroidWidget(id: String, override val className: String) : AndroidResource(id) {
override val mainProperties = listOf(
"android.app.Activity" to "findViewById(0)",
"android.app.Fragment" to "getView().findViewById(0)")
"android.app.Fragment" to "getView().findViewById(0)",
"android.support.v4.app.Fragment" to "getView().findViewById(0)")
override val viewProperties = listOf("android.view.View" to "findViewById(0)")
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.plugin.android
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.module.Module
import com.intellij.psi.JavaPsiFacade
import org.jetbrains.kotlin.plugin.android.IDEAndroidResourceManager
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.plugin.android.AndroidXmlVisitor
@@ -29,6 +30,12 @@ import com.intellij.psi.util.CachedValueProvider.Result
class IDEAndroidUIXmlProcessor(val module: Module) : AndroidUIXmlProcessor(module.getProject()) {
init {
val scope = module.getModuleWithDependenciesAndLibrariesScope(false)
supportV4 = JavaPsiFacade.getInstance(module.getProject())
.findClasses(AndroidConst.SUPPORT_FRAGMENT_FQNAME, scope).isNotEmpty()
}
override val resourceManager: IDEAndroidResourceManager = IDEAndroidResourceManager(module)
private val psiTreeChangePreprocessor by Delegates.lazy {
@@ -23,6 +23,9 @@ import org.jetbrains.jps.model.module.JpsModule
import java.io.File
import org.jetbrains.jps.android.AndroidJpsUtil
import com.intellij.util.PathUtil
import org.jetbrains.jps.model.library.JpsOrderRootType
import org.jetbrains.jps.model.module.JpsLibraryDependency
import org.jetbrains.jps.model.module.JpsModuleDependency
public class KotlinAndroidJpsPlugin : KotlinJpsCompilerArgumentsProvider {
override fun getExtraArguments(moduleBuildTarget: ModuleBuildTarget, context: CompileContext): List<String> {
@@ -30,12 +33,27 @@ public class KotlinAndroidJpsPlugin : KotlinJpsCompilerArgumentsProvider {
val pluginId = ANDROID_COMPILER_PLUGIN_ID
val resPath = getAndroidResPath(module)
val manifestFile = getAndroidManifest(module)
val supportV4 = if (isSupportV4LibraryAttached(module)) "true" else "false"
return if (resPath != null && manifestFile != null) listOf(
getPluginOptionString(pluginId, RESOURCE_PATH_OPTION_NAME, resPath),
getPluginOptionString(pluginId, MANIFEST_FILE_OPTION_NAME, manifestFile))
getPluginOptionString(pluginId, MANIFEST_FILE_OPTION_NAME, manifestFile),
getPluginOptionString(pluginId, SUPPORT_V4_OPTION_NAME, supportV4))
else listOf()
}
private fun isSupportV4LibraryAttached(module: JpsModule): Boolean {
return module.getDependenciesList().getDependencies().any { dep ->
when (dep) {
is JpsLibraryDependency ->
dep.getLibrary()?.getFiles(JpsOrderRootType.COMPILED)?.any {
it.name.startsWith("support-v4") && it.extension.toUpperCase() == "JAR"
} ?: false
else -> false
}
}
}
override fun getClasspath(moduleBuildTarget: ModuleBuildTarget, context: CompileContext): List<String> {
val inJar = File(PathUtil.getJarPathForClass(javaClass)).isFile()
val manifestFile = getAndroidManifest(moduleBuildTarget.getModule())
@@ -72,6 +90,7 @@ public class KotlinAndroidJpsPlugin : KotlinJpsCompilerArgumentsProvider {
private val RESOURCE_PATH_OPTION_NAME = "androidRes"
private val MANIFEST_FILE_OPTION_NAME = "androidManifest"
private val SUPPORT_V4_OPTION_NAME = "supportV4"
private fun getPluginOptionString(pluginId: String, key: String, value: String): String {
return "plugin:$pluginId:$key=$value"