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