Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include "RemoteObjectPropertyModel.h"
8#include "RemoteObject.h"
9#include "RemoteProcess.h"
10
11namespace Inspector {
12
13RemoteObjectPropertyModel::RemoteObjectPropertyModel(RemoteObject& object)
14 : m_object(object)
15{
16}
17
18int RemoteObjectPropertyModel::row_count(const GUI::ModelIndex& index) const
19{
20 Function<int(JsonValue const&)> do_count = [&](JsonValue const& value) {
21 if (value.is_array())
22 return value.as_array().size();
23 else if (value.is_object())
24 return value.as_object().size();
25 return (size_t)0;
26 };
27
28 if (index.is_valid()) {
29 auto* path = static_cast<JsonPath const*>(index.internal_data());
30 return do_count(path->resolve(m_object.json));
31 } else {
32 return do_count(m_object.json);
33 }
34}
35
36DeprecatedString RemoteObjectPropertyModel::column_name(int column) const
37{
38 switch (column) {
39 case Column::Name:
40 return "Name";
41 case Column::Value:
42 return "Value";
43 }
44 VERIFY_NOT_REACHED();
45}
46
47GUI::Variant RemoteObjectPropertyModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const
48{
49 auto* path = static_cast<JsonPath const*>(index.internal_data());
50 if (!path)
51 return {};
52
53 if (role == GUI::ModelRole::Display) {
54 switch (index.column()) {
55 case Column::Name:
56 return path->last().to_deprecated_string();
57 case Column::Value: {
58 auto data = path->resolve(m_object.json);
59 if (data.is_array())
60 return DeprecatedString::formatted("<Array with {} element{}", data.as_array().size(), data.as_array().size() == 1 ? ">" : "s>");
61 if (data.is_object())
62 return DeprecatedString::formatted("<Object with {} entr{}", data.as_object().size(), data.as_object().size() == 1 ? "y>" : "ies>");
63 return data;
64 }
65 }
66 }
67 return {};
68}
69
70void RemoteObjectPropertyModel::set_data(const GUI::ModelIndex& index, const GUI::Variant& new_value)
71{
72 if (!index.is_valid())
73 return;
74
75 auto* path = static_cast<JsonPath const*>(index.internal_data());
76 if (path->size() != 1)
77 return;
78
79 FlatPtr address = m_object.address;
80 RemoteProcess::the().set_property(address, path->first().to_deprecated_string(), new_value.to_deprecated_string());
81 did_update();
82}
83
84GUI::ModelIndex RemoteObjectPropertyModel::index(int row, int column, const GUI::ModelIndex& parent) const
85{
86 auto const& parent_path = parent.is_valid() ? *static_cast<JsonPath const*>(parent.internal_data()) : JsonPath {};
87
88 auto nth_child = [&](int n, JsonValue const& value) -> JsonPath const* {
89 auto path = make<JsonPath>();
90 path->extend(parent_path);
91 int row_index = n;
92 if (value.is_object()) {
93 DeprecatedString property_name;
94 auto& object = value.as_object();
95 object.for_each_member([&](auto& name, auto&) {
96 if (row_index > 0) {
97 --row_index;
98 } else if (row_index == 0) {
99 property_name = name;
100 --row_index;
101 }
102 });
103 if (property_name.is_null())
104 return nullptr;
105
106 path->append({ property_name });
107 m_paths.append(move(path));
108 } else if (value.is_array()) {
109 path->append(JsonPathElement { (size_t)n });
110 m_paths.append(move(path));
111 } else {
112 return nullptr;
113 }
114 return m_paths.last();
115 };
116
117 if (!parent.is_valid()) {
118 if (m_object.json.is_empty())
119 return {};
120 }
121
122 auto index_path = cached_path_at(row, parent_path);
123
124 if (!index_path)
125 index_path = nth_child(row, parent_path.resolve(m_object.json));
126
127 if (!index_path)
128 return {};
129
130 return create_index(row, column, index_path);
131}
132
133GUI::ModelIndex RemoteObjectPropertyModel::parent_index(const GUI::ModelIndex& index) const
134{
135 if (!index.is_valid())
136 return index;
137
138 auto path = *static_cast<JsonPath const*>(index.internal_data());
139 if (path.is_empty())
140 return {};
141
142 path.take_last();
143 if (path.is_empty())
144 return {};
145
146 auto* cpath = find_cached_path(path);
147 if (cpath) {
148 int index_in_parent = 0;
149 if (cpath->last().kind() == JsonPathElement::Kind::Index)
150 index_in_parent = cpath->last().index();
151 else if (cpath->last().kind() == JsonPathElement::Kind::Key) {
152 auto path_copy = path;
153 auto last = path_copy.take_last();
154 bool found = false;
155 path_copy.resolve(m_object.json).as_object().for_each_member([&](auto& name, auto&) {
156 if (!found) {
157 if (last.key() == name)
158 found = true;
159 else
160 index_in_parent++;
161 }
162 });
163 }
164 return create_index(index_in_parent, 0, cpath);
165 }
166
167 dbgln("No cached path found for path {}", path.to_deprecated_string());
168 return {};
169}
170
171JsonPath const* RemoteObjectPropertyModel::cached_path_at(int n, Vector<JsonPathElement> const& prefix) const
172{
173 // FIXME: ModelIndex wants a void*, so we have to keep these
174 // indices alive, but allocating a new path every time
175 // we're asked for an index is silly, so we have to look for existing ones first.
176 JsonPath const* index_path = nullptr;
177 int row_index = n;
178 for (auto& path : m_paths) {
179 if (path->size() != prefix.size() + 1)
180 continue;
181
182 for (size_t i = 0; i < prefix.size(); ++i) {
183 if ((*path)[i] != prefix[i])
184 goto do_continue;
185 }
186
187 if (row_index == 0) {
188 index_path = path;
189 break;
190 }
191 --row_index;
192 do_continue:;
193 }
194
195 return index_path;
196};
197
198JsonPath const* RemoteObjectPropertyModel::find_cached_path(Vector<JsonPathElement> const& path) const
199{
200 for (auto& cpath : m_paths) {
201 if (cpath->size() != path.size())
202 continue;
203
204 for (size_t i = 0; i < cpath->size(); ++i) {
205 if ((*cpath)[i] != path[i])
206 goto do_continue;
207 }
208
209 return cpath;
210 do_continue:;
211 }
212
213 return nullptr;
214}
215
216}