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