Files
go-mobile-uiautomator/libs/selector.go
2025-11-12 18:40:14 +08:00

354 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package libs
import (
"encoding/json"
"errors"
"fmt"
"strconv"
)
// FieldMeta 保存每个字段对应的 mask 位和默认值nil 表示无默认值)
type FieldMeta struct {
Bit uint32
Default interface{}
}
// Selector 表示一个 UiSelector 的构造器
type Selector struct {
// 存放字段及其值(只包含显式设置的字段)
fields map[string]interface{}
// mask 值(通过设置/删除字段自动维护)
mask uint32
// childOrSibling 顺序列表,元素为 "child" 或 "sibling"
childOrSibling []string
// 对应的嵌套 Selector 列表,长度与 childOrSibling 相同
childOrSiblingSelector []*Selector
}
// 字段元数据(与 Python 版本一致)
var fieldDefs = map[string]FieldMeta{
"text": {Bit: 0x01, Default: nil},
"textContains": {Bit: 0x02, Default: nil},
"textMatches": {Bit: 0x04, Default: nil},
"textStartsWith": {Bit: 0x08, Default: nil},
"className": {Bit: 0x10, Default: nil},
"classNameMatches": {Bit: 0x20, Default: nil},
"description": {Bit: 0x40, Default: nil},
"descriptionContains": {Bit: 0x80, Default: nil},
"descriptionMatches": {Bit: 0x0100, Default: nil},
"descriptionStartsWith": {Bit: 0x0200, Default: nil},
"checkable": {Bit: 0x0400, Default: false},
"checked": {Bit: 0x0800, Default: false},
"clickable": {Bit: 0x1000, Default: false},
"longClickable": {Bit: 0x2000, Default: false},
"scrollable": {Bit: 0x4000, Default: false},
"enabled": {Bit: 0x8000, Default: false},
"focusable": {Bit: 0x010000, Default: false},
"focused": {Bit: 0x020000, Default: false},
"selected": {Bit: 0x040000, Default: false},
"packageName": {Bit: 0x080000, Default: nil},
"packageNameMatches": {Bit: 0x100000, Default: nil},
"resourceId": {Bit: 0x200000, Default: nil},
"resourceIdMatches": {Bit: 0x400000, Default: nil},
"index": {Bit: 0x800000, Default: 0},
"instance": {Bit: 0x01000000, Default: 0},
}
// New creates a Selector and可选传入初始字段
func New(initial map[string]interface{}) (*Selector, error) {
s := &Selector{
fields: make(map[string]interface{}),
childOrSibling: []string{},
childOrSiblingSelector: []*Selector{},
mask: 0,
}
for k, v := range initial {
if err := s.Set(k, v); err != nil {
return nil, err
}
}
return s, nil
}
// MustNew 跟 New 相同,但在错误时 panic便于简洁示例
func MustNew(initial map[string]interface{}) *Selector {
s, err := New(initial)
if err != nil {
panic(err)
}
return s
}
// validateValue 对给定字段和值做类型校验(布尔字段与整数字段)
func validateValue(key string, val interface{}) error {
meta, ok := fieldDefs[key]
if !ok {
return fmt.Errorf("field %s is not allowed", key)
}
if meta.Default == false {
// 期望 bool
_, ok := val.(bool)
if !ok {
return fmt.Errorf("%s must be bool", key)
}
return nil
}
// 对整数字段Default 为 int 类型)要求 int
switch d := meta.Default.(type) {
case int:
// 支持 int 和可被转为 int 的数值(如 int64
switch val.(type) {
case int, int8, int16, int32, int64:
return nil
case uint, uint8, uint16, uint32, uint64:
return nil
default:
return fmt.Errorf("%s must be integer type, default=%v", key, d)
}
default:
// 其它字段没有特别要求
return nil
}
}
// Set 设置字段并更新 mask若字段非法或类型不对则返回错误
func (s *Selector) Set(key string, val interface{}) error {
if _, ok := fieldDefs[key]; !ok {
return fmt.Errorf("%s is not allowed", key)
}
if err := validateValue(key, val); err != nil {
return err
}
s.fields[key] = val
s.mask = s.mask | fieldDefs[key].Bit
return nil
}
// Delete 删除字段并更新 mask幂等删除不存在字段不报错
func (s *Selector) Delete(key string) error {
if _, ok := fieldDefs[key]; !ok {
return fmt.Errorf("%s is not allowed", key)
}
if _, present := s.fields[key]; present {
delete(s.fields, key)
s.mask = s.mask & ^fieldDefs[key].Bit
}
return nil
}
// Mask 返回当前 mask只读
func (s *Selector) Mask() uint32 {
return s.mask
}
// Child 在末尾添加 child
func (s *Selector) Child(initial map[string]interface{}) (*Selector, error) {
child, err := New(initial)
if err != nil {
return nil, err
}
s.childOrSibling = append(s.childOrSibling, "child")
s.childOrSiblingSelector = append(s.childOrSiblingSelector, child)
return s, nil
}
// Sibling 在末尾添加 sibling
func (s *Selector) Sibling(initial map[string]interface{}) (*Selector, error) {
child, err := New(initial)
if err != nil {
return nil, err
}
s.childOrSibling = append(s.childOrSibling, "sibling")
s.childOrSiblingSelector = append(s.childOrSiblingSelector, child)
return s, nil
}
// UpdateInstance 更新最后一个 childOrSiblingSelector 的 instance 字段(或根 selector
func (s *Selector) UpdateInstance(i int) error {
n := len(s.childOrSiblingSelector)
if n > 0 {
return s.childOrSiblingSelector[n-1].Set("instance", i)
}
return s.Set("instance", i)
}
// Clone 深拷贝 Selector包括子/兄弟
func (s *Selector) Clone() *Selector {
clone := &Selector{
fields: make(map[string]interface{}, len(s.fields)),
mask: s.mask,
childOrSibling: append([]string{}, s.childOrSibling...),
childOrSiblingSelector: make([]*Selector, 0, len(s.childOrSiblingSelector)),
}
for k, v := range s.fields {
// 简单深拷贝对于常见类型string,bool,int直接赋值即可。
// 若值为复杂结构,调用方应使用 ToMap/ToJSON 再 Parse 得到深拷贝。
clone.fields[k] = v
}
for _, c := range s.childOrSiblingSelector {
clone.childOrSiblingSelector = append(clone.childOrSiblingSelector, c.Clone())
}
return clone
}
// ToMap 序列化为 map便于 RPC 调用或 JSON 编码
func (s *Selector) ToMap() map[string]interface{} {
out := make(map[string]interface{}, len(s.fields)+3)
for k, v := range s.fields {
out[k] = v
}
out["mask"] = s.mask
if len(s.childOrSibling) > 0 {
out["childOrSibling"] = append([]string{}, s.childOrSibling...)
cs := make([]map[string]interface{}, 0, len(s.childOrSiblingSelector))
for _, c := range s.childOrSiblingSelector {
cs = append(cs, c.ToMap())
}
out["childOrSiblingSelector"] = cs
}
return out
}
// ToJSON 返回 ToMap 的 JSON 编码
func (s *Selector) ToJSON() ([]byte, error) {
return json.Marshal(s.ToMap())
}
// FromMap 从 map 恢复 Selector简单实现忽略非法字段
func FromMap(data map[string]interface{}) (*Selector, error) {
// 提取根字段
root := &Selector{
fields: make(map[string]interface{}),
childOrSibling: []string{},
childOrSiblingSelector: []*Selector{},
mask: 0,
}
// 读取已知字段
for k, meta := range fieldDefs {
if v, ok := data[k]; ok {
// 尝试 Set 以做类型校验并设置 mask
if err := root.Set(k, v); err != nil {
return nil, err
}
// 注意Set 已经更新了 mask
_ = meta
}
}
// 恢复 mask如果提供了 mask并且为数值
if m, ok := data["mask"]; ok {
switch mv := m.(type) {
case float64:
root.mask = uint32(mv)
case uint32:
root.mask = mv
case int:
root.mask = uint32(mv)
case int64:
root.mask = uint32(mv)
default:
// 忽略不能解析的 mask
}
}
// 恢复 childOrSibling 列表和对应 selector期望 childOrSiblingSelector 为 []map[string]interface{}
if cs, ok := data["childOrSibling"]; ok {
if arr, ok := cs.([]interface{}); ok {
for _, e := range arr {
if sname, ok := e.(string); ok {
root.childOrSibling = append(root.childOrSibling, sname)
}
}
}
}
if css, ok := data["childOrSiblingSelector"]; ok {
if arr, ok := css.([]interface{}); ok {
for _, item := range arr {
if m, ok := item.(map[string]interface{}); ok {
c, err := FromMap(m)
if err != nil {
return nil, err
}
root.childOrSiblingSelector = append(root.childOrSiblingSelector, c)
}
}
}
}
return root, nil
}
// UpdateAtPath 在指定路径child 索引链)上更新字段
// path: 逐级索引,例如 [0,2] 表示 childOrSiblingSelector[0].childOrSiblingSelector[2]
func (s *Selector) UpdateAtPath(path []int, updates map[string]interface{}) error {
node := s
for _, idx := range path {
if idx < 0 || idx >= len(node.childOrSiblingSelector) {
return errors.New("path out of range")
}
node = node.childOrSiblingSelector[idx]
}
for k, v := range updates {
if err := node.Set(k, v); err != nil {
return err
}
}
return nil
}
// String 实现 fmt.Stringer输出友好可读的 Selector 表示(类似 Python 的 __str__
func (s *Selector) String() string {
m := s.ToMap()
// 删除空的 childOrSibling 字段以保持简洁
if _, ok := m["childOrSibling"]; !ok {
delete(m, "childOrSibling")
delete(m, "childOrSiblingSelector")
}
b, _ := json.Marshal(m)
return "Selector " + string(b)
}
// Example 用法示例(不是正式测试,仅供快速手动运行)
func Example() {
// 初始化根 selector
root := MustNew(map[string]interface{}{
"className": "android.widget.LinearLayout",
})
// 添加 child
root.Child(map[string]interface{}{
"text": "下一步",
"instance": 0,
})
// 更新最后一个 child 的 instance
_ = root.UpdateInstance(2)
// 深拷贝
cpy := root.Clone()
// 序列化到 JSON
j, _ := cpy.ToJSON()
fmt.Println(string(j))
}
// 简单测试函数(你可在 package 内使用 testing 包将其改写成真正的单元测试)
func SimpleTests() {
// set & delete
s := MustNew(map[string]interface{}{"text": "hello"})
fmt.Println("mask after set:", strconv.FormatUint(uint64(s.Mask()), 10))
_ = s.Delete("text")
fmt.Println("mask after delete:", strconv.FormatUint(uint64(s.Mask()), 10))
// bool 类型校验
_, err := New(map[string]interface{}{"checkable": "yes"})
fmt.Println("expected error for bad bool:", err != nil)
// clone 深拷贝检查
s2 := MustNew(map[string]interface{}{"text": "a"})
s2.Child(map[string]interface{}{"text": "b", "instance": 1})
c := s2.Clone()
c.childOrSibling[0] = "sibling"
fmt.Println("original childOrSibling:", s2.childOrSibling[0], "clone childOrSibling:", c.childOrSibling[0])
}