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