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 assert(!segments_.empty());
232 Node n = segments_.back();
233 segments_.pop_back();
234 return n;
235 }
236
TakeLastNode()237 inline RefUri::Node RefUri::TakeLastNode()
238 {
239 assert(!segments_.empty());
240 Node n = segments_.front();
241 segments_.erase(segments_.begin());
242 return n;
243 }
244
ReferencedName()245 inline BASE_NS::string RefUri::ReferencedName() const
246 {
247 return segments_.empty() ? "" : segments_.front().name;
248 }
249
StartsFromRoot()250 inline bool RefUri::StartsFromRoot() const
251 {
252 return startFromRoot_;
253 }
254
SetStartsFromRoot(bool value)255 inline void RefUri::SetStartsFromRoot(bool value)
256 {
257 startFromRoot_ = value;
258 }
259
BaseObjectUid()260 inline InstanceId RefUri::BaseObjectUid() const
261 {
262 return baseUid_;
263 }
264
SetBaseObjectUid(const InstanceId & uid)265 inline void RefUri::SetBaseObjectUid(const InstanceId& uid)
266 {
267 baseUid_ = uid;
268 }
269
PushObjectSegment(BASE_NS::string name)270 inline void RefUri::PushObjectSegment(BASE_NS::string name)
271 {
272 segments_.push_back(Node { BASE_NS::move(name), Node::OBJECT });
273 }
274
PushPropertySegment(BASE_NS::string name)275 inline void RefUri::PushPropertySegment(BASE_NS::string name)
276 {
277 segments_.push_back(Node { BASE_NS::move(name), Node::PROPERTY });
278 }
279
PushObjectContextSegment()280 inline void RefUri::PushObjectContextSegment()
281 {
282 segments_.push_back(Node { "@Context", Node::SPECIAL });
283 }
284
ToString()285 inline BASE_NS::string RefUri::ToString() const
286 {
287 BASE_NS::string res = "ref:";
288 if (baseUid_.IsValid()) {
289 res += baseUid_.ToString();
290 }
291 if (startFromRoot_) {
292 res += SEPARATOR_CHAR;
293 }
294 if (segments_.empty()) {
295 res += SEPARATOR_CHAR;
296 }
297 for (auto it = segments_.rbegin(); it != segments_.rend(); ++it) {
298 res += SEPARATOR_CHAR;
299 if (it->type == Node::PROPERTY) {
300 res += PROPERTY_CHAR;
301 }
302 // don't escape special names
303 res += it->type != Node::SPECIAL ? EscapeName(it->name) : it->name;
304 }
305 return res;
306 }
307
308 inline bool RefUri::operator==(const RefUri& uri) const
309 {
310 // always unequal for non-valid uri
311 if (!isValid_ || !uri.isValid_) {
312 return false;
313 }
314 if (baseUid_ != uri.baseUid_) {
315 return false;
316 }
317 if (startFromRoot_ != uri.startFromRoot_) {
318 return false;
319 }
320 if (interpretAbsolute_ != uri.interpretAbsolute_) {
321 return false;
322 }
323 if (segments_.size() != uri.segments_.size()) {
324 return false;
325 }
326 for (auto it1 = segments_.begin(), it2 = uri.segments_.begin(); it1 != segments_.end(); ++it1, ++it2) {
327 if (it1->name != it2->name || it1->type != it2->type) {
328 return false;
329 }
330 }
331 return true;
332 }
333
334 inline bool RefUri::operator!=(const RefUri& uri) const
335 {
336 return !(*this == uri);
337 }
338
ParentUri()339 inline const RefUri& RefUri::ParentUri()
340 {
341 static const RefUri uri { "ref:/.." };
342 return uri;
343 }
344
SelfUri()345 inline const RefUri& RefUri::SelfUri()
346 {
347 static const RefUri uri { "ref:/" };
348 return uri;
349 }
350
ContextUri()351 inline const RefUri& RefUri::ContextUri()
352 {
353 static const RefUri uri { "ref:/@Context" };
354 return uri;
355 }
356
AddSegment(BASE_NS::string seg)357 inline bool RefUri::AddSegment(BASE_NS::string seg)
358 {
359 if (seg == ".") {
360 // ignore .
361 } else if (seg == ".." && !segments_.empty()) {
362 // remove last segment if we have one
363 segments_.pop_back();
364 } else if (seg[0] == PROPERTY_CHAR) {
365 seg.erase(0, 1);
366 if (seg.empty()) {
367 return false;
368 }
369 PushPropertySegment(UnEscapeName(seg));
370 } else if (seg.substr(0, 8) == "@Context") { // 8: str length
371 PushObjectContextSegment();
372 } else if (seg.substr(0, 8) == "@Theme") { // 8: str length
373 PushObjectContextSegment();
374 PushPropertySegment("Theme");
375 } else {
376 PushObjectSegment(UnEscapeName(seg));
377 }
378 return true;
379 }
380
ParseSegment(BASE_NS::string_view & path)381 inline bool RefUri::ParseSegment(BASE_NS::string_view& path)
382 {
383 size_t i = 0;
384 for (; i < path.size() && path[i] != SEPARATOR_CHAR; ++i) {
385 if (path[i] == ESCAPE_CHAR) {
386 ++i;
387 }
388 }
389 if (i == 0 || !AddSegment(BASE_NS::string(path.substr(0, i)))) {
390 return false;
391 }
392
393 path.remove_prefix(i + 1);
394 return true;
395 }
396
ParsePath(BASE_NS::string_view path)397 inline bool RefUri::ParsePath(BASE_NS::string_view path)
398 {
399 if (path.empty() || path[0] != SEPARATOR_CHAR) {
400 return false;
401 }
402 path.remove_prefix(1);
403 if (path.empty()) {
404 return true;
405 }
406 // see if we have double separator meaning root object
407 if (path[0] == SEPARATOR_CHAR) {
408 startFromRoot_ = true;
409 path.remove_prefix(1);
410 }
411 while (!path.empty()) {
412 if (!ParseSegment(path)) {
413 return false;
414 }
415 }
416 // all good, reverse segments and we are done
417 BASE_NS::vector<Node> rev { segments_.rbegin(), segments_.rend() };
418 segments_ = BASE_NS::move(rev);
419 return true;
420 }
421
ParseUid(BASE_NS::string_view & path)422 inline bool RefUri::ParseUid(BASE_NS::string_view& path)
423 {
424 static constexpr size_t uidSize = 36;
425 if (path.size() < uidSize) {
426 return false;
427 }
428 baseUid_ = BASE_NS::StringToUid(path.substr(0, uidSize));
429 path.remove_prefix(uidSize);
430 return true;
431 }
432
Parse(BASE_NS::string_view uri)433 inline bool RefUri::Parse(BASE_NS::string_view uri)
434 {
435 // we check the header and size must be at least 5 (at least / after the header)
436 if (uri.size() < 5 || uri.substr(0, 4) != "ref:") { // 5: size, 4: size
437 return false;
438 }
439 uri.remove_prefix(4); // 4: size
440 // see if it is path or valid uid
441 if (uri[0] != SEPARATOR_CHAR && !ParseUid(uri)) {
442 return false;
443 }
444 if (!uri.empty() && uri[0] == SEPARATOR_CHAR) {
445 return ParsePath(uri);
446 }
447 return uri.empty();
448 }
449
EscapeName(BASE_NS::string_view str)450 inline BASE_NS::string RefUri::EscapeName(BASE_NS::string_view str)
451 {
452 BASE_NS::string res { str };
453 for (size_t i = 0; i != res.size(); ++i) {
454 if (ESCAPED_CHARS.find(res[i]) != BASE_NS::string_view::npos) {
455 res.insert(i, &ESCAPE_CHAR, 1);
456 ++i;
457 }
458 }
459 return res;
460 }
461
UnEscapeName(BASE_NS::string_view str)462 inline BASE_NS::string RefUri::UnEscapeName(BASE_NS::string_view str)
463 {
464 BASE_NS::string res { str };
465 for (size_t i = 0; i < res.size(); ++i) {
466 if (res[i] == ESCAPE_CHAR) {
467 res.erase(i, 1);
468 }
469 }
470 return res;
471 }
472
473 META_TYPE(RefUri);
474
475 META_END_NAMESPACE()
476
477 #endif
478