• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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