1 /*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #ifndef META_BASE_REF_URI_H
17 #define META_BASE_REF_URI_H
18
19 #include <cassert>
20
21 #include <base/containers/string.h>
22 #include <base/containers/vector.h>
23
24 #include <meta/base/ids.h>
25 #include <meta/base/meta_types.h>
26 #include <meta/base/namespace.h>
27
META_BEGIN_NAMESPACE()28 META_BEGIN_NAMESPACE()
29
30 /**
31 * @brief The RefUri class represents a URI that is used to reference objects and properties in
32 * an object hierarchy.
33 *
34 * Examples of valid uris:
35 * - "ref:/$Name": Current object's "Name" property
36 * - "ref:/../MyList/$ItemCount": Parent object's child with name "MyList", and the property "ItemCount"
37 * from that object
38 * - "ref:/MyList/$CurrentItem/$ItemName": MyList object's CurrentItem property (which contains an object),
39 * whose ItemName property is accessed
40 * - "ref:1234-5678-9abc-deff/$Name": absolute path to object, object's "Name" property
41 * - "ref:1234-5678-9abc-deff//$Name": absolute path to object, object's hierarchy's root object's "Name"
42 * property (note extra / for "root")
43 * - "ref://$Name": Current object's hierarchy's root object's "Name" property
44 * - "ref:/../$Name": Current object's parent's "Name" property
45 * - "ref:/../AnotherChild/$Name": Current object's sibling AnotherChild's "Name" property
46 * - "ref:/./$Name": Current object's "Name" property
47 * - "ref:/@Context/$Theme": Current object's context's property named Theme
48 * - "ref:/@Theme": Current widget's theme
49 * - "ref:1234-5678-9abc-deff/@Context/$Name": absolute path to object, object's context's "Name" property
50 * - "ref://": Current object's hierarchy's root object
51 * - "ref:/": Current object
52 */
53 class RefUri {
54 public:
55 /** Character identifying properties */
56 constexpr static char PROPERTY_CHAR = '$';
57 /** Separator character between names */
58 constexpr static char SEPARATOR_CHAR = '/';
59 /** Escape character */
60 constexpr static char ESCAPE_CHAR = '\\';
61 /** Characters that are automatically escaped */
62 constexpr static BASE_NS::string_view ESCAPED_CHARS = "/@$\\";
63
64 struct Node {
65 BASE_NS::string name;
66 enum Type { OBJECT, PROPERTY, SPECIAL } type {};
67 };
68
69 RefUri();
70 /**
71 * @brief Construct RefUri by parsing uri.
72 */
73 explicit RefUri(BASE_NS::string_view uri);
74 /**
75 * @brief Construct RefUri using baseObject as starting point and parse path.
76 */
77 RefUri(const InstanceId& baseObject, BASE_NS::string_view path = "/");
78
79 /**
80 * @brief Check if this is valid RefUri. This means the associated uri is well-formed,
81 * it might or might not point to existing object or property.
82 */
83 bool IsValid() const;
84 /**
85 * @brief Check if this is empty RefUri (i.e. default constructed).
86 */
87 bool IsEmpty() const;
88 /**
89 * @brief Convert RefUri to string presentation of the associated uri.
90 */
91 BASE_NS::string ToString() const;
92 /**
93 * @brief Check if the last segment in the associated uri is property.
94 */
95 bool ReferencesProperty() const;
96 /**
97 * @brief Check if the last segment in the associated uri is object.
98 */
99 bool ReferencesObject() const;
100 /**
101 * @brief UID of the base object if present.
102 */
103 InstanceId BaseObjectUid() const;
104 /**
105 * @brief Sets the base object UID, can be used to replace the existing UID to re-root the path.
106 */
107 void SetBaseObjectUid(const InstanceId& id);
108 /**
109 * @brief Check if the associated uri starts from the root object (i.e. it has // in the beginning).
110 */
111 bool StartsFromRoot() const;
112 /**
113 * @brief Enable/Disable starting from root object. This can be used to make the RefUri relative to the root.
114 */
115 void SetStartsFromRoot(bool value);
116 /**
117 * @brief Returns relative uri to the base object (i.e. effectively drops off the base object).
118 */
119 RefUri RelativeUri() const;
120 /**
121 * @brief Removes and returns the first segment in the path.
122 */
123 Node TakeFirstNode();
124 /**
125 * @brief Removes and returns the last segment in the path.
126 */
127 Node TakeLastNode();
128 /**
129 * @brief Name of the last segment in the uri.
130 * Returns empty string for "ref:/.." and "ref:/[/]".
131 */
132 BASE_NS::string ReferencedName() const;
133
134 /**
135 * @brief Add object name as first segment (the first pushed segment will be the last one in the end).
136 */
137 void PushObjectSegment(BASE_NS::string name);
138 /**
139 * @brief Add property name as first segment (the first pushed segment will be the last one in the end).
140 */
141 void PushPropertySegment(BASE_NS::string name);
142 /**
143 * @brief Add special object context as first segment (the first pushed segment will be the last one in the end).
144 */
145 void PushObjectContextSegment();
146
147 bool operator==(const RefUri& uri) const;
148 bool operator!=(const RefUri& uri) const;
149
150 /** Shorthand for RefUri("ref:/..") */
151 static const RefUri& ParentUri();
152 /** Shorthand for RefUri("ref:/") */
153 static const RefUri& SelfUri();
154 /** Shorthand for RefUri("ref:/@Context") */
155 static const RefUri& ContextUri();
156
157 /** Return true if ref:/base/$test refers to the property, otherwise (default) it refers to the value of the
158 * property */
159 bool GetAbsoluteInterpretation() const
160 {
161 return interpretAbsolute_;
162 }
163 void SetAbsoluteInterpretation(bool v)
164 {
165 interpretAbsolute_ = v;
166 }
167
168 private:
169 bool Parse(BASE_NS::string_view uri);
170 bool ParsePath(BASE_NS::string_view path);
171 bool ParseSegment(BASE_NS::string_view& path);
172 bool ParseUid(BASE_NS::string_view& path);
173 bool AddSegment(BASE_NS::string seg);
174
175 static BASE_NS::string EscapeName(BASE_NS::string_view str);
176 static BASE_NS::string UnEscapeName(BASE_NS::string_view str);
177
178 private:
179 bool isValid_ {};
180 InstanceId baseUid_;
181 bool startFromRoot_ {};
182 BASE_NS::vector<Node> segments_;
183 // this is context specific, not in the string format
184 bool interpretAbsolute_ {};
185 };
186
RefUri()187 inline RefUri::RefUri() : isValid_ { true } {}
188
RefUri(BASE_NS::string_view uri)189 inline RefUri::RefUri(BASE_NS::string_view uri)
190 {
191 isValid_ = Parse(uri);
192 }
193
RefUri(const InstanceId & baseObject,BASE_NS::string_view path)194 inline RefUri::RefUri(const InstanceId& baseObject, BASE_NS::string_view path) : baseUid_(baseObject)
195 {
196 isValid_ = ParsePath(path);
197 }
198
IsValid()199 inline bool RefUri::IsValid() const
200 {
201 return isValid_;
202 }
203
IsEmpty()204 inline bool RefUri::IsEmpty() const
205 {
206 RefUri empty {};
207 // ignore the context specific flag for empty check
208 empty.SetAbsoluteInterpretation(GetAbsoluteInterpretation());
209 return *this == empty;
210 }
211
ReferencesProperty()212 inline bool RefUri::ReferencesProperty() const
213 {
214 return IsValid() && !segments_.empty() && segments_[0].type == Node::PROPERTY;
215 }
216
ReferencesObject()217 inline bool RefUri::ReferencesObject() const
218 {
219 return IsValid() && !ReferencesProperty();
220 }
221
RelativeUri()222 inline RefUri RefUri::RelativeUri() const
223 {
224 RefUri copy { *this };
225 copy.SetBaseObjectUid({});
226 return copy;
227 }
228
TakeFirstNode()229 inline RefUri::Node RefUri::TakeFirstNode()
230 {
231 Node n = segments_.back();
232 segments_.pop_back();
233 return n;
234 }
235
TakeLastNode()236 inline RefUri::Node RefUri::TakeLastNode()
237 {
238 Node n = segments_.front();
239 segments_.erase(segments_.begin());
240 return n;
241 }
242
ReferencedName()243 inline BASE_NS::string RefUri::ReferencedName() const
244 {
245 return segments_.empty() ? "" : segments_.front().name;
246 }
247
StartsFromRoot()248 inline bool RefUri::StartsFromRoot() const
249 {
250 return startFromRoot_;
251 }
252
SetStartsFromRoot(bool value)253 inline void RefUri::SetStartsFromRoot(bool value)
254 {
255 startFromRoot_ = value;
256 }
257
BaseObjectUid()258 inline InstanceId RefUri::BaseObjectUid() const
259 {
260 return baseUid_;
261 }
262
SetBaseObjectUid(const InstanceId & uid)263 inline void RefUri::SetBaseObjectUid(const InstanceId& uid)
264 {
265 baseUid_ = uid;
266 }
267
PushObjectSegment(BASE_NS::string name)268 inline void RefUri::PushObjectSegment(BASE_NS::string name)
269 {
270 segments_.push_back(Node { BASE_NS::move(name), Node::OBJECT });
271 }
272
PushPropertySegment(BASE_NS::string name)273 inline void RefUri::PushPropertySegment(BASE_NS::string name)
274 {
275 segments_.push_back(Node { BASE_NS::move(name), Node::PROPERTY });
276 }
277
PushObjectContextSegment()278 inline void RefUri::PushObjectContextSegment()
279 {
280 segments_.push_back(Node { "@Context", Node::SPECIAL });
281 }
282
ToString()283 inline BASE_NS::string RefUri::ToString() const
284 {
285 BASE_NS::string res = "ref:";
286 if (baseUid_.IsValid()) {
287 res += baseUid_.ToString();
288 }
289 if (startFromRoot_) {
290 res += SEPARATOR_CHAR;
291 }
292 if (segments_.empty()) {
293 res += SEPARATOR_CHAR;
294 }
295 for (auto it = segments_.rbegin(); it != segments_.rend(); ++it) {
296 res += SEPARATOR_CHAR;
297 if (it->type == Node::PROPERTY) {
298 res += PROPERTY_CHAR;
299 }
300 // don't escape special names
301 res += it->type != Node::SPECIAL ? EscapeName(it->name) : it->name;
302 }
303 return res;
304 }
305
306 inline bool RefUri::operator==(const RefUri& uri) const
307 {
308 // always unequal for non-valid uri
309 if (!isValid_ || !uri.isValid_) {
310 return false;
311 }
312 if (baseUid_ != uri.baseUid_) {
313 return false;
314 }
315 if (startFromRoot_ != uri.startFromRoot_) {
316 return false;
317 }
318 if (interpretAbsolute_ != uri.interpretAbsolute_) {
319 return false;
320 }
321 if (segments_.size() != uri.segments_.size()) {
322 return false;
323 }
324 for (auto it1 = segments_.begin(), it2 = uri.segments_.begin(); it1 != segments_.end(); ++it1, ++it2) {
325 if (it1->name != it2->name || it1->type != it2->type) {
326 return false;
327 }
328 }
329 return true;
330 }
331
332 inline bool RefUri::operator!=(const RefUri& uri) const
333 {
334 return !(*this == uri);
335 }
336
ParentUri()337 inline const RefUri& RefUri::ParentUri()
338 {
339 static const RefUri uri { "ref:/.." };
340 return uri;
341 }
342
SelfUri()343 inline const RefUri& RefUri::SelfUri()
344 {
345 static const RefUri uri { "ref:/" };
346 return uri;
347 }
348
ContextUri()349 inline const RefUri& RefUri::ContextUri()
350 {
351 static const RefUri uri { "ref:/@Context" };
352 return uri;
353 }
354
AddSegment(BASE_NS::string seg)355 inline bool RefUri::AddSegment(BASE_NS::string seg)
356 {
357 if (seg == ".") {
358 // ignore .
359 } else if (seg == ".." && !segments_.empty()) {
360 // remove last segment if we have one
361 segments_.pop_back();
362 } else if (seg[0] == PROPERTY_CHAR) {
363 seg.erase(0, 1);
364 if (seg.empty()) {
365 return false;
366 }
367 PushPropertySegment(UnEscapeName(seg));
368 } else if (seg.substr(0, 8) == "@Context") { // 8 count index
369 PushObjectContextSegment();
370 } else if (seg.substr(0, 8) == "@Theme") { // 8 count index
371 PushObjectContextSegment();
372 PushPropertySegment("Theme");
373 } else {
374 PushObjectSegment(UnEscapeName(seg));
375 }
376 return true;
377 }
378
ParseSegment(BASE_NS::string_view & path)379 inline bool RefUri::ParseSegment(BASE_NS::string_view& path)
380 {
381 size_t i = 0;
382 for (; i < path.size() && path[i] != SEPARATOR_CHAR; ++i) {
383 if (path[i] == ESCAPE_CHAR) {
384 ++i;
385 }
386 }
387 if (i == 0 || !AddSegment(BASE_NS::string(path.substr(0, i)))) {
388 return false;
389 }
390
391 path.remove_prefix(i + 1);
392 return true;
393 }
394
ParsePath(BASE_NS::string_view path)395 inline bool RefUri::ParsePath(BASE_NS::string_view path)
396 {
397 if (path.empty() || path[0] != SEPARATOR_CHAR) {
398 return false;
399 }
400 path.remove_prefix(1);
401 if (path.empty()) {
402 return true;
403 }
404 // see if we have double separator meaning root object
405 if (path[0] == SEPARATOR_CHAR) {
406 startFromRoot_ = true;
407 path.remove_prefix(1);
408 }
409 while (!path.empty()) {
410 if (!ParseSegment(path)) {
411 return false;
412 }
413 }
414 // all good, reverse segments and we are done
415 BASE_NS::vector<Node> rev { segments_.rbegin(), segments_.rend() };
416 segments_ = BASE_NS::move(rev);
417 return true;
418 }
419
ParseUid(BASE_NS::string_view & path)420 inline bool RefUri::ParseUid(BASE_NS::string_view& path)
421 {
422 static constexpr size_t uidSize = 36;
423 if (path.size() < uidSize) {
424 return false;
425 }
426 baseUid_ = BASE_NS::StringToUid(path.substr(0, uidSize));
427 path.remove_prefix(uidSize);
428 return true;
429 }
430
Parse(BASE_NS::string_view uri)431 inline bool RefUri::Parse(BASE_NS::string_view uri)
432 {
433 // we check the header and size must be at least 5 (at least / after the header)
434 if (uri.size() < 5 || uri.substr(0, 4) != "ref:") { // 5 4 size
435 return false;
436 }
437 uri.remove_prefix(4); // 4 size
438 // see if it is path or valid uid
439 if (uri[0] != SEPARATOR_CHAR && !ParseUid(uri)) {
440 return false;
441 }
442 if (!uri.empty() && uri[0] == SEPARATOR_CHAR) {
443 return ParsePath(uri);
444 }
445 return uri.empty();
446 }
447
EscapeName(BASE_NS::string_view str)448 inline BASE_NS::string RefUri::EscapeName(BASE_NS::string_view str)
449 {
450 BASE_NS::string res { str };
451 for (size_t i = 0; i != res.size(); ++i) {
452 if (ESCAPED_CHARS.find(res[i]) != BASE_NS::string_view::npos) {
453 res.insert(i, &ESCAPE_CHAR, 1);
454 ++i;
455 }
456 }
457 return res;
458 }
459
UnEscapeName(BASE_NS::string_view str)460 inline BASE_NS::string RefUri::UnEscapeName(BASE_NS::string_view str)
461 {
462 BASE_NS::string res { str };
463 for (size_t i = 0; i < res.size(); ++i) {
464 if (res[i] == ESCAPE_CHAR) {
465 res.erase(i, 1);
466 }
467 }
468 return res;
469 }
470
471 META_TYPE(RefUri);
472
473 META_END_NAMESPACE()
474
475 #endif
476