metadata_model.py revision b556bc47068d816cb319a5d0e2f6841b007b38f2
1#!/usr/bin/python 2 3# 4# Copyright (C) 2012 The Android Open Source Project 5# 6# Licensed under the Apache License, Version 2.0 (the "License"); 7# you may not use this file except in compliance with the License. 8# You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, 14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15# See the License for the specific language governing permissions and 16# limitations under the License. 17# 18 19""" 20A set of classes (models) each closely representing an XML node in the 21metadata_properties.xml file. 22 23 Node: Base class for most nodes. 24 Entry: A node corresponding to <entry> elements. 25 Clone: A node corresponding to <clone> elements. 26 Kind: A node corresponding to <dynamic>, <static>, <controls> elements. 27 InnerNamespace: A node corresponding to a <namespace> nested under a <kind>. 28 OuterNamespace: A node corresponding to a <namespace> with <kind> children. 29 Section: A node corresponding to a <section> element. 30 Enum: A class corresponding an <enum> element within an <entry> 31 Value: A class corresponding to a <value> element within an Enum 32 Metadata: Root node that also provides tree construction functionality. 33 Tag: A node corresponding to a top level <tag> element. 34""" 35 36import sys 37from collections import OrderedDict 38 39class Node(object): 40 """ 41 Base class for most nodes that are part of the Metadata graph. 42 43 Attributes (Read-Only): 44 parent: An edge to a parent Node. 45 name: A string describing the name, usually but not always the 'name' 46 attribute of the corresponding XML node. 47 """ 48 49 def __init__(self): 50 self._parent = None 51 self._name = None 52 53 @property 54 def parent(self): 55 return self._parent 56 57 @property 58 def name(self): 59 return self._name 60 61 def find_all(self, pred): 62 """ 63 Find all descendants that match the predicate. 64 65 Args: 66 pred: a predicate function that acts as a filter for a Node 67 68 Yields: 69 A sequence of all descendants for which pred(node) is true, 70 in a pre-order visit order. 71 """ 72 if pred(self): 73 yield self 74 75 if self._get_children() is None: 76 return 77 78 for i in self._get_children(): 79 for j in i.find_all(pred): 80 yield j 81 82 83 def find_first(self, pred): 84 """ 85 Find the first descendant that matches the predicate. 86 87 Args: 88 pred: a predicate function that acts as a filter for a Node 89 90 Returns: 91 The first Node from find_all(pred), or None if there were no results. 92 """ 93 for i in self.find_all(pred): 94 return i 95 96 return None 97 98 def find_parent_first(self, pred): 99 """ 100 Find the first ancestor that matches the predicate. 101 102 Args: 103 pred: A predicate function that acts as a filter for a Node 104 105 Returns: 106 The first ancestor closest to the node for which pred(node) is true. 107 """ 108 for i in self.find_parents(pred): 109 return i 110 111 return None 112 113 def find_parents(self, pred): 114 """ 115 Find all ancestors that match the predicate. 116 117 Args: 118 pred: A predicate function that acts as a filter for a Node 119 120 Yields: 121 A sequence of all ancestors (closest to furthest) from the node, 122 where pred(node) is true. 123 """ 124 parent = self.parent 125 126 while parent is not None: 127 if pred(parent): 128 yield parent 129 parent = parent.parent 130 131 def sort_children(self): 132 """ 133 Sorts the immediate children in-place. 134 """ 135 self._sort_by_name(self._children) 136 137 def _sort_by_name(self, what): 138 what.sort(key=lambda x: x.name) 139 140 def _get_name(self): 141 return lambda x: x.name 142 143 # Iterate over all children nodes. None when node doesn't support children. 144 def _get_children(self): 145 return (i for i in self._children) 146 147 def _children_name_map_matching(self, match=lambda x: True): 148 d = {} 149 for i in _get_children(): 150 if match(i): 151 d[i.name] = i 152 return d 153 154 @staticmethod 155 def _dictionary_by_name(values): 156 d = OrderedDict() 157 for i in values: 158 d[i.name] = i 159 160 return d 161 162 def validate_tree(self): 163 """ 164 Sanity check the tree recursively, ensuring for a node n, all children's 165 parents are also n. 166 167 Returns: 168 True if validation succeeds, False otherwise. 169 """ 170 succ = True 171 children = self._get_children() 172 if children is None: 173 return True 174 175 for child in self._get_children(): 176 if child.parent != self: 177 print >> sys.stderr, ("ERROR: Node '%s' doesn't match the parent" + \ 178 "(expected: %s, actual %s)") \ 179 %(child, self, child.parent) 180 succ = False 181 182 succ = child.validate_tree() and succ 183 184 return succ 185 186 def __str__(self): 187 return "<%s name='%s'>" %(self.__class__, self.name) 188 189class Metadata(Node): 190 """ 191 A node corresponding to a <metadata> entry. 192 193 Attributes (Read-Only): 194 parent: An edge to the parent Node. This is always None for Metadata. 195 outer_namespaces: A sequence of immediate OuterNamespace children. 196 tags: A sequence of all Tag instances available in the graph. 197 """ 198 199 def __init__(self): 200 """ 201 Initialize with no children. Use insert_* functions and then 202 construct_graph() to build up the Metadata from some source. 203 """ 204 205# Private 206 self._entries = [] 207 # kind => { name => entry } 208 self._entry_map = { 'static': {}, 'dynamic': {}, 'controls': {} } 209 self._entries_ordered = [] # list of ordered Entry/Clone instances 210 self._clones = [] 211 212# Public (Read Only) 213 self._parent = None 214 self._outer_namespaces = None 215 self._tags = [] 216 217 @property 218 def outer_namespaces(self): 219 if self._outer_namespaces is None: 220 return None 221 else: 222 return (i for i in self._outer_namespaces) 223 224 @property 225 def tags(self): 226 return (i for i in self._tags) 227 228 def _get_properties(self): 229 230 for i in self._entries: 231 yield i 232 233 for i in self._clones: 234 yield i 235 236 def insert_tag(self, tag, description=""): 237 """ 238 Insert a tag into the metadata. 239 240 Args: 241 tag: A string identifier for a tag. 242 description: A string description for a tag. 243 244 Example: 245 metadata.insert_tag("BC", "Backwards Compatibility for old API") 246 247 Remarks: 248 Subsequent calls to insert_tag with the same tag are safe (they will 249 be ignored). 250 """ 251 tag_ids = [tg.name for tg in self.tags if tg.name == tag] 252 if not tag_ids: 253 self._tags.append(Tag(tag, self, description)) 254 255 def insert_entry(self, entry): 256 """ 257 Insert an entry into the metadata. 258 259 Args: 260 entry: A key-value dictionary describing an entry. Refer to 261 Entry#__init__ for the keys required/optional. 262 263 Remarks: 264 Subsequent calls to insert_entry with the same entry+kind name are safe 265 (they will be ignored). 266 """ 267 e = Entry(**entry) 268 self._entries.append(e) 269 self._entry_map[e.kind][e.name] = e 270 self._entries_ordered.append(e) 271 272 def insert_clone(self, clone): 273 """ 274 Insert a clone into the metadata. 275 276 Args: 277 clone: A key-value dictionary describing a clone. Refer to 278 Clone#__init__ for the keys required/optional. 279 280 Remarks: 281 Subsequent calls to insert_clone with the same clone+kind name are safe 282 (they will be ignored). Also the target entry need not be inserted 283 ahead of the clone entry. 284 """ 285 entry_name = clone['name'] 286 # figure out corresponding entry later. allow clone insert, entry insert 287 entry = None 288 c = Clone(entry, **clone) 289 self._entry_map[c.kind][c.name] = c 290 self._clones.append(c) 291 self._entries_ordered.append(c) 292 293 def prune_clones(self): 294 """ 295 Remove all clones that don't point to an existing entry. 296 297 Remarks: 298 This should be called after all insert_entry/insert_clone calls have 299 finished. 300 """ 301 remove_list = [] 302 for p in self._clones: 303 if p.entry is None: 304 remove_list.append(p) 305 306 for p in remove_list: 307 308 # remove from parent's entries list 309 if p.parent is not None: 310 p.parent._entries.remove(p) 311 # remove from parents' _leafs list 312 for ancestor in p.find_parents(lambda x: not isinstance(x, MetadataSet)): 313 ancestor._leafs.remove(p) 314 315 # remove from global list 316 self._clones.remove(p) 317 self._entry_map[p.kind].pop(p.name) 318 self._entries_ordered.remove(p) 319 320 321 # After all entries/clones are inserted, 322 # invoke this to generate the parent/child node graph all these objects 323 def construct_graph(self): 324 """ 325 Generate the graph recursively, after which all Entry nodes will be 326 accessible recursively by crawling through the outer_namespaces sequence. 327 328 Remarks: 329 This is safe to be called multiple times at any time. It should be done at 330 least once or there will be no graph. 331 """ 332 self.validate_tree() 333 self._construct_tags() 334 self.validate_tree() 335 self._construct_clones() 336 self.validate_tree() 337 self._construct_outer_namespaces() 338 self.validate_tree() 339 340 def _construct_tags(self): 341 tag_dict = self._dictionary_by_name(self.tags) 342 for p in self._get_properties(): 343 p._tags = [] 344 for tag_id in p._tag_ids: 345 tag = tag_dict.get(tag_id) 346 347 if tag not in p._tags: 348 p._tags.append(tag) 349 350 if p not in tag.entries: 351 tag._entries.append(p) 352 353 def _construct_clones(self): 354 for p in self._clones: 355 target_kind = p.target_kind 356 target_entry = self._entry_map[target_kind].get(p.name) 357 p._entry = target_entry 358 359 # should not throw if we pass validation 360 # but can happen when importing obsolete CSV entries 361 if target_entry is None: 362 print >> sys.stderr, ("WARNING: Clone entry '%s' target kind '%s'" + \ 363 " has no corresponding entry") \ 364 %(p.name, p.target_kind) 365 366 def _construct_outer_namespaces(self): 367 368 if self._outer_namespaces is None: #the first time this runs 369 self._outer_namespaces = [] 370 371 root = self._dictionary_by_name(self._outer_namespaces) 372 for ons_name, ons in root.iteritems(): 373 ons._leafs = [] 374 375 for p in self._entries_ordered: 376 ons_name = p.get_outer_namespace() 377 ons = root.get(ons_name, OuterNamespace(ons_name, self)) 378 root[ons_name] = ons 379 380 if p not in ons._leafs: 381 ons._leafs.append(p) 382 383 for ons_name, ons in root.iteritems(): 384 385 ons.validate_tree() 386 387 self._construct_sections(ons) 388 389 if ons not in self._outer_namespaces: 390 self._outer_namespaces.append(ons) 391 392 ons.validate_tree() 393 394 def _construct_sections(self, outer_namespace): 395 396 sections_dict = self._dictionary_by_name(outer_namespace.sections) 397 for sec_name, sec in sections_dict.iteritems(): 398 sec._leafs = [] 399 sec.validate_tree() 400 401 for p in outer_namespace._leafs: 402 does_exist = sections_dict.get(p.get_section()) 403 404 sec = sections_dict.get(p.get_section(), \ 405 Section(p.get_section(), outer_namespace)) 406 sections_dict[p.get_section()] = sec 407 408 sec.validate_tree() 409 410 if p not in sec._leafs: 411 sec._leafs.append(p) 412 413 for sec_name, sec in sections_dict.iteritems(): 414 415 if not sec.validate_tree(): 416 print >> sys.stderr, ("ERROR: Failed to validate tree in " + \ 417 "construct_sections (start), with section = '%s'")\ 418 %(sec) 419 420 self._construct_kinds(sec) 421 422 if sec not in outer_namespace.sections: 423 outer_namespace._sections.append(sec) 424 425 if not sec.validate_tree(): 426 print >> sys.stderr, ("ERROR: Failed to validate tree in " + \ 427 "construct_sections (end), with section = '%s'") \ 428 %(sec) 429 430 # 'controls', 'static' 'dynamic'. etc 431 def _construct_kinds(self, section): 432 433 kinds_dict = self._dictionary_by_name(section.kinds) 434 for name, kind in kinds_dict.iteritems(): 435 kind._leafs = [] 436 section.validate_tree() 437 438 for p in section._leafs: 439 kind = kinds_dict.get(p.kind, Kind(p.kind, section)) 440 kinds_dict[p.kind] = kind 441 section.validate_tree() 442 443 if p not in kind._leafs: 444 kind._leafs.append(p) 445 446 if len(kinds_dict) > 3: 447 sec = section 448 if sec is not None: 449 sec_name = sec.name 450 else: 451 sec_name = "Unknown" 452 453 print >> sys.stderr, ("ERROR: Kind '%s' has too many children(%d) " + \ 454 "in section '%s'") %(name, len(kc), sec_name) 455 456 457 for name, kind in kinds_dict.iteritems(): 458 459 kind.validate_tree() 460 self._construct_inner_namespaces(kind) 461 kind.validate_tree() 462 self._construct_entries(kind) 463 kind.validate_tree() 464 465 if kind not in section.kinds: 466 section._kinds.append(kind) 467 468 if not section.validate_tree(): 469 print >> sys.stderr, ("ERROR: Failed to validate tree in " + \ 470 "construct_kinds, with kind = '%s'") %(kind) 471 472 if not kind.validate_tree(): 473 print >> sys.stderr, ("ERROR: Failed to validate tree in " + \ 474 "construct_kinds, with kind = '%s'") %(kind) 475 476 def _construct_inner_namespaces(self, parent, depth=0): 477 #parent is InnerNamespace or Kind 478 ins_dict = self._dictionary_by_name(parent.namespaces) 479 for name, ins in ins_dict.iteritems(): 480 ins._leafs = [] 481 482 for p in parent._leafs: 483 ins_list = p.get_inner_namespace_list() 484 485 if len(ins_list) > depth: 486 ins_str = ins_list[depth] 487 ins = ins_dict.get(ins_str, InnerNamespace(ins_str, parent)) 488 ins_dict[ins_str] = ins 489 490 if p not in ins._leafs: 491 ins._leafs.append(p) 492 493 for name, ins in ins_dict.iteritems(): 494 ins.validate_tree() 495 # construct children INS 496 self._construct_inner_namespaces(ins, depth + 1) 497 ins.validate_tree() 498 # construct children entries 499 self._construct_entries(ins, depth + 1) 500 501 if ins not in parent.namespaces: 502 parent._namespaces.append(ins) 503 504 if not ins.validate_tree(): 505 print >> sys.stderr, ("ERROR: Failed to validate tree in " + \ 506 "construct_inner_namespaces, with ins = '%s'") \ 507 %(ins) 508 509 # doesnt construct the entries, so much as links them 510 def _construct_entries(self, parent, depth=0): 511 #parent is InnerNamespace or Kind 512 entry_dict = self._dictionary_by_name(parent.entries) 513 for p in parent._leafs: 514 ins_list = p.get_inner_namespace_list() 515 516 if len(ins_list) == depth: 517 entry = entry_dict.get(p.name, p) 518 entry_dict[p.name] = entry 519 520 for name, entry in entry_dict.iteritems(): 521 522 old_parent = entry.parent 523 entry._parent = parent 524 525 if entry not in parent.entries: 526 parent._entries.append(entry) 527 528 if old_parent is not None and old_parent != parent: 529 print >> sys.stderr, ("ERROR: Parent changed from '%s' to '%s' for " + \ 530 "entry '%s'") \ 531 %(old_parent.name, parent.name, entry.name) 532 533 def _get_children(self): 534 if self.outer_namespaces is not None: 535 for i in self.outer_namespaces: 536 yield i 537 538 if self.tags is not None: 539 for i in self.tags: 540 yield i 541 542class Tag(Node): 543 """ 544 A tag Node corresponding to a top-level <tag> element. 545 546 Attributes (Read-Only): 547 name: alias for id 548 id: The name of the tag, e.g. for <tag id="BC"/> id = 'BC' 549 description: The description of the tag, the contents of the <tag> element. 550 parent: An edge to the parent, which is always the Metadata root node. 551 entries: A sequence of edges to entries/clones that are using this Tag. 552 """ 553 def __init__(self, name, parent, description=""): 554 self._name = name # 'id' attribute in XML 555 self._id = name 556 self._description = description 557 self._parent = parent 558 559 # all entries that have this tag, including clones 560 self._entries = [] # filled in by Metadata#construct_tags 561 562 @property 563 def id(self): 564 return self._id 565 566 @property 567 def description(self): 568 return self._description 569 570 @property 571 def entries(self): 572 return (i for i in self._entries) 573 574 def _get_children(self): 575 return None 576 577class OuterNamespace(Node): 578 """ 579 A node corresponding to a <namespace> element under <metadata> 580 581 Attributes (Read-Only): 582 name: The name attribute of the <namespace name="foo"> element. 583 parent: An edge to the parent, which is always the Metadata root node. 584 sections: A sequence of Section children. 585 """ 586 def __init__(self, name, parent, sections=[]): 587 self._name = name 588 self._parent = parent # MetadataSet 589 self._sections = sections[:] 590 self._leafs = [] 591 592 self._children = self._sections 593 594 @property 595 def sections(self): 596 return (i for i in self._sections) 597 598class Section(Node): 599 """ 600 A node corresponding to a <section> element under <namespace> 601 602 Attributes (Read-Only): 603 name: The name attribute of the <section name="foo"> element. 604 parent: An edge to the parent, which is always an OuterNamespace instance. 605 description: A string description of the section, or None. 606 kinds: A sequence of Kind children. 607 """ 608 def __init__(self, name, parent, description=None, kinds=[]): 609 self._name = name 610 self._parent = parent 611 self._description = description 612 self._kinds = kinds[:] 613 614 self._leafs = [] 615 616 617 @property 618 def description(self): 619 return self._description 620 621 @property 622 def kinds(self): 623 return (i for i in self._kinds) 624 625 def sort_children(self): 626 self.validate_tree() 627 # order is always controls,static,dynamic 628 find_child = lambda x: [i for i in self._get_children() if i.name == x] 629 new_lst = find_child('controls') \ 630 + find_child('static') \ 631 + find_child('dynamic') 632 self._kinds = new_lst 633 self.validate_tree() 634 635 def _get_children(self): 636 return (i for i in self.kinds) 637 638class Kind(Node): 639 """ 640 A node corresponding to one of: <static>,<dynamic>,<controls> under a 641 <section> element. 642 643 Attributes (Read-Only): 644 name: A string which is one of 'static', 'dynamic, or 'controls'. 645 parent: An edge to the parent, which is always a Section instance. 646 namespaces: A sequence of InnerNamespace children. 647 entries: A sequence of Entry/Clone children. 648 merged_entries: A sequence of MergedEntry virtual nodes from entries 649 """ 650 def __init__(self, name, parent): 651 self._name = name 652 self._parent = parent 653 self._namespaces = [] 654 self._entries = [] 655 656 self._leafs = [] 657 658 @property 659 def namespaces(self): 660 return self._namespaces 661 662 @property 663 def entries(self): 664 return self._entries 665 666 @property 667 def merged_entries(self): 668 for i in self.entries: 669 yield i.merge() 670 671 def sort_children(self): 672 self._namespaces.sort(key=self._get_name()) 673 self._entries.sort(key=self._get_name()) 674 675 def _get_children(self): 676 for i in self.namespaces: 677 yield i 678 for i in self.entries: 679 yield i 680 681class InnerNamespace(Node): 682 """ 683 A node corresponding to a <namespace> which is an ancestor of a Kind. 684 These namespaces may have other namespaces recursively, or entries as leafs. 685 686 Attributes (Read-Only): 687 name: Name attribute from the element, e.g. <namespace name="foo"> -> 'foo' 688 parent: An edge to the parent, which is an InnerNamespace or a Kind. 689 namespaces: A sequence of InnerNamespace children. 690 entries: A sequence of Entry/Clone children. 691 merged_entries: A sequence of MergedEntry virtual nodes from entries 692 """ 693 def __init__(self, name, parent): 694 self._name = name 695 self._parent = parent 696 self._namespaces = [] 697 self._entries = [] 698 self._leafs = [] 699 700 @property 701 def namespaces(self): 702 return self._namespaces 703 704 @property 705 def entries(self): 706 return self._entries 707 708 @property 709 def merged_entries(self): 710 for i in self.entries: 711 yield i.merge() 712 713 def sort_children(self): 714 self._namespaces.sort(key=self._get_name()) 715 self._entries.sort(key=self._get_name()) 716 717 def _get_children(self): 718 for i in self.namespaces: 719 yield i 720 for i in self.entries: 721 yield i 722 723class EnumValue(Node): 724 """ 725 A class corresponding to a <value> element within an <enum> within an <entry>. 726 727 Attributes (Read-Only): 728 name: A string, e.g. 'ON' or 'OFF' 729 id: An optional numeric string, e.g. '0' or '0xFF' 730 optional: A boolean 731 notes: A string describing the notes, or None. 732 parent: An edge to the parent, always an Enum instance. 733 """ 734 def __init__(self, name, parent, id=None, optional=False, notes=None): 735 self._name = name # str, e.g. 'ON' or 'OFF' 736 self._id = id # int, e.g. '0' 737 self._optional = optional # bool 738 self._notes = notes # None or str 739 self._parent = parent 740 741 @property 742 def id(self): 743 return self._id 744 745 @property 746 def optional(self): 747 return self._optional 748 749 @property 750 def notes(self): 751 return self._notes 752 753 def _get_children(self): 754 return None 755 756class Enum(Node): 757 """ 758 A class corresponding to an <enum> element within an <entry>. 759 760 Attributes (Read-Only): 761 parent: An edge to the parent, always an Entry instance. 762 values: A sequence of EnumValue children. 763 """ 764 def __init__(self, parent, values, ids={}, optionals=[], notes={}): 765 self._values = \ 766 [ EnumValue(val, self, ids.get(val), val in optionals, notes.get(val)) \ 767 for val in values ] 768 769 self._parent = parent 770 self._name = None 771 772 @property 773 def values(self): 774 return (i for i in self._values) 775 776 def _get_children(self): 777 return (i for i in self._values) 778 779class Entry(Node): 780 """ 781 A node corresponding to an <entry> element. 782 783 Attributes (Read-Only): 784 parent: An edge to the parent node, which is an InnerNamespace or Kind. 785 name: The fully qualified name string, e.g. 'android.shading.mode' 786 name_short: The name attribute from <entry name="mode">, e.g. mode 787 type: The type attribute from <entry type="bar"> 788 kind: A string ('static', 'dynamic', 'controls') corresponding to the 789 ancestor Kind#name 790 container: The container attribute from <entry container="array">, or None. 791 container_sizes: A sequence of size strings or None if container is None. 792 enum: An Enum instance if the enum attribute is true, None otherwise. 793 tuple_values: A sequence of strings describing the tuple values, 794 None if container is not 'tuple'. 795 description: A string description, or None. 796 range: A string range, or None. 797 units: A string units, or None. 798 tags: A sequence of Tag nodes associated with this Entry. 799 type_notes: A string describing notes for the type, or None. 800 801 Remarks: 802 Subclass Clone can be used interchangeable with an Entry, 803 for when we don't care about the underlying type. 804 805 parent and tags edges are invalid until after Metadata#construct_graph 806 has been invoked. 807 """ 808 def __init__(self, **kwargs): 809 """ 810 Instantiate a new Entry node. 811 812 Args: 813 name: A string with the fully qualified name, e.g. 'android.shading.mode' 814 type: A string describing the type, e.g. 'int32' 815 kind: A string describing the kind, e.g. 'static' 816 817 Args (if container): 818 container: A string describing the container, e.g. 'array' or 'tuple' 819 container_sizes: A list of string sizes if a container, or None otherwise 820 821 Args (if container is 'tuple'): 822 tuple_values: A list of tuple values, e.g. ['width', 'height'] 823 824 Args (if the 'enum' attribute is true): 825 enum: A boolean, True if this is an enum, False otherwise 826 enum_values: A list of value strings, e.g. ['ON', 'OFF'] 827 enum_optionals: A list of optional enum values, e.g. ['OFF'] 828 enum_notes: A dictionary of value->notes strings. 829 enum_ids: A dictionary of value->id strings. 830 831 Args (optional): 832 description: A string with a description of the entry. 833 range: A string with the range of the values of the entry, e.g. '>= 0' 834 units: A string with the units of the values, e.g. 'inches' 835 notes: A string with the notes for the entry 836 tag_ids: A list of tag ID strings, e.g. ['BC', 'V1'] 837 type_notes: A string with the notes for the type 838 """ 839 840 if kwargs.get('type') is None: 841 print >> sys.stderr, "ERROR: Missing type for entry '%s' kind '%s'" \ 842 %(kwargs.get('name'), kwargs.get('kind')) 843 844 # Attributes are Read-Only, but edges may be mutated by 845 # Metadata, particularly during construct_graph 846 847 self._name = kwargs['name'] 848 self._type = kwargs['type'] 849 self._kind = kwargs['kind'] # static, dynamic, or controls 850 851 self._init_common(**kwargs) 852 853 @property 854 def type(self): 855 return self._type 856 857 @property 858 def kind(self): 859 return self._kind 860 861 @property 862 def name_short(self): 863 return self.get_name_minimal() 864 865 @property 866 def container(self): 867 return self._container 868 869 @property 870 def container_sizes(self): 871 if self._container_sizes is None: 872 return None 873 else: 874 return (i for i in self._container_sizes) 875 876 @property 877 def tuple_values(self): 878 if self._tuple_values is None: 879 return None 880 else: 881 return (i for i in self._tuple_values) 882 883 @property 884 def description(self): 885 return self._description 886 887 @property 888 def range(self): 889 return self._range 890 891 @property 892 def units(self): 893 return self._units 894 895 @property 896 def notes(self): 897 return self._notes 898 899 @property 900 def tags(self): 901 if self._tags is None: 902 return None 903 else: 904 return (i for i in self._tags) 905 906 @property 907 def type_notes(self): 908 return self._type_notes 909 910 @property 911 def enum(self): 912 return self._enum 913 914 def _get_children(self): 915 if self.enum: 916 yield self.enum 917 918 def sort_children(self): 919 return None 920 921 def is_clone(self): 922 """ 923 Whether or not this is a Clone instance. 924 925 Returns: 926 False 927 """ 928 return False 929 930 def _init_common(self, **kwargs): 931 932 self._parent = None # filled in by MetadataSet::_construct_entries 933 934 self._container = kwargs.get('container') 935 self._container_sizes = kwargs.get('container_sizes') 936 937 # access these via the 'enum' prop 938 enum_values = kwargs.get('enum_values') 939 enum_optionals = kwargs.get('enum_optionals') 940 enum_notes = kwargs.get('enum_notes') # { value => notes } 941 enum_ids = kwargs.get('enum_ids') # { value => notes } 942 self._tuple_values = kwargs.get('tuple_values') 943 944 self._description = kwargs.get('description') 945 self._range = kwargs.get('range') 946 self._units = kwargs.get('units') 947 self._notes = kwargs.get('notes') 948 949 self._tag_ids = kwargs.get('tag_ids', []) 950 self._tags = None # Filled in by MetadataSet::_construct_tags 951 952 self._type_notes = kwargs.get('type_notes') 953 954 if kwargs.get('enum', False): 955 self._enum = Enum(self, enum_values, enum_ids, enum_optionals, enum_notes) 956 else: 957 self._enum = None 958 959 self._property_keys = kwargs 960 961 def merge(self): 962 """ 963 Copy the attributes into a new entry, merging it with the target entry 964 if it's a clone. 965 """ 966 return MergedEntry(self) 967 968 # Helpers for accessing less than the fully qualified name 969 970 def get_name_as_list(self): 971 """ 972 Returns the name as a list split by a period. 973 974 For example: 975 entry.name is 'android.lens.info.shading' 976 entry.get_name_as_list() == ['android', 'lens', 'info', 'shading'] 977 """ 978 return self.name.split(".") 979 980 def get_inner_namespace_list(self): 981 """ 982 Returns the inner namespace part of the name as a list 983 984 For example: 985 entry.name is 'android.lens.info.shading' 986 entry.get_inner_namespace_list() == ['info'] 987 """ 988 return self.get_name_as_list()[2:-1] 989 990 def get_outer_namespace(self): 991 """ 992 Returns the outer namespace as a string. 993 994 For example: 995 entry.name is 'android.lens.info.shading' 996 entry.get_outer_namespace() == 'android' 997 998 Remarks: 999 Since outer namespaces are non-recursive, 1000 and each entry has one, this does not need to be a list. 1001 """ 1002 return self.get_name_as_list()[0] 1003 1004 def get_section(self): 1005 """ 1006 Returns the section as a string. 1007 1008 For example: 1009 entry.name is 'android.lens.info.shading' 1010 entry.get_section() == '' 1011 1012 Remarks: 1013 Since outer namespaces are non-recursive, 1014 and each entry has one, this does not need to be a list. 1015 """ 1016 return self.get_name_as_list()[1] 1017 1018 def get_name_minimal(self): 1019 """ 1020 Returns only the last component of the fully qualified name as a string. 1021 1022 For example: 1023 entry.name is 'android.lens.info.shading' 1024 entry.get_name_minimal() == 'shading' 1025 1026 Remarks: 1027 entry.name_short it an alias for this 1028 """ 1029 return self.get_name_as_list()[-1] 1030 1031 def get_path_without_name(self): 1032 """ 1033 Returns a string path to the entry, with the name component excluded. 1034 1035 For example: 1036 entry.name is 'android.lens.info.shading' 1037 entry.get_path_without_name() == 'android.lens.info' 1038 """ 1039 return ".".join(self.get_name_as_list()[0:-1]) 1040 1041 1042class Clone(Entry): 1043 """ 1044 A Node corresponding to a <clone> element. It has all the attributes of an 1045 <entry> element (Entry) plus the additions specified below. 1046 1047 Attributes (Read-Only): 1048 entry: an edge to an Entry object that this targets 1049 target_kind: A string describing the kind of the target entry. 1050 name: a string of the name, same as entry.name 1051 kind: a string of the Kind ancestor, one of 'static', 'controls', 'dynamic' 1052 for the <clone> element. 1053 type: always None, since a clone cannot override the type. 1054 """ 1055 def __init__(self, entry=None, **kwargs): 1056 """ 1057 Instantiate a new Clone node. 1058 1059 Args: 1060 name: A string with the fully qualified name, e.g. 'android.shading.mode' 1061 type: A string describing the type, e.g. 'int32' 1062 kind: A string describing the kind, e.g. 'static' 1063 target_kind: A string for the kind of the target entry, e.g. 'dynamic' 1064 1065 Args (if container): 1066 container: A string describing the container, e.g. 'array' or 'tuple' 1067 container_sizes: A list of string sizes if a container, or None otherwise 1068 1069 Args (if container is 'tuple'): 1070 tuple_values: A list of tuple values, e.g. ['width', 'height'] 1071 1072 Args (if the 'enum' attribute is true): 1073 enum: A boolean, True if this is an enum, False otherwise 1074 enum_values: A list of value strings, e.g. ['ON', 'OFF'] 1075 enum_optionals: A list of optional enum values, e.g. ['OFF'] 1076 enum_notes: A dictionary of value->notes strings. 1077 enum_ids: A dictionary of value->id strings. 1078 1079 Args (optional): 1080 entry: An edge to the corresponding target Entry. 1081 description: A string with a description of the entry. 1082 range: A string with the range of the values of the entry, e.g. '>= 0' 1083 units: A string with the units of the values, e.g. 'inches' 1084 notes: A string with the notes for the entry 1085 tag_ids: A list of tag ID strings, e.g. ['BC', 'V1'] 1086 type_notes: A string with the notes for the type 1087 1088 Remarks: 1089 Note that type is not specified since it has to be the same as the 1090 entry.type. 1091 """ 1092 self._entry = entry # Entry object 1093 self._target_kind = kwargs['target_kind'] 1094 self._name = kwargs['name'] # same as entry.name 1095 self._kind = kwargs['kind'] 1096 1097 # illegal to override the type, it should be the same as the entry 1098 self._type = None 1099 # the rest of the kwargs are optional 1100 # can be used to override the regular entry data 1101 self._init_common(**kwargs) 1102 1103 @property 1104 def entry(self): 1105 return self._entry 1106 1107 @property 1108 def target_kind(self): 1109 return self._target_kind 1110 1111 def is_clone(self): 1112 """ 1113 Whether or not this is a Clone instance. 1114 1115 Returns: 1116 True 1117 """ 1118 return True 1119 1120class MergedEntry(Entry): 1121 """ 1122 A MergedEntry has all the attributes of a Clone and its target Entry merged 1123 together. 1124 1125 Remarks: 1126 Useful when we want to 'unfold' a clone into a real entry by copying out 1127 the target entry data. In this case we don't care about distinguishing 1128 a clone vs an entry. 1129 """ 1130 def __init__(self, entry): 1131 """ 1132 Create a new instance of MergedEntry. 1133 1134 Args: 1135 entry: An Entry or Clone instance 1136 """ 1137 props_distinct = ['description', 'units', 'range', 'notes', 'tags', 'kind'] 1138 1139 for p in props_distinct: 1140 if entry.is_clone(): 1141 setattr(self, '_' + p, getattr(entry, p) or getattr(entry.entry, p)) 1142 else: 1143 setattr(self, '_' + p, getattr(entry, p)) 1144 1145 props_common = ['parent', 'name', 'name_short', 'container', 1146 'container_sizes', 'enum', 1147 'tuple_values', 1148 'type', 1149 'type_notes', 1150 'enum' 1151 ] 1152 1153 for p in props_common: 1154 if entry.is_clone(): 1155 setattr(self, '_' + p, getattr(entry.entry, p)) 1156 else: 1157 setattr(self, '_' + p, getattr(entry, p)) 1158 1159