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