Implemented ID parser

This commit is contained in:
Akash Yadav
2022-11-06 19:05:42 +05:30
parent bb2f36fe6b
commit 3ed07e9bc9
15 changed files with 176 additions and 24 deletions
Binary file not shown.
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/included_parent"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/layout_include"/>
</LinearLayout>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:id="@+id/inlined_parent"
android:layout_width="match_parent"
android:layout_height="match_parent">
<aapt:attr name="android:background">
<shape android:id="@+id/my_shape">
<solid android:color="#f44336"/>
</shape>
</aapt:attr>
</LinearLayout>
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/included_parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<TextView
android:id="@+id/included_text"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="This is an included layout"/>
<TextView
android:id="@+id/included_text_other"
android:layout_below="@id/included_text"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="This is an included layout"/>
</RelativeLayout>
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/single_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
@@ -23,6 +23,7 @@ import android.graphics.drawable.ColorDrawable
import android.view.View
import android.widget.TextView
import androidx.core.content.ContextCompat
import com.android.aaptcompiler.XmlResource
import com.itsaky.androidide.inflater.R
import java.io.File
@@ -31,7 +32,7 @@ import java.io.File
*
* @author Akash Yadav
*/
class ErrorView(file: File, context: Context, message: String) :
class ErrorView(file: LayoutFile, context: Context, message: String) :
ViewImpl(file = file, name = NAME, view = createErrView(context, message)) {
companion object {
private val NAME = View::class.java.name
@@ -0,0 +1,30 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.inflater.internal
import java.io.File
/**
* A data class to hold information about the file which was used to inflate and create a [ViewImpl]
* instance.
*
* @property file The actual file.
* @property resName The resource name.
* @author Akash Yadav
*/
data class LayoutFile(val file: File, val resName: String)
@@ -19,12 +19,12 @@ package com.itsaky.androidide.inflater.internal
import android.content.Context
import android.view.View
import com.itsaky.androidide.inflater.InflateException
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams
import com.android.aapt.Resources.SourcePosition
import com.android.aapt.Resources.XmlElement
import com.android.aapt.Resources.XmlNode.NodeCase.ELEMENT
import com.android.aaptcompiler.AaptResourceType.ID
import com.android.aaptcompiler.AaptResourceType.LAYOUT
import com.android.aaptcompiler.BlameLogger
import com.android.aaptcompiler.ResourceFile
@@ -36,8 +36,10 @@ import com.itsaky.androidide.inflater.IInflateEventsListener
import com.itsaky.androidide.inflater.ILayoutInflater
import com.itsaky.androidide.inflater.IView
import com.itsaky.androidide.inflater.IViewGroup
import com.itsaky.androidide.inflater.InflateException
import com.itsaky.androidide.inflater.InflationFinishEvent
import com.itsaky.androidide.inflater.InflationStartEvent
import com.itsaky.androidide.inflater.internal.utils.IDTable
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.api.AndroidModule
import com.itsaky.androidide.utils.ILogger
@@ -55,17 +57,22 @@ open class LayoutInflaterImpl : ILayoutInflater() {
override var inflationEventListener: IInflateEventsListener? = null
private val log = ILogger.newInstance("LayoutInflaterImpl")
private var inflatingFile: File? = null
private var _primaryInflatingFile: File? = null
private var _currentLayoutFile: LayoutFile? = null
protected val primaryInflatingFile: File
get() = this._primaryInflatingFile!!
protected val file: File
get() = this.inflatingFile!!
protected val currentLayoutFile: LayoutFile
get() = _currentLayoutFile!!
override fun inflate(file: File, parent: ViewGroup): IView? {
this.inflatingFile = file
this._primaryInflatingFile = file
IDTable.newRound()
inflationEventListener?.onEvent(InflationStartEvent())
return doInflate(file, wrap(parent)).apply {
inflationEventListener?.onEvent(InflationFinishEvent(this))
inflatingFile = null
_primaryInflatingFile = null
}
}
@@ -90,7 +97,7 @@ open class LayoutInflaterImpl : ILayoutInflater() {
val processor = XmlProcessor(pathData.source, BlameLogger(IDELogger))
processor.process(resFile, file.inputStream())
return doInflate(processor, parent, module)
}
@@ -101,18 +108,29 @@ open class LayoutInflaterImpl : ILayoutInflater() {
): IView? {
// TODO(itsaky) : Add test for multiple view as root layout
// The inflater should fail in such cases
val (_, node) =
val (file, node) =
processor.xmlResources.find { it.file == processor.primaryFile }
?: throw InflateException("Unable to find primary XML resource from XmlProcessor")
this._currentLayoutFile = LayoutFile(this.primaryInflatingFile, file.name.entry!!)
if (node.nodeCase != ELEMENT) {
throw InflateException(
"Found ${node.nodeCase} but $ELEMENT was expected at ${node.source.lineCol()}"
)
}
// Store all IDs
file.exportedSymbols.filter { it.name.type == ID }.forEach {
IDTable.set(currentLayoutFile.resName, it.name.entry!!, View.generateViewId())
}
val element = node.element
return onCreateView(element, parent, module)
val view = onCreateView(element, parent, module)
this._currentLayoutFile = null
return view
}
protected open fun onCreateView(
@@ -195,7 +213,7 @@ open class LayoutInflaterImpl : ILayoutInflater() {
): IView {
return try {
val v = createViewInstance(widget, parent)
return ViewImpl(file, widget.qualifiedName, v)
return ViewImpl(currentLayoutFile, widget.qualifiedName, v)
} catch (err: Throwable) {
onCreateUnsupportedView("Unable to create view for widget ${widget.qualifiedName}", parent)
}
@@ -233,11 +251,11 @@ open class LayoutInflaterImpl : ILayoutInflater() {
}
private fun onCreateUnsupportedView(message: String, parent: ViewGroup): IView {
return ErrorView(file, parent.context, message)
return ErrorView(currentLayoutFile, parent.context, message)
}
private fun wrap(parent: ViewGroup): IViewGroup {
return ViewGroupImpl(file, parent.javaClass.name, parent)
return ViewGroupImpl(currentLayoutFile, parent.javaClass.name, parent)
}
private fun SourcePosition.lineCol(): String {
@@ -18,11 +18,12 @@
package com.itsaky.androidide.inflater.internal
import android.view.ViewGroup
import com.android.aaptcompiler.XmlResource
import com.itsaky.androidide.inflater.IView
import com.itsaky.androidide.inflater.IViewGroup
import java.io.File
open class ViewGroupImpl(file: File, name: String, view: ViewGroup) :
open class ViewGroupImpl(file: LayoutFile, name: String, view: ViewGroup) :
ViewImpl(file = file, name = name, view = view), IViewGroup {
protected val children = mutableListOf<IView>()
@@ -18,6 +18,7 @@
package com.itsaky.androidide.inflater.internal
import android.view.View
import com.android.aaptcompiler.XmlResource
import com.itsaky.androidide.inflater.IAttribute
import com.itsaky.androidide.inflater.IView
import com.itsaky.androidide.inflater.IViewGroup
@@ -29,7 +30,7 @@ import java.io.File
open class ViewImpl
@JvmOverloads
constructor(
internal val file: File,
internal val file: LayoutFile,
override val name: String,
override val view: View,
override val simpleName: String = name.simpleName(),
@@ -29,13 +29,14 @@ import com.itsaky.androidide.inflater.IAttribute
import com.itsaky.androidide.inflater.IAttributeAdapter
import com.itsaky.androidide.inflater.INamespace
import com.itsaky.androidide.inflater.IView
import com.itsaky.androidide.inflater.internal.LayoutFile
import com.itsaky.androidide.inflater.internal.ViewImpl
import com.itsaky.androidide.inflater.internal.utils.endParse
import com.itsaky.androidide.inflater.internal.utils.parseId
import com.itsaky.androidide.inflater.internal.utils.parseString
import com.itsaky.androidide.inflater.internal.utils.startParse
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.api.AndroidModule
import java.io.File
/**
* Attribute adapter for [View].
@@ -69,7 +70,7 @@ open class ViewAttrAdapter : IAttributeAdapter() {
"foregroundGravity" -> foregroundGravity = parseGravity(value)
"foregroundTint" -> foregroundTintList = parseColorStateList(context, value)
"foregroundTintMode" -> foregroundTintMode = parsePorterDuffMode(value)
"id" -> {}
"id" -> parseId(file.resName, value)
"minHeight" -> minimumWidth = parseDimension(context, value)
"minWidth" -> minimumHeight = parseDimension(context, value)
"padding" ->
@@ -127,7 +128,7 @@ open class ViewAttrAdapter : IAttributeAdapter() {
attribute: IAttribute,
block:
T.(
file: File,
file: LayoutFile,
context: Context,
layoutParams: ViewGroup.LayoutParams,
namespace: INamespace,
@@ -141,7 +142,7 @@ open class ViewAttrAdapter : IAttributeAdapter() {
(view.view as T).apply {
val file = (view as ViewImpl).file
val module =
ProjectManager.findModuleForFile(file) as? AndroidModule
ProjectManager.findModuleForFile(file.file) as? AndroidModule
?: throw IllegalStateException("Cannot find module for file: $file")
startParse(module)
block(file, context, layoutParams, attribute.namespace, attribute.name, attribute.value)
@@ -0,0 +1,35 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.inflater.internal.utils
internal object IDTable {
private val ids = mutableMapOf<String, MutableMap<String, Int>>()
fun newRound() {
ids.clear()
}
fun get(file: String, name: String): Int? {
return ids[file]?.get(name)
}
fun set(file: String, name: String, id: Int) {
ids.computeIfAbsent(file) { mutableMapOf() }[name] = id
}
}
@@ -440,7 +440,7 @@ fun <T> resolveResourceReference(
}
}
private fun parseResourceReference(value: String): Triple<String?, AaptResourceType, String>? {
internal fun parseResourceReference(value: String): Triple<String?, AaptResourceType, String>? {
return tryParseReference(value)?.let {
val pck = it.reference.name.pck
val type = it.reference.name.type
@@ -19,9 +19,13 @@ package com.itsaky.androidide.inflater.internal.utils
import android.graphics.PorterDuff
import android.view.View
import com.android.aaptcompiler.AaptResourceType.ID
import com.itsaky.androidide.inflater.internal.ViewImpl
fun parseId(value: String) : Int {
return 0
@JvmOverloads
fun parseId(resName: String, value: String, def: Int = 0): Int {
val name = parseResourceReference(value)?.third ?: return 0
return IDTable.get(resName, name) ?: def
}
@Suppress("DEPRECATION")
@@ -53,6 +53,22 @@ class LayoutInflaterTest {
}
}
}
@Test
fun `test inlined layout`() {
inflaterTest {
requiresActivity { activity ->
val file = layoutFile("inlined")
val parent = LinearLayout(activity)
val inflater = ILayoutInflater.newInflater()
val inflated = inflater.inflate(file, parent)
inflated.apply {
assertThat(this).isNotNull()
assertThat(this).isInstanceOf(LinearLayout::class.java)
}
}
}
}
private fun layoutFile(name: String): File {
val app = ProjectManager.app ?: throw IllegalStateException("Project is not initialized")