Home | History | Annotate | Download | only in docs
      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 """
     20 A set of classes (models) each closely representing an XML node in the
     21 metadata_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 
     36 import sys
     37 import itertools
     38 from collections import OrderedDict
     39 
     40 class 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 
    190 class 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 
    536 class 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 
    571 class 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 
    592 class 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 
    655 class 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 
    698 class 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 
    740 class 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 
    773 class 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 
    796 class 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 
   1059 class 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 
   1137 class 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