package libs import ( "encoding/json" "errors" "fmt" "strconv" ) // FieldMeta 定义选择器字段的掩码位和默认值 // Bit 为该字段对应的掩码位,Default 为默认值(nil 表示无默认值) type FieldMeta struct { Bit uint32 Default interface{} } // Selector 表示一个 UiSelector 的构造器 // 用于构建 Android UI 元素的查询条件 type Selector struct { // 存放已设置的字段及其值 fields map[string]interface{} // 掩码值(通过设置/删除字段自动维护) mask uint32 // 子/兄弟关系列表,元素为 "child" 或 "sibling" childOrSibling []string // 对应的嵌套 Selector 列表,长度与 childOrSibling 相同 childOrSiblingSelector []*Selector } // fieldDefs 定义所有支持的选择器字段及其元数据(与 Python 版 uiautomator2 一致) 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 创建一个新的 Selector,可选传入初始字段 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 对给定字段和值进行类型校验 // 布尔字段要求值为 bool 类型,整数字段要求值为整数类型 func validateValue(key string, val interface{}) error { meta, ok := fieldDefs[key] if !ok { return fmt.Errorf("不支持的字段: %s", key) } if meta.Default == false { // 布尔字段校验 _, ok := val.(bool) if !ok { return fmt.Errorf("%s 必须是 bool 类型", key) } return nil } // 整数字段校验 switch d := meta.Default.(type) { case int: switch val.(type) { case int, int8, int16, int32, int64: return nil case uint, uint8, uint16, uint32, uint64: return nil default: return fmt.Errorf("%s 必须是整数类型, 默认值=%v", key, d) } default: // 其他字段无特殊类型要求 return nil } } // Set 设置字段值并更新掩码 // 如果字段名非法或类型不匹配则返回错误 func (s *Selector) Set(key string, val interface{}) error { if _, ok := fieldDefs[key]; !ok { return fmt.Errorf("不支持的字段: %s", key) } if err := validateValue(key, val); err != nil { return err } s.fields[key] = val s.mask = s.mask | fieldDefs[key].Bit return nil } // Delete 删除字段并更新掩码 // 删除不存在的字段不会报错(幂等操作) func (s *Selector) Delete(key string) error { if _, ok := fieldDefs[key]; !ok { return fmt.Errorf("不支持的字段: %s", key) } if _, present := s.fields[key]; present { delete(s.fields, key) s.mask = s.mask & ^fieldDefs[key].Bit } return nil } // Mask 返回当前掩码值(只读) func (s *Selector) Mask() uint32 { return s.mask } // 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 添加一个兄弟元素选择器 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 更新最后一个子/兄弟选择器的 instance 字段 // 如果没有子/兄弟选择器,则更新根选择器的 instance 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 { clone.fields[k] = v } for _, c := range s.childOrSiblingSelector { clone.childOrSiblingSelector = append(clone.childOrSiblingSelector, c.Clone()) } return clone } // ToMap 将选择器序列化为 map,便于 JSON 编码或 RPC 调用 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 返回选择器的 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 := range fieldDefs { if v, ok := data[k]; ok { if err := root.Set(k, v); err != nil { return nil, err } } } // 恢复掩码值 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) } } // 恢复子/兄弟关系列表 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 在指定路径上更新字段 // 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("路径索引越界") } node = node.childOrSiblingSelector[idx] } for k, v := range updates { if err := node.Set(k, v); err != nil { return err } } return nil } // String 实现 fmt.Stringer 接口,输出可读的选择器表示 func (s *Selector) String() string { m := s.ToMap() if _, ok := m["childOrSibling"]; !ok { delete(m, "childOrSibling") delete(m, "childOrSiblingSelector") } b, _ := json.Marshal(m) return "Selector " + string(b) } // Example 使用示例(仅供参考,非单元测试) func Example() { // 初始化根选择器 root := MustNew(map[string]interface{}{ "className": "android.widget.LinearLayout", }) // 添加子元素选择器 root.Child(map[string]interface{}{ "text": "下一步", "instance": 0, }) // 更新最后一个子选择器的 instance _ = root.UpdateInstance(2) // 深拷贝 cpy := root.Clone() // 序列化为 JSON j, _ := cpy.ToJSON() fmt.Println(string(j)) } // SimpleTests 简单测试函数(建议迁移到 _test.go 文件中使用 testing 包) func SimpleTests() { // 设置与删除字段 s := MustNew(map[string]interface{}{"text": "hello"}) fmt.Println("设置后掩码:", strconv.FormatUint(uint64(s.Mask()), 10)) _ = s.Delete("text") fmt.Println("删除后掩码:", strconv.FormatUint(uint64(s.Mask()), 10)) // 类型校验:bool 字段传入非 bool 值应报错 _, err := New(map[string]interface{}{"checkable": "yes"}) fmt.Println("非法 bool 值报错:", err != nil) // 深拷贝独立性验证 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("原始 childOrSibling:", s2.childOrSibling[0], "克隆 childOrSibling:", c.childOrSibling[0]) }