Descriptor clean up

Summary:
1. The base class descriptor was removed, this was given that we have chained descriptor there is no need for this anymore
2. The Base interface node descriptor no longer has a mutable list based api. You simply return a list. The mutable list based api was specifically to allow chaining and this quirk is isoltated to the chained descriptor
3. AbstractChainedDescriptor and ChainedDescriptor were merged

Reviewed By: lblasa

Differential Revision: D39496073

fbshipit-source-id: fb3ec629ec3b27f587bdbd0b323624a4bc4ebea3
This commit is contained in:
Luke De Feo
2022-09-14 05:07:51 -07:00
committed by Facebook GitHub Bot
parent f5a5e1b19d
commit 2090120cda
19 changed files with 156 additions and 195 deletions

View File

@@ -9,9 +9,8 @@ package com.facebook.flipper.plugins.uidebugger.core
import android.util.Log
import com.facebook.flipper.plugins.uidebugger.LogTag
import com.facebook.flipper.plugins.uidebugger.common.InspectableObject
import com.facebook.flipper.plugins.uidebugger.descriptors.Descriptor
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister
import com.facebook.flipper.plugins.uidebugger.descriptors.NodeDescriptor
import com.facebook.flipper.plugins.uidebugger.model.Node
class LayoutTraversal(
@@ -19,7 +18,7 @@ class LayoutTraversal(
val root: ApplicationRef
) {
internal inline fun Descriptor<*>.asAny(): Descriptor<Any> = this as Descriptor<Any>
internal inline fun NodeDescriptor<*>.asAny(): NodeDescriptor<Any> = this as NodeDescriptor<Any>
/** Traverses the native android hierarchy */
fun traverse(): List<Node> {
@@ -36,8 +35,7 @@ class LayoutTraversal(
val descriptor = descriptorRegister.descriptorForClassUnsafe(node::class.java).asAny()
val children = mutableListOf<Any>()
descriptor.getChildren(node, children)
val children = descriptor.getChildren(node)
val childrenIds = mutableListOf<String>()
@@ -61,8 +59,7 @@ class LayoutTraversal(
descriptorRegister.descriptorForClassUnsafe(activeChild.javaClass).getId(activeChild)
}
val attributes = mutableMapOf<String, InspectableObject>()
descriptor.getData(node, attributes)
val attributes = descriptor.getData(node)
result.add(
Node(

View File

@@ -34,7 +34,7 @@ class RootViewResolver {
private var viewsField: Field? = null
private var paramsField: Field? = null
class RootView(val view: View, val param: WindowManager.LayoutParams)
class RootView(val view: View, val param: WindowManager.LayoutParams?)
interface Listener {
fun onRootViewAdded(rootView: View)
fun onRootViewRemoved(rootView: View)
@@ -153,9 +153,9 @@ class RootViewResolver {
for (i in views.indices) {
val view = views[i]
// TODO FIX, len(param) is not always the same as len(views) For now just use first
val param = params[0]
// params
roots.add(RootView(view, param))
roots.add(RootView(view, null))
}
}
}

View File

@@ -1,75 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.flipper.plugins.uidebugger.descriptors
import com.facebook.flipper.plugins.uidebugger.common.InspectableObject
/**
* This class derives from Descriptor and provides a canonical implementation of ChainedDescriptor}.
*/
abstract class AbstractChainedDescriptor<T> : Descriptor<T>(), ChainedDescriptor<T> {
private var mSuper: Descriptor<T>? = null
final override fun setSuper(superDescriptor: Descriptor<T>) {
if (superDescriptor !== mSuper) {
check(mSuper == null)
mSuper = superDescriptor
}
}
fun getSuper(): Descriptor<T>? {
return mSuper
}
final override fun getActiveChild(node: T): Any? {
// ask each descriptor in the chain for an active child, if none available look up the chain
// until no more super descriptors
return onGetActiveChild(node) ?: mSuper?.getActiveChild(node)
}
/**
* A globally unique ID used to identify a node in a hierarchy. If your node does not have a
* globally unique ID it is fine to rely on [System.identityHashCode].
*/
final override fun getId(node: T): String {
return onGetId(node)
}
abstract fun onGetId(node: T): String
/**
* The name used to identify this node in the inspector. Does not need to be unique. A good
* default is to use the class name of the node.
*/
final override fun getName(node: T): String {
return onGetName(node)
}
open fun onGetActiveChild(node: T): Any? = null
abstract fun onGetName(node: T): String
/** The children this node exposes in the inspector. */
final override fun getChildren(node: T, children: MutableList<Any>) {
mSuper?.getChildren(node, children)
onGetChildren(node, children)
}
open fun onGetChildren(node: T, children: MutableList<Any>) {}
final override fun getData(node: T, builder: MutableMap<String, InspectableObject>) {
mSuper?.getData(node, builder)
onGetData(node, builder)
}
/**
* Get the data to show for this node in the sidebar of the inspector. Each key will be a have its
* own section
*/
open fun onGetData(node: T, attributeSections: MutableMap<String, InspectableObject>) {}
}

View File

@@ -11,7 +11,7 @@ import android.app.Activity
import com.facebook.flipper.plugins.uidebugger.common.InspectableObject
import com.facebook.flipper.plugins.uidebugger.stetho.FragmentCompat
object ActivityDescriptor : AbstractChainedDescriptor<Activity>() {
object ActivityDescriptor : ChainedDescriptor<Activity>() {
override fun onGetId(node: Activity): String {
return System.identityHashCode(node).toString()

View File

@@ -13,7 +13,7 @@ import com.facebook.flipper.plugins.uidebugger.LogTag
import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef
import com.facebook.flipper.plugins.uidebugger.core.RootViewResolver
object ApplicationRefDescriptor : AbstractChainedDescriptor<ApplicationRef>() {
object ApplicationRefDescriptor : ChainedDescriptor<ApplicationRef>() {
val rootsLocal = RootViewResolver()
override fun onGetActiveChild(node: ApplicationRef): Any? {

View File

@@ -10,7 +10,7 @@ package com.facebook.flipper.plugins.uidebugger.descriptors
import android.widget.Button
import com.facebook.flipper.plugins.uidebugger.common.InspectableObject
object ButtonDescriptor : AbstractChainedDescriptor<Button>() {
object ButtonDescriptor : ChainedDescriptor<Button>() {
override fun onGetId(node: Button): String {
return System.identityHashCode(node).toString()
@@ -20,5 +20,8 @@ object ButtonDescriptor : AbstractChainedDescriptor<Button>() {
return node.javaClass.simpleName
}
override fun onGetData(node: Button, attributeSections: MutableMap<String, InspectableObject>) {}
override fun onGetData(
node: Button,
attributeSections: MutableMap<SectionName, InspectableObject>
) {}
}

View File

@@ -7,36 +7,94 @@
package com.facebook.flipper.plugins.uidebugger.descriptors
import com.facebook.flipper.plugins.uidebugger.common.InspectableObject
/**
* This interface marks a Descriptor in a way that is specially understood by the register. When
* registered for a particular class 'T', a Descriptor that implements this interface will be
* chained (via ChainedDescriptor.setSuper) to the Descriptor that is registered for the super class
* of 'T'. If the super class of 'T' doesn't have a registration, then the super-super class will be
* used (and so on). This allows you to implement Descriptor for any class in an inheritance
* hierarchy without having to couple it (via direct inheritance) to the super-class' Descriptor.
* A chained descriptor is a special type of descriptor that models the inheritance hierarchy in
* native UI frameworks. With this setup you can define a descriptor for each class in the
* inheritance chain and given that we record the super class's descriptor we can automatically
* aggregate the results for each descriptor in the inheritance hierarchy in a chain.
*
* To understand why this is useful, let's say you wanted to write a Descriptor for ListView. You
* have three options:
*
* The first option is to derive directly from Descriptor and write code to describe everything
* about instances of ListView, including details that are exposed by super classes such as
* ViewGroup, View, and even Object. This isn't generally a very good choice because it would
* require a lot of duplicated code amongst many descriptor implementations.
*
* The second option is to derive your ListViewDescriptor from ViewGroupDescriptor and only
* implement code to describe how ListView differs from ViewGroup. This will result in a class
* hierarchy that is parallel to the one that you are describing, but is also not a good choice for
* two reasons (let's assume for the moment that ViewGroupDescriptor is deriving from
* ViewDescriptor). The first problem is that you will need to write code for aggregating results
* from the super-class in methods such as Descriptor.getChildren and Descriptor.getAttributes. The
* second problem is that you'd end up with a log of fragility if you ever want to implement a
* descriptor for classes that are in-between ViewGroup and ListView, e.g. AbsListView. Any
* descriptor that derived from ViewGroupDescriptor and described a class deriving from AbsListView
* would have to be modified to now derive from AbsListViewDescriptor.
*
* The third option is to implement ChainedDescriptor (e.g. by deriving from
* AbstractChainedDescriptor) which solves all of these issues for you.
* The result is that each descriptor in the inheritance chain only exports the attributes that it
* knows about but we still get all the attributes from the parent classes
*/
interface ChainedDescriptor<T> {
fun setSuper(superDescriptor: Descriptor<T>)
abstract class ChainedDescriptor<T> : NodeDescriptor<T> {
private var mSuper: ChainedDescriptor<T>? = null
fun setSuper(superDescriptor: ChainedDescriptor<T>) {
if (superDescriptor !== mSuper) {
check(mSuper == null)
mSuper = superDescriptor
}
}
fun getSuper(): ChainedDescriptor<T>? {
return mSuper
}
final override fun getActiveChild(node: T): Any? {
// ask each descriptor in the chain for an active child, if none available look up the chain
// until no more super descriptors
return onGetActiveChild(node) ?: mSuper?.getActiveChild(node)
}
/**
* A globally unique ID used to identify a node in a hierarchy. If your node does not have a
* globally unique ID it is fine to rely on [System.identityHashCode].
*/
final override fun getId(node: T): String {
return onGetId(node)
}
abstract fun onGetId(node: T): String
/**
* The name used to identify this node in the inspector. Does not need to be unique. A good
* default is to use the class name of the node.
*/
final override fun getName(node: T): String {
return onGetName(node)
}
open fun onGetActiveChild(node: T): Any? = null
abstract fun onGetName(node: T): String
/** The children this node exposes in the inspector. */
final override fun getChildren(node: T): List<Any> {
val builder = mutableListOf<Any>()
onGetChildren(node, builder)
var curDescriptor: ChainedDescriptor<T>? = mSuper
while (curDescriptor != null) {
curDescriptor.onGetChildren(node, builder)
curDescriptor = curDescriptor.mSuper
}
return builder
}
// this probably should not be chained as its unlikely you would want children to come from >1
// descriptor
open fun onGetChildren(node: T, children: MutableList<Any>) {}
final override fun getData(node: T): Map<SectionName, InspectableObject> {
val builder = mutableMapOf<String, InspectableObject>()
onGetData(node, builder)
var curDescriptor: ChainedDescriptor<T>? = mSuper
while (curDescriptor != null) {
curDescriptor.onGetData(node, builder)
curDescriptor = curDescriptor.mSuper
}
return builder
}
/**
* Get the data to show for this node in the sidebar of the inspector. Each key will be a have its
* own section
*/
open fun onGetData(node: T, attributeSections: MutableMap<SectionName, InspectableObject>) {}
}

View File

@@ -1,29 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.flipper.plugins.uidebugger.descriptors
abstract class Descriptor<T> : NodeDescriptor<T> {
var descritorRegister: DescriptorRegister? = null
fun setDescriptorRegister(descriptorMapping: DescriptorRegister) {
this.descritorRegister = descriptorMapping
}
protected fun descriptorForClass(clazz: Class<*>): Descriptor<*>? {
descritorRegister?.let { register ->
return register.descriptorForClass(clazz)
}
return null
}
protected fun descriptorForObject(obj: Any): Descriptor<*>? {
descritorRegister?.let { register ->
return register.descriptorForClass(obj::class.java)
}
return null
}
}

View File

@@ -18,7 +18,7 @@ import com.facebook.flipper.plugins.uidebugger.common.UIDebuggerException
import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef
class DescriptorRegister {
private val register: MutableMap<Class<*>, Descriptor<*>> = HashMap()
private val register: MutableMap<Class<*>, NodeDescriptor<*>> = HashMap()
companion object {
@@ -35,40 +35,37 @@ class DescriptorRegister {
mapping.register(ViewPager::class.java, ViewPagerDescriptor)
for (clazz in mapping.register.keys) {
val descriptor: Descriptor<*>? = mapping.register[clazz]
val descriptor: NodeDescriptor<*>? = mapping.register[clazz]
descriptor?.let { descriptor ->
if (descriptor is ChainedDescriptor<*>) {
val chainedDescriptor = descriptor as ChainedDescriptor<Any>
val superClass: Class<*> = clazz.getSuperclass()
val superDescriptor: Descriptor<*>? = mapping.descriptorForClass(superClass)
superDescriptor?.let { superDescriptor ->
chainedDescriptor.setSuper(superDescriptor as Descriptor<Any>)
val superDescriptor: NodeDescriptor<*>? = mapping.descriptorForClass(superClass)
// todo we should walk all the way up the superclass hierarchy?
if (superDescriptor is ChainedDescriptor<*>) {
chainedDescriptor.setSuper(superDescriptor as ChainedDescriptor<Any>)
}
}
}
}
for (descriptor in mapping.register.values) {
descriptor.setDescriptorRegister(mapping)
}
return mapping
}
}
fun <T> register(clazz: Class<T>, descriptor: Descriptor<T>) {
fun <T> register(clazz: Class<T>, descriptor: NodeDescriptor<T>) {
register[clazz] = descriptor
}
fun <T> descriptorForClass(clazz: Class<T>): Descriptor<T>? {
fun <T> descriptorForClass(clazz: Class<T>): NodeDescriptor<T>? {
var clazz: Class<*> = clazz
while (!register.containsKey(clazz)) {
clazz = clazz.superclass
}
return register[clazz] as Descriptor<T>
return register[clazz] as NodeDescriptor<T>
}
fun <T> descriptorForClassUnsafe(clazz: Class<T>): Descriptor<T> {
fun <T> descriptorForClassUnsafe(clazz: Class<T>): NodeDescriptor<T> {
return descriptorForClass(clazz)
?: throw UIDebuggerException("No descriptor found for ${clazz.name}")
}

View File

@@ -10,12 +10,15 @@ package com.facebook.flipper.plugins.uidebugger.descriptors
import com.facebook.flipper.plugins.uidebugger.common.InspectableObject
/*
Descriptors are an extension point used during traversal to extract data out of arbitary
objects in the hierachy. Descriptors can represent native view or declarative components or
Descriptors are an extension point used during traversal to extract data out of arbitrary
objects in the hierarchy. Descriptors can represent native view or declarative components or
any type of object such as an activity
Descriptors should be stateless and each descriptor should be a singleton
*/
typealias SectionName = String
interface NodeDescriptor<T> {
/**
* A globally unique ID used to identify a node in a hierarchy. If your node does not have a
@@ -30,7 +33,7 @@ interface NodeDescriptor<T> {
fun getName(node: T): String
/** The children this node exposes in the inspector. */
fun getChildren(node: T, children: MutableList<Any>)
fun getChildren(node: T): List<Any>
/**
* If you have overlapping children this indicates which child is active / on top, we will only
@@ -42,5 +45,5 @@ interface NodeDescriptor<T> {
* Get the data to show for this node in the sidebar of the inspector. The object will be shown in
* order and with a header matching the given name.
*/
fun getData(node: T, builder: MutableMap<String, InspectableObject>)
fun getData(node: T): Map<SectionName, InspectableObject>
}

View File

@@ -9,7 +9,7 @@ package com.facebook.flipper.plugins.uidebugger.descriptors
import com.facebook.flipper.plugins.uidebugger.common.InspectableObject
object ObjectDescriptor : Descriptor<Any>() {
object ObjectDescriptor : NodeDescriptor<Any> {
override fun getActiveChild(node: Any): Any? = null
@@ -21,7 +21,7 @@ object ObjectDescriptor : Descriptor<Any>() {
return node.javaClass.simpleName
}
override fun getChildren(node: Any, children: MutableList<Any>) {}
override fun getChildren(node: Any) = listOf<Any>()
override fun getData(node: Any, builder: MutableMap<String, InspectableObject>) {}
override fun getData(node: Any) = mutableMapOf<SectionName, InspectableObject>()
}

View File

@@ -10,7 +10,7 @@ package com.facebook.flipper.plugins.uidebugger.descriptors
import android.widget.TextView
import com.facebook.flipper.plugins.uidebugger.common.InspectableObject
object TextViewDescriptor : AbstractChainedDescriptor<TextView>() {
object TextViewDescriptor : ChainedDescriptor<TextView>() {
override fun onGetId(node: TextView): String {
return System.identityHashCode(node).toString()
@@ -22,6 +22,6 @@ object TextViewDescriptor : AbstractChainedDescriptor<TextView>() {
override fun onGetData(
node: TextView,
attributeSections: MutableMap<String, InspectableObject>
attributeSections: MutableMap<SectionName, InspectableObject>
) {}
}

View File

@@ -24,7 +24,7 @@ import com.facebook.flipper.plugins.uidebugger.stetho.ResourcesUtil
import java.lang.reflect.Field
@SuppressLint("DiscouragedPrivateApi")
object ViewDescriptor : AbstractChainedDescriptor<View>() {
object ViewDescriptor : ChainedDescriptor<View>() {
override fun onGetId(node: View): String {
return Integer.toBinaryString(System.identityHashCode(node))
@@ -34,7 +34,10 @@ object ViewDescriptor : AbstractChainedDescriptor<View>() {
return node.javaClass.simpleName
}
override fun onGetData(node: View, attributeSections: MutableMap<String, InspectableObject>) {
override fun onGetData(
node: View,
attributeSections: MutableMap<SectionName, InspectableObject>
) {
val positionOnScreen = IntArray(2)
node.getLocationOnScreen(positionOnScreen)

View File

@@ -18,7 +18,7 @@ import com.facebook.flipper.plugins.uidebugger.common.InspectableObject
import com.facebook.flipper.plugins.uidebugger.common.InspectableValue
import com.facebook.flipper.plugins.uidebugger.stetho.FragmentCompat
object ViewGroupDescriptor : AbstractChainedDescriptor<ViewGroup>() {
object ViewGroupDescriptor : ChainedDescriptor<ViewGroup>() {
override fun onGetId(node: ViewGroup): String {
return System.identityHashCode(node).toString()
@@ -41,7 +41,7 @@ object ViewGroupDescriptor : AbstractChainedDescriptor<ViewGroup>() {
override fun onGetData(
node: ViewGroup,
attributeSections: MutableMap<String, InspectableObject>
attributeSections: MutableMap<SectionName, InspectableObject>
) {
val viewGroupAttrs = mutableMapOf<String, Inspectable>()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {

View File

@@ -9,7 +9,7 @@ package com.facebook.flipper.plugins.uidebugger.descriptors
import androidx.viewpager.widget.ViewPager
object ViewPagerDescriptor : AbstractChainedDescriptor<ViewPager>() {
object ViewPagerDescriptor : ChainedDescriptor<ViewPager>() {
override fun onGetId(node: ViewPager): String = System.identityHashCode(node).toString()

View File

@@ -9,7 +9,7 @@ package com.facebook.flipper.plugins.uidebugger.descriptors
import android.view.Window
object WindowDescriptor : AbstractChainedDescriptor<Window>() {
object WindowDescriptor : ChainedDescriptor<Window>() {
override fun onGetId(node: Window): String {
return System.identityHashCode(node).toString()

View File

@@ -9,9 +9,8 @@ package com.facebook.flipper.plugins.uidebugger.observers
import android.util.Log
import com.facebook.flipper.plugins.uidebugger.LogTag
import com.facebook.flipper.plugins.uidebugger.common.InspectableObject
import com.facebook.flipper.plugins.uidebugger.descriptors.Descriptor
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister
import com.facebook.flipper.plugins.uidebugger.descriptors.NodeDescriptor
import com.facebook.flipper.plugins.uidebugger.model.Node
/**
@@ -24,7 +23,7 @@ class PartialLayoutTraversal(
private val treeObserverfactory: TreeObserverFactory,
) {
internal fun Descriptor<*>.asAny(): Descriptor<Any> = this as Descriptor<Any>
internal fun NodeDescriptor<*>.asAny(): NodeDescriptor<Any> = this as NodeDescriptor<Any>
fun traverse(root: Any): Pair<MutableList<Node>, List<Any>> {
@@ -47,8 +46,7 @@ class PartialLayoutTraversal(
val descriptor = descriptorRegister.descriptorForClassUnsafe(node::class.java).asAny()
val children = mutableListOf<Any>()
descriptor.getChildren(node, children)
val children = descriptor.getChildren(node)
val childrenIds = mutableListOf<String>()
val activeChild = descriptor.getActiveChild(node)
@@ -71,8 +69,7 @@ class PartialLayoutTraversal(
descriptorRegister.descriptorForClassUnsafe(activeChild.javaClass).getId(activeChild)
}
val attributes = mutableMapOf<String, InspectableObject>()
descriptor.getData(node, attributes)
val attributes = descriptor.getData(node)
visited.add(
Node(

View File

@@ -67,6 +67,9 @@ abstract class TreeObserver<T> {
children[childKey]!!.cleanUpRecursive()
}
}
// send
Log.d(LogTag, "For Observer ${this.type} Sending ${visitedNodes.size} ")
context.treeObserverManager.send(
SubtreeUpdate(type, visitedNodes, start, System.currentTimeMillis()))
}