• 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_definitions.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  MergedEntry: A node corresponding to either <entry> or <clone> elements.
27  Kind: A node corresponding to <dynamic>, <static>, <controls> elements.
28  InnerNamespace: A node corresponding to a <namespace> nested under a <kind>.
29  OuterNamespace: A node corresponding to a <namespace> with <kind> children.
30  Section: A node corresponding to a <section> element.
31  Enum: A class corresponding an <enum> element within an <entry>
32  EnumValue: A class corresponding to a <value> element within an Enum
33  Metadata: Root node that also provides tree construction functionality.
34  Tag: A node corresponding to a top level <tag> element.
35  Typedef: A node corresponding to a <typedef> element under <types>.
36"""
37
38import sys
39import functools
40import itertools
41from collections import OrderedDict
42
43class Node(object):
44  """
45  Base class for most nodes that are part of the Metadata graph.
46
47  Attributes (Read-Only):
48    parent: An edge to a parent Node.
49    name: A string describing the name, usually but not always the 'name'
50          attribute of the corresponding XML node.
51  """
52
53  def __init__(self):
54    self._parent = None
55    self._name = None
56
57  @property
58  def parent(self):
59    return self._parent
60
61  @property
62  def name(self):
63    return self._name
64
65  def find_all(self, pred):
66    """
67    Find all descendants that match the predicate.
68
69    Args:
70      pred: a predicate function that acts as a filter for a Node
71
72    Yields:
73      A sequence of all descendants for which pred(node) is true,
74      in a pre-order visit order.
75    """
76    if pred(self):
77      yield self
78
79    if self._get_children() is None:
80      return
81
82    for i in self._get_children():
83      for j in i.find_all(pred):
84        yield j
85
86  def find_first(self, pred):
87    """
88    Find the first descendant that matches the predicate.
89
90    Args:
91      pred: a predicate function that acts as a filter for a Node
92
93    Returns:
94      The first Node from find_all(pred), or None if there were no results.
95    """
96    for i in self.find_all(pred):
97      return i
98
99    return None
100
101  def find_parent_first(self, pred):
102    """
103    Find the first ancestor that matches the predicate.
104
105    Args:
106      pred: A predicate function that acts as a filter for a Node
107
108    Returns:
109      The first ancestor closest to the node for which pred(node) is true.
110    """
111    for i in self.find_parents(pred):
112      return i
113
114    return None
115
116  def find_parents(self, pred):
117    """
118    Find all ancestors that match the predicate.
119
120    Args:
121      pred: A predicate function that acts as a filter for a Node
122
123    Yields:
124      A sequence of all ancestors (closest to furthest) from the node,
125      where pred(node) is true.
126    """
127    parent = self.parent
128
129    while parent is not None:
130      if pred(parent):
131        yield parent
132      parent = parent.parent
133
134  def sort_children(self):
135    """
136    Sorts the immediate children in-place.
137    """
138    self._sort_by_name(self._children)
139
140  def _sort_by_name(self, what):
141    what.sort(key=lambda x: x.name)
142
143  def _get_name(self):
144    return lambda x: x.name
145
146  # Iterate over all children nodes. None when node doesn't support children.
147  def _get_children(self):
148    return (i for i in self._children)
149
150  def _children_name_map_matching(self, match=lambda x: True):
151    d = {}
152    for i in self._get_children():
153      if match(i):
154        d[i.name] = i
155    return d
156
157  @staticmethod
158  def _dictionary_by_name(values):
159    d = OrderedDict()
160    for i in values:
161      d[i.name] = i
162
163    return d
164
165  def validate_tree(self):
166    """
167    Sanity check the tree recursively, ensuring for a node n, all children's
168    parents are also n.
169
170    Returns:
171      True if validation succeeds, False otherwise.
172    """
173    succ = True
174    children = self._get_children()
175    if children is None:
176      return True
177
178    for child in self._get_children():
179      if child.parent != self:
180        print("ERROR: Node '%s' doesn't match the parent (expected: %s, actual %s)" \
181              % (child, self, child.parent), file=sys.stderr)
182        succ = False
183
184      succ = child.validate_tree() and succ
185
186    return succ
187
188  def __str__(self):
189    return "<%s name='%s'>" %(self.__class__, self.name)
190
191class Metadata(Node):
192  """
193  A node corresponding to a <metadata> entry.
194
195  Attributes (Read-Only):
196    parent: An edge to the parent Node. This is always None for Metadata.
197    outer_namespaces: A sequence of immediate OuterNamespace children.
198    tags: A sequence of all Tag instances available in the graph.
199    types: An iterable of all Typedef instances available in the graph.
200  """
201
202  def __init__(self):
203    """
204    Initialize with no children. Use insert_* functions and then
205    construct_graph() to build up the Metadata from some source.
206    """
207# Private
208    self._entries = []
209    # kind => { name => entry }
210    self._entry_map = { 'static': {}, 'dynamic': {}, 'controls': {} }
211    self._entries_ordered = [] # list of ordered Entry/Clone instances
212    self._clones = []
213
214# Public (Read Only)
215    self._name = None
216    self._parent = None
217    self._outer_namespaces = None
218    self._tags = []
219    self._types = []
220
221  @property
222  def outer_namespaces(self):
223    if self._outer_namespaces is None:
224      return None
225    else:
226      return (i for i in self._outer_namespaces)
227
228  @property
229  def tags(self):
230    return (i for i in self._tags)
231
232  @property
233  def types(self):
234    return (i for i in self._types)
235
236  def _get_properties(self):
237
238    for i in self._entries:
239      yield i
240
241    for i in self._clones:
242      yield i
243
244  def insert_tag(self, tag, description=""):
245    """
246    Insert a tag into the metadata.
247
248    Args:
249      tag: A string identifier for a tag.
250      description: A string description for a tag.
251
252    Example:
253      metadata.insert_tag("BC", "Backwards Compatibility for old API")
254
255    Remarks:
256      Subsequent calls to insert_tag with the same tag are safe (they will
257      be ignored).
258    """
259    tag_ids = [tg.name for tg in self.tags if tg.name == tag]
260    if not tag_ids:
261      self._tags.append(Tag(tag, self, description))
262
263  def insert_type(self, type_name, type_selector="typedef", **kwargs):
264    """
265    Insert a type into the metadata.
266
267    Args:
268      type_name: A type's name
269      type_selector: The selector for the type, e.g. 'typedef'
270
271    Args (if type_selector == 'typedef'):
272      languages: A map of 'language name' -> 'fully qualified class path'
273
274    Example:
275      metadata.insert_type('rectangle', 'typedef',
276                           { 'java': 'android.graphics.Rect' })
277
278    Remarks:
279      Subsequent calls to insert_type with the same type name are safe (they
280      will be ignored)
281    """
282
283    if type_selector != 'typedef':
284      raise ValueError("Unsupported type_selector given " + type_selector)
285
286    type_names = [tp.name for tp in self.types if tp.name == tp]
287    if not type_names:
288      self._types.append(Typedef(type_name, self, kwargs.get('languages')))
289
290  def insert_entry(self, entry):
291    """
292    Insert an entry into the metadata.
293
294    Args:
295      entry: A key-value dictionary describing an entry. Refer to
296             Entry#__init__ for the keys required/optional.
297
298    Remarks:
299      Subsequent calls to insert_entry with the same entry+kind name are safe
300      (they will be ignored).
301    """
302    e = Entry(**entry)
303    self._entries.append(e)
304    self._entry_map[e.kind][e.name] = e
305    self._entries_ordered.append(e)
306
307  def insert_clone(self, clone):
308    """
309    Insert a clone into the metadata.
310
311    Args:
312      clone: A key-value dictionary describing a clone. Refer to
313            Clone#__init__ for the keys required/optional.
314
315    Remarks:
316      Subsequent calls to insert_clone with the same clone+kind name are safe
317      (they will be ignored). Also the target entry need not be inserted
318      ahead of the clone entry.
319    """
320    # figure out corresponding entry later. allow clone insert, entry insert
321    entry = None
322    c = Clone(entry, **clone)
323    self._entry_map[c.kind][c.name] = c
324    self._clones.append(c)
325    self._entries_ordered.append(c)
326
327  def prune_clones(self):
328    """
329    Remove all clones that don't point to an existing entry.
330
331    Remarks:
332      This should be called after all insert_entry/insert_clone calls have
333      finished.
334    """
335    remove_list = []
336    for p in self._clones:
337      if p.entry is None:
338        remove_list.append(p)
339
340    for p in remove_list:
341
342      # remove from parent's entries list
343      if p.parent is not None:
344        p.parent._entries.remove(p)
345      # remove from parents' _leafs list
346      for ancestor in p.find_parents(lambda x: not isinstance(x, Metadata)):
347        ancestor._leafs.remove(p)
348
349      # remove from global list
350      self._clones.remove(p)
351      self._entry_map[p.kind].pop(p.name)
352      self._entries_ordered.remove(p)
353
354  def is_entry_this_kind(self, entry, kind):
355    """
356    Check if input entry if of input kind
357
358    Args:
359      entry: an Entry object
360      kind: a string. Possible values are "static", "dynamic", "controls"
361
362    Returns:
363      A boolean indicating whether input entry is of input kind.
364    """
365    if kind not in ("static", "dynamic", "controls"):
366      assert(False), "Unknown kind value " + kind
367
368    return entry.name in self._entry_map[kind]
369
370  # After all entries/clones are inserted,
371  # invoke this to generate the parent/child node graph all these objects
372  def construct_graph(self):
373    """
374    Generate the graph recursively, after which all Entry nodes will be
375    accessible recursively by crawling through the outer_namespaces sequence.
376
377    Remarks:
378      This is safe to be called multiple times at any time. It should be done at
379      least once or there will be no graph.
380    """
381    self.validate_tree()
382    self._construct_tags()
383    self.validate_tree()
384    self._construct_types()
385    self.validate_tree()
386    self._construct_clones()
387    self.validate_tree()
388    self._construct_outer_namespaces()
389    self.validate_tree()
390
391  def _construct_tags(self):
392    tag_dict = self._dictionary_by_name(self.tags)
393    for p in self._get_properties():
394      p._tags = []
395      for tag_id in p._tag_ids:
396        tag = tag_dict.get(tag_id)
397
398        if tag not in p._tags:
399          p._tags.append(tag)
400
401        if p not in tag.entries:
402          tag._entries.append(p)
403
404  def _construct_types(self):
405    type_dict = self._dictionary_by_name(self.types)
406    for p in self._get_properties():
407      if p._type_name:
408        type_node = type_dict.get(p._type_name)
409        p._typedef = type_node
410
411        if p not in type_node.entries:
412          type_node._entries.append(p)
413
414  def _construct_clones(self):
415    for p in self._clones:
416      target_kind = p.target_kind
417      target_entry = self._entry_map[target_kind].get(p.name)
418      p._entry = target_entry
419      if (p.hal_major_version == 0):
420        p._hal_major_version = target_entry._hal_major_version
421        p._hal_minor_version = target_entry._hal_minor_version
422      # should not throw if we pass validation
423      # but can happen when importing obsolete CSV entries
424      if target_entry is None:
425        print("WARNING: Clone entry '%s' target kind '%s' has no corresponding entry" \
426              % (p.name, p.target_kind), file=sys.stderr)
427
428  def _construct_outer_namespaces(self):
429
430    if self._outer_namespaces is None: #the first time this runs
431      self._outer_namespaces = []
432
433    root = self._dictionary_by_name(self._outer_namespaces)
434    for ons_name, ons in root.items():
435      ons._leafs = []
436
437    for p in self._entries_ordered:
438      ons_name = p.get_outer_namespace()
439      ons = root.get(ons_name, OuterNamespace(ons_name, self))
440      root[ons_name] = ons
441
442      if p not in ons._leafs:
443        ons._leafs.append(p)
444
445    for ons_name, ons in root.items():
446
447      ons.validate_tree()
448
449      self._construct_sections(ons)
450
451      if ons not in self._outer_namespaces:
452        self._outer_namespaces.append(ons)
453
454      ons.validate_tree()
455
456  def _construct_sections(self, outer_namespace):
457
458    sections_dict = self._dictionary_by_name(outer_namespace.sections)
459    for sec_name, sec in sections_dict.items():
460      sec._leafs = []
461      sec.validate_tree()
462
463    for p in outer_namespace._leafs:
464      does_exist = sections_dict.get(p.get_section())
465
466      sec = sections_dict.get(p.get_section(), \
467          Section(p.get_section(), outer_namespace))
468      sections_dict[p.get_section()] = sec
469
470      sec.validate_tree()
471
472      if p not in sec._leafs:
473        sec._leafs.append(p)
474
475    for sec_name, sec in sections_dict.items():
476
477      if not sec.validate_tree():
478        print("ERROR: Failed to validate tree in construct_sections (start), with section = '%s'"
479              % (sec), file=sys.stderr)
480
481      self._construct_kinds(sec)
482
483      if sec not in outer_namespace.sections:
484        outer_namespace._sections.append(sec)
485
486      if not sec.validate_tree():
487        print("ERROR: Failed to validate tree in construct_sections (end), with section = '%s'"
488              % (sec), file=sys.stderr)
489
490  # 'controls', 'static' 'dynamic'. etc
491  def _construct_kinds(self, section):
492    for kind in section.kinds:
493      kind._leafs = []
494      section.validate_tree()
495
496    group_entry_by_kind = itertools.groupby(section._leafs, lambda x: x.kind)
497    leaf_it = ((k, g) for k, g in group_entry_by_kind)
498
499    # allow multiple kinds with the same name. merge if adjacent
500    # e.g. dynamic,dynamic,static,static,dynamic -> dynamic,static,dynamic
501    # this helps maintain ABI compatibility when adding an entry in a new kind
502    for idx, (kind_name, entry_it) in enumerate(leaf_it):
503      if idx >= len(section._kinds):
504        kind = Kind(kind_name, section)
505        section._kinds.append(kind)
506        section.validate_tree()
507
508      kind = section._kinds[idx]
509
510      for p in entry_it:
511        if p not in kind._leafs:
512          kind._leafs.append(p)
513
514    for kind in section._kinds:
515      kind.validate_tree()
516      self._construct_inner_namespaces(kind)
517      kind.validate_tree()
518      self._construct_entries(kind)
519      kind.validate_tree()
520
521      if not section.validate_tree():
522        print("ERROR: Failed to validate tree in construct_kinds, with kind = '%s'" % (kind),
523              file=sys.stderr)
524
525      if not kind.validate_tree():
526        print("ERROR: Failed to validate tree in construct_kinds, with kind = '%s'" % (kind),
527              file=sys.stderr)
528
529  def _construct_inner_namespaces(self, parent, depth=0):
530    #parent is InnerNamespace or Kind
531    ins_dict = self._dictionary_by_name(parent.namespaces)
532    for name, ins in ins_dict.items():
533      ins._leafs = []
534
535    for p in parent._leafs:
536      ins_list = p.get_inner_namespace_list()
537
538      if len(ins_list) > depth:
539        ins_str = ins_list[depth]
540        ins = ins_dict.get(ins_str, InnerNamespace(ins_str, parent))
541        ins_dict[ins_str] = ins
542
543        if p not in ins._leafs:
544          ins._leafs.append(p)
545
546    for name, ins in ins_dict.items():
547      ins.validate_tree()
548      # construct children INS
549      self._construct_inner_namespaces(ins, depth + 1)
550      ins.validate_tree()
551      # construct children entries
552      self._construct_entries(ins, depth + 1)
553
554      if ins not in parent.namespaces:
555        parent._namespaces.append(ins)
556
557      if not ins.validate_tree():
558        print("ERROR: Failed to validate tree in construct_inner_namespaces, with ins = '%s'"
559              % (ins), file=sys.stderr)
560
561  # doesnt construct the entries, so much as links them
562  def _construct_entries(self, parent, depth=0):
563    #parent is InnerNamespace or Kind
564    entry_dict = self._dictionary_by_name(parent.entries)
565    for p in parent._leafs:
566      ins_list = p.get_inner_namespace_list()
567
568      if len(ins_list) == depth:
569        entry = entry_dict.get(p.name, p)
570        entry_dict[p.name] = entry
571
572    for name, entry in entry_dict.items():
573
574      old_parent = entry.parent
575      entry._parent = parent
576
577      if entry not in parent.entries:
578        parent._entries.append(entry)
579
580      if old_parent is not None and old_parent != parent:
581        print("ERROR: Parent changed from '%s' to '%s' for entry '%s'"
582              % (old_parent.name, parent.name, entry.name), file = sys.stderr)
583
584  def _get_children(self):
585    if self.outer_namespaces is not None:
586      for i in self.outer_namespaces:
587        yield i
588
589    if self.tags is not None:
590      for i in self.tags:
591        yield i
592
593class Tag(Node):
594  """
595  A tag Node corresponding to a top-level <tag> element.
596
597  Attributes (Read-Only):
598    name: alias for id
599    id: The name of the tag, e.g. for <tag id="BC"/> id = 'BC'
600    description: The description of the tag, the contents of the <tag> element.
601    parent: An edge to the parent, which is always the Metadata root node.
602    entries: A sequence of edges to entries/clones that are using this Tag.
603  """
604  def __init__(self, name, parent, description=""):
605    self._name        = name  # 'id' attribute in XML
606    self._id          = name
607    self._description = description
608    self._parent      = parent
609
610    # all entries that have this tag, including clones
611    self._entries     = []  # filled in by Metadata#construct_tags
612
613  @property
614  def id(self):
615    return self._id
616
617  @property
618  def description(self):
619    return self._description
620
621  @property
622  def entries(self):
623    return (i for i in self._entries)
624
625  def _get_children(self):
626    return None
627
628class Typedef(Node):
629  """
630  A typedef Node corresponding to a <typedef> element under a top-level <types>.
631
632  Attributes (Read-Only):
633    name: The name of this typedef as a string.
634    languages: A dictionary of 'language name' -> 'fully qualified class'.
635    parent: An edge to the parent, which is always the Metadata root node.
636    entries: An iterable over all entries which reference this typedef.
637  """
638  def __init__(self, name, parent, languages=None):
639    self._name        = name
640    self._parent      = parent
641
642    # all entries that have this typedef
643    self._entries     = []  # filled in by Metadata#construct_types
644
645    self._languages   = languages or {}
646
647  @property
648  def languages(self):
649    return self._languages
650
651  @property
652  def entries(self):
653    return (i for i in self._entries)
654
655  def _get_children(self):
656    return None
657
658class OuterNamespace(Node):
659  """
660  A node corresponding to a <namespace> element under <metadata>
661
662  Attributes (Read-Only):
663    name: The name attribute of the <namespace name="foo"> element.
664    parent: An edge to the parent, which is always the Metadata root node.
665    sections: A sequence of Section children.
666  """
667  def __init__(self, name, parent, sections=[]):
668    self._name = name
669    self._parent = parent # MetadataSet
670    self._sections = sections[:]
671    self._leafs = []
672
673    self._children = self._sections
674
675  @property
676  def sections(self):
677    return (i for i in self._sections)
678
679class Section(Node):
680  """
681  A node corresponding to a <section> element under <namespace>
682
683  Attributes (Read-Only):
684    name: The name attribute of the <section name="foo"> element.
685    parent: An edge to the parent, which is always an OuterNamespace instance.
686    description: A string description of the section, or None.
687    kinds: A sequence of Kind children.
688    merged_kinds: A sequence of virtual Kind children,
689                  with each Kind's children merged by the kind.name
690    hal_versions: A set of tuples (major, minor) describing all the HAL versions entries in this section have
691  """
692  def __init__(self, name, parent, description=None, kinds=[]):
693    self._name = name
694    self._parent = parent
695    self._description = description
696    self._kinds = kinds[:]
697
698    self._leafs = []
699
700  @property
701  def description(self):
702    return self._description
703
704  @property
705  def kinds(self):
706    return (i for i in self._kinds)
707
708  @property
709  def hal_versions(self):
710    hal_versions = set()
711    for i in self._kinds:
712      for entry in i.entries:
713        hal_versions.add( (entry.hal_major_version, entry.hal_minor_version) )
714      for namespace in i.namespaces:
715        hal_versions.update(namespace.hal_versions)
716    return hal_versions
717
718  def sort_children(self):
719    self.validate_tree()
720    # order is always controls,static,dynamic
721    find_child = lambda x: [i for i in self._get_children() if i.name == x]
722    new_lst = find_child('controls') \
723            + find_child('static')   \
724            + find_child('dynamic')
725    self._kinds = new_lst
726    self.validate_tree()
727
728  def _get_children(self):
729    return (i for i in self.kinds)
730
731  @property
732  def merged_kinds(self):
733
734    def aggregate_by_name(acc, el):
735      existing = [i for i in acc if i.name == el.name]
736      if existing:
737        k = existing[0]
738      else:
739        k = Kind(el.name, el.parent)
740        acc.append(k)
741
742      k._namespaces.extend(el._namespaces)
743      k._entries.extend(el._entries)
744
745      return acc
746
747    new_kinds_lst = functools.reduce(aggregate_by_name, self.kinds, [])
748
749    for k in new_kinds_lst:
750      yield k
751
752  def combine_kinds_into_single_node(self):
753    r"""
754    Combines the section's Kinds into a single node.
755
756    Combines all the children (kinds) of this section into a single
757    virtual Kind node.
758
759    Returns:
760      A new Kind node that collapses all Kind siblings into one, combining
761      all their children together.
762
763      For example, given self.kinds == [ x, y ]
764
765        x  y               z
766      / |  | \    -->   / | | \
767      a b  c d          a b c d
768
769      a new instance z is returned in this example.
770
771    Remarks:
772      The children of the kinds are the same references as before, that is
773      their parents will point to the old parents and not to the new parent.
774    """
775    combined = Kind(name="combined", parent=self)
776
777    for k in self._get_children():
778      combined._namespaces.extend(k.namespaces)
779      combined._entries.extend(k.entries)
780
781    return combined
782
783class Kind(Node):
784  """
785  A node corresponding to one of: <static>,<dynamic>,<controls> under a
786  <section> element.
787
788  Attributes (Read-Only):
789    name: A string which is one of 'static', 'dynamic, or 'controls'.
790    parent: An edge to the parent, which is always a Section  instance.
791    namespaces: A sequence of InnerNamespace children.
792    entries: A sequence of Entry/Clone children.
793    merged_entries: A sequence of MergedEntry virtual nodes from entries
794  """
795  def __init__(self, name, parent):
796    self._name = name
797    self._parent = parent
798    self._namespaces = []
799    self._entries = []
800
801    self._leafs = []
802
803  @property
804  def namespaces(self):
805    return self._namespaces
806
807  @property
808  def entries(self):
809    return self._entries
810
811  @property
812  def merged_entries(self):
813    for i in self.entries:
814      yield i.merge()
815
816  def sort_children(self):
817    self._namespaces.sort(key=self._get_name())
818    self._entries.sort(key=self._get_name())
819
820  def _get_children(self):
821    for i in self.namespaces:
822      yield i
823    for i in self.entries:
824      yield i
825
826  def combine_children_by_name(self):
827    r"""
828    Combine multiple children with the same name into a single node.
829
830    Returns:
831      A new Kind where all of the children with the same name were combined.
832
833      For example:
834
835      Given a Kind k:
836
837              k
838            / | \
839            a b c
840            | | |
841            d e f
842
843      a.name == "foo"
844      b.name == "foo"
845      c.name == "bar"
846
847      The returned Kind will look like this:
848
849             k'
850            /  \
851            a' c'
852          / |  |
853          d e  f
854
855    Remarks:
856      This operation is not recursive. To combine the grandchildren and other
857      ancestors, call this method on the ancestor nodes.
858    """
859    return Kind._combine_children_by_name(self, new_type=type(self))
860
861  # new_type is either Kind or InnerNamespace
862  @staticmethod
863  def _combine_children_by_name(self, new_type):
864    new_ins_dict = OrderedDict()
865    new_ent_dict = OrderedDict()
866
867    for ins in self.namespaces:
868      new_ins = new_ins_dict.setdefault(ins.name,
869                                        InnerNamespace(ins.name, parent=self))
870      new_ins._namespaces.extend(ins.namespaces)
871      new_ins._entries.extend(ins.entries)
872
873    for ent in self.entries:
874      new_ent = new_ent_dict.setdefault(ent.name,
875                                        ent.merge())
876
877    kind = new_type(self.name, self.parent)
878    kind._namespaces = new_ins_dict.values()
879    kind._entries = new_ent_dict.values()
880
881    return kind
882
883class InnerNamespace(Node):
884  """
885  A node corresponding to a <namespace> which is an ancestor of a Kind.
886  These namespaces may have other namespaces recursively, or entries as leafs.
887
888  Attributes (Read-Only):
889    name: Name attribute from the element, e.g. <namespace name="foo"> -> 'foo'
890    parent: An edge to the parent, which is an InnerNamespace or a Kind.
891    namespaces: A sequence of InnerNamespace children.
892    entries: A sequence of Entry/Clone children.
893    merged_entries: A sequence of MergedEntry virtual nodes from entries
894    hal_versions: A set of tuples (major, minor) describing all the HAL versions entries in this section have
895  """
896  def __init__(self, name, parent):
897    self._name        = name
898    self._parent      = parent
899    self._namespaces  = []
900    self._entries     = []
901    self._leafs       = []
902
903  @property
904  def namespaces(self):
905    return self._namespaces
906
907  @property
908  def entries(self):
909    return self._entries
910
911  @property
912  def hal_versions(self):
913    hal_versions = set()
914    for entry in self.entries:
915      hal_versions.add( (entry.hal_major_version, entry.hal_minor_version) )
916    for namespace in self.namespaces:
917      hal_versions.update(namespace.hal_versions)
918    return hal_versions
919
920  @property
921  def merged_entries(self):
922    for i in self.entries:
923      yield i.merge()
924
925  def sort_children(self):
926    self._namespaces.sort(key=self._get_name())
927    self._entries.sort(key=self._get_name())
928
929  def _get_children(self):
930    for i in self.namespaces:
931      yield i
932    for i in self.entries:
933      yield i
934
935  def combine_children_by_name(self):
936    r"""
937    Combine multiple children with the same name into a single node.
938
939    Returns:
940      A new InnerNamespace where all of the children with the same name were
941      combined.
942
943      For example:
944
945      Given an InnerNamespace i:
946
947              i
948            / | \
949            a b c
950            | | |
951            d e f
952
953      a.name == "foo"
954      b.name == "foo"
955      c.name == "bar"
956
957      The returned InnerNamespace will look like this:
958
959             i'
960            /  \
961            a' c'
962          / |  |
963          d e  f
964
965    Remarks:
966      This operation is not recursive. To combine the grandchildren and other
967      ancestors, call this method on the ancestor nodes.
968    """
969    return Kind._combine_children_by_name(self, new_type=type(self))
970
971class EnumValue(Node):
972  """
973  A class corresponding to a <value> element within an <enum> within an <entry>.
974
975  Attributes (Read-Only):
976    name: A string,                 e.g. 'ON' or 'OFF'
977    id: An optional numeric string, e.g. '0' or '0xFF'
978    deprecated: A boolean, True if the enum should be deprecated.
979    optional: A boolean
980    hidden: A boolean, True if the enum should be hidden.
981    ndk_hidden: A boolean, True if the enum should be hidden in NDK
982    notes: A string describing the notes, or None.
983    sdk_notes: A string describing extra notes for public SDK only
984    ndk_notes: A string describing extra notes for public NDK only
985    parent: An edge to the parent, always an Enum instance.
986    hal_major_version: The major HIDL HAL version this value was first added in
987    hal_minor_version: The minor HIDL HAL version this value was first added in
988  """
989  def __init__(self, name, parent,
990      id=None, deprecated=False, optional=False, hidden=False, notes=None, sdk_notes=None, ndk_notes=None, ndk_hidden=False, hal_version='3.2'):
991    self._name = name                    # str, e.g. 'ON' or 'OFF'
992    self._id = id                        # int, e.g. '0'
993    self._deprecated = deprecated        # bool
994    self._optional = optional            # bool
995    self._hidden = hidden                # bool
996    self._ndk_hidden = ndk_hidden        # bool
997    self._notes = notes                  # None or str
998    self._sdk_notes = sdk_notes          # None or str
999    self._ndk_notes = ndk_notes          # None or str
1000    self._parent = parent
1001    if hal_version is None:
1002      if parent is not None and parent.parent is not None:
1003        self._hal_major_version = parent.parent.hal_major_version
1004        self._hal_minor_version = parent.parent.hal_minor_version
1005      else:
1006        self._hal_major_version = 3
1007        self._hal_minor_version = 2
1008    else:
1009      self._hal_major_version = int(hal_version.partition('.')[0])
1010      self._hal_minor_version = int(hal_version.partition('.')[2])
1011
1012  @property
1013  def id(self):
1014    return self._id
1015
1016  @property
1017  def deprecated(self):
1018    return self._deprecated
1019
1020  @property
1021  def optional(self):
1022    return self._optional
1023
1024  @property
1025  def hidden(self):
1026    return self._hidden
1027
1028  @property
1029  def ndk_hidden(self):
1030    return self._ndk_hidden
1031
1032  @property
1033  def notes(self):
1034    return self._notes
1035
1036  @property
1037  def sdk_notes(self):
1038    return self._sdk_notes
1039
1040  @property
1041  def ndk_notes(self):
1042    return self._ndk_notes
1043
1044  @property
1045  def hal_major_version(self):
1046    return self._hal_major_version
1047
1048  @property
1049  def hal_minor_version(self):
1050    return self._hal_minor_version
1051
1052  def _get_children(self):
1053    return None
1054
1055class Enum(Node):
1056  """
1057  A class corresponding to an <enum> element within an <entry>.
1058
1059  Attributes (Read-Only):
1060    parent: An edge to the parent, always an Entry instance.
1061    values: A sequence of EnumValue children.
1062    has_values_with_id: A boolean representing if any of the children have a
1063        non-empty id property.
1064  """
1065  def __init__(self, parent, values, ids={}, deprecateds=[],
1066      optionals=[], hiddens=[], notes={}, sdk_notes={}, ndk_notes={}, ndk_hiddens=[], hal_versions={}):
1067    self._parent = parent
1068    self._name = None
1069    self._values =                                                             \
1070      [ EnumValue(val, self, ids.get(val), val in deprecateds, val in optionals, val in hiddens,  \
1071                  notes.get(val), sdk_notes.get(val), ndk_notes.get(val), val in ndk_hiddens, hal_versions.get(val))     \
1072        for val in values ]
1073
1074  @property
1075  def values(self):
1076    return (i for i in self._values)
1077
1078  @property
1079  def has_values_with_id(self):
1080    return bool(any(i for i in self.values if i.id))
1081
1082  def has_new_values_added_in_hal_version(self, hal_major_version, hal_minor_version):
1083    return bool(any(i for i in self.values if i.hal_major_version == hal_major_version and i.hal_minor_version == hal_minor_version))
1084
1085  def _get_children(self):
1086    return (i for i in self._values)
1087
1088class Entry(Node):
1089  """
1090  A node corresponding to an <entry> element.
1091
1092  Attributes (Read-Only):
1093    parent: An edge to the parent node, which is an InnerNamespace or Kind.
1094    name: The fully qualified name string, e.g. 'android.shading.mode'
1095    name_short: The name attribute from <entry name="mode">, e.g. mode
1096    type: The type attribute from <entry type="bar">
1097    kind: A string ('static', 'dynamic', 'controls') corresponding to the
1098          ancestor Kind#name
1099    container: The container attribute from <entry container="array">, or None.
1100    container_sizes: A sequence of size strings or None if container is None.
1101    enum: An Enum instance if the enum attribute is true, None otherwise.
1102    visibility: The visibility of this entry ('system', 'hidden', 'public')
1103                across the system. System entries are only visible in native code
1104                headers. Hidden entries are marked @hide in managed code, while
1105                public entries are visible in the Android SDK.
1106    applied_visibility: As visibility, but always valid, defaulting to 'system'
1107                        if no visibility is given for an entry.
1108    applied_ndk_visible: Always valid. Default is 'false'.
1109                         Set to 'true' when the visibility implied entry is visible
1110                         in NDK.
1111    synthetic: The C-level visibility of this entry ('false', 'true').
1112               Synthetic entries will not be generated into the native metadata
1113               list of entries (in C code). In general a synthetic entry is
1114               glued together at the Java layer from multiple visibiltity=hidden
1115               entries.
1116    hwlevel: The lowest hardware level at which the entry is guaranteed
1117             to be supported by the camera device. All devices with higher
1118             hwlevels will also include this entry. None means that the
1119             entry is optional on any hardware level.
1120    permission_needed: Flags whether the tag needs extra camera permission.
1121    deprecated: Marks an entry as @Deprecated in the Java layer; if within an
1122               unreleased version this needs to be removed altogether. If applied
1123               to an entry from an older release, then this means the entry
1124               should be ignored by newer code.
1125    optional: a bool representing the optional attribute, which denotes the entry
1126              is required for hardware level full devices, but optional for other
1127              hardware levels.  None if not present.
1128    applied_optional: As optional but always valid, defaulting to False if no
1129                      optional attribute is present.
1130    tuple_values: A sequence of strings describing the tuple values,
1131                  None if container is not 'tuple'.
1132    description: A string description, or None.
1133    deprecation_description: A string describing the reason for deprecation. Must be present
1134                 if deprecated is true, otherwise may be None.
1135    range: A string range, or None.
1136    units: A string units, or None.
1137    tags: A sequence of Tag nodes associated with this Entry.
1138    type_notes: A string describing notes for the type, or None.
1139    typedef: A Typedef associated with this Entry, or None.
1140
1141  Remarks:
1142    Subclass Clone can be used interchangeable with an Entry,
1143    for when we don't care about the underlying type.
1144
1145    parent and tags edges are invalid until after Metadata#construct_graph
1146    has been invoked.
1147  """
1148  def __init__(self, **kwargs):
1149    """
1150    Instantiate a new Entry node.
1151
1152    Args:
1153      name: A string with the fully qualified name, e.g. 'android.shading.mode'
1154      type: A string describing the type, e.g. 'int32'
1155      kind: A string describing the kind, e.g. 'static'
1156      hal_version: A string for the initial HIDL HAL metadata version this entry
1157                   was added in
1158
1159    Args (if container):
1160      container: A string describing the container, e.g. 'array' or 'tuple'
1161      container_sizes: A list of string sizes if a container, or None otherwise
1162
1163    Args (if container is 'tuple'):
1164      tuple_values: A list of tuple values, e.g. ['width', 'height']
1165
1166    Args (if the 'enum' attribute is true):
1167      enum: A boolean, True if this is an enum, False otherwise
1168      enum_values: A list of value strings, e.g. ['ON', 'OFF']
1169      enum_optionals: A list of optional enum values, e.g. ['OFF']
1170      enum_notes: A dictionary of value->notes strings.
1171      enum_ids: A dictionary of value->id strings.
1172      enum_hal_versions: A dictionary of value->hal version strings
1173
1174    Args (if the 'deprecated' attribute is true):
1175      deprecation_description: A string explaining the deprecation, to be added
1176                               to the Java-layer @deprecated tag
1177
1178    Args (optional):
1179      description: A string with a description of the entry.
1180      range: A string with the range of the values of the entry, e.g. '>= 0'
1181      units: A string with the units of the values, e.g. 'inches'
1182      details: A string with the detailed documentation for the entry
1183      hal_details: A string with the HAL implementation details for the entry
1184      ndk_details: A string with the extra NDK API documentation for the entry=
1185      tag_ids: A list of tag ID strings, e.g. ['BC', 'V1']
1186      type_notes: A string with the notes for the type
1187      visibility: A string describing the visibility, eg 'system', 'hidden',
1188                  'public'
1189      synthetic: A bool to mark whether this entry is visible only at the Java
1190                 layer (True), or at both layers (False = default).
1191      hwlevel: A string of the HW level (one of 'legacy', 'limited', 'full')
1192      deprecated: A bool to mark whether this is @Deprecated at the Java layer
1193                 (default = False).
1194      optional: A bool to mark whether optional for non-full hardware devices
1195      typedef: A string corresponding to a typedef's name attribute.
1196    """
1197
1198    if kwargs.get('type') is None:
1199      print("ERROR: Missing type for entry '%s' kind '%s'"
1200            % (kwargs.get('name'), kwargs.get('kind')), file=sys.stderr)
1201
1202    # Attributes are Read-Only, but edges may be mutated by
1203    # Metadata, particularly during construct_graph
1204
1205    self._name = kwargs['name']
1206    self._type = kwargs['type']
1207    self._kind = kwargs['kind'] # static, dynamic, or controls
1208
1209    self._init_common(**kwargs)
1210
1211  @property
1212  def type(self):
1213    return self._type
1214
1215  @property
1216  def kind(self):
1217    return self._kind
1218
1219  @property
1220  def hal_major_version(self):
1221    return self._hal_major_version
1222
1223  @property
1224  def hal_minor_version(self):
1225    return self._hal_minor_version
1226
1227  @property
1228  def visibility(self):
1229    return self._visibility
1230
1231  @property
1232  def applied_visibility(self):
1233    return self._visibility or 'system'
1234
1235  @property
1236  def applied_ndk_visible(self):
1237    if self._visibility in ("public", "ndk_public"):
1238      return "true"
1239    return "false"
1240
1241  @property
1242  def synthetic(self):
1243    return self._synthetic
1244
1245  @property
1246  def hwlevel(self):
1247    return self._hwlevel
1248
1249  @property
1250  def deprecated(self):
1251    return self._deprecated
1252
1253  @property
1254  def deprecation_description(self):
1255    return self._deprecation_description
1256
1257  @property
1258  def permission_needed(self):
1259    return self._permission_needed or "false"
1260
1261  # TODO: optional should just return hwlevel is None
1262  @property
1263  def optional(self):
1264    return self._optional
1265
1266  @property
1267  def applied_optional(self):
1268    return self._optional or False
1269
1270  @property
1271  def name_short(self):
1272    return self.get_name_minimal()
1273
1274  @property
1275  def container(self):
1276    return self._container
1277
1278  @property
1279  def container_sizes(self):
1280    if self._container_sizes is None:
1281      return None
1282    else:
1283      return (i for i in self._container_sizes)
1284
1285  @property
1286  def tuple_values(self):
1287    if self._tuple_values is None:
1288      return None
1289    else:
1290      return (i for i in self._tuple_values)
1291
1292  @property
1293  def description(self):
1294    return self._description
1295
1296  @property
1297  def range(self):
1298    return self._range
1299
1300  @property
1301  def units(self):
1302    return self._units
1303
1304  @property
1305  def details(self):
1306    return self._details
1307
1308  @property
1309  def hal_details(self):
1310    return self._hal_details
1311
1312  @property
1313  def ndk_details(self):
1314    return self._ndk_details
1315
1316  @property
1317  def applied_ndk_details(self):
1318    return (self._details or "") + (self._ndk_details or "")
1319
1320  @property
1321  def tags(self):
1322    if self._tags is None:
1323      return None
1324    else:
1325      return (i for i in self._tags)
1326
1327  @property
1328  def type_notes(self):
1329    return self._type_notes
1330
1331  @property
1332  def typedef(self):
1333    return self._typedef
1334
1335  @property
1336  def enum(self):
1337    return self._enum
1338
1339  def has_new_values_added_in_hal_version(self, hal_major_version, hal_minor_version):
1340    if self._enum is not None:
1341      return self._enum.has_new_values_added_in_hal_version(hal_major_version,hal_minor_version)
1342    else:
1343      return False
1344
1345  def _get_children(self):
1346    if self.enum:
1347      yield self.enum
1348
1349  def sort_children(self):
1350    return None
1351
1352  def is_clone(self):
1353    """
1354    Whether or not this is a Clone instance.
1355
1356    Returns:
1357      False
1358    """
1359    return False
1360
1361  def _init_common(self, **kwargs):
1362
1363    self._parent = None # filled in by Metadata::_construct_entries
1364
1365    self._container = kwargs.get('container')
1366    self._container_sizes = kwargs.get('container_sizes')
1367
1368    hal_version = kwargs.get('hal_version')
1369    if hal_version is None:
1370      if self.is_clone():
1371        self._hal_major_version = 0
1372        self._hal_minor_version = 0
1373      else:
1374        self._hal_major_version = 3
1375        self._hal_minor_version = 2
1376    else:
1377      self._hal_major_version = int(hal_version.partition('.')[0])
1378      self._hal_minor_version = int(hal_version.partition('.')[2])
1379
1380    # access these via the 'enum' prop
1381    enum_values = kwargs.get('enum_values')
1382    enum_deprecateds = kwargs.get('enum_deprecateds')
1383    enum_optionals = kwargs.get('enum_optionals')
1384    enum_hiddens = kwargs.get('enum_hiddens')
1385    enum_ndk_hiddens = kwargs.get('enum_ndk_hiddens')
1386    enum_notes = kwargs.get('enum_notes')  # { value => notes }
1387    enum_sdk_notes = kwargs.get('enum_sdk_notes')  # { value => sdk_notes }
1388    enum_ndk_notes = kwargs.get('enum_ndk_notes')  # { value => ndk_notes }
1389    enum_ids = kwargs.get('enum_ids')  # { value => notes }
1390    enum_hal_versions = kwargs.get('enum_hal_versions') # { value => hal_versions }
1391
1392    self._tuple_values = kwargs.get('tuple_values')
1393
1394    self._description = kwargs.get('description')
1395    self._range = kwargs.get('range')
1396    self._units = kwargs.get('units')
1397    self._details = kwargs.get('details')
1398    self._hal_details = kwargs.get('hal_details')
1399    self._ndk_details = kwargs.get('ndk_details')
1400
1401    self._tag_ids = kwargs.get('tag_ids', [])
1402    self._tags = None  # Filled in by Metadata::_construct_tags
1403
1404    self._type_notes = kwargs.get('type_notes')
1405    self._type_name = kwargs.get('type_name')
1406    self._typedef = None # Filled in by Metadata::_construct_types
1407
1408    if kwargs.get('enum', False):
1409      self._enum = Enum(self, enum_values, enum_ids, enum_deprecateds, enum_optionals,
1410                        enum_hiddens, enum_notes, enum_sdk_notes, enum_ndk_notes, enum_ndk_hiddens, enum_hal_versions)
1411    else:
1412      self._enum = None
1413
1414    self._visibility = kwargs.get('visibility')
1415    self._synthetic = kwargs.get('synthetic', False)
1416    self._hwlevel = kwargs.get('hwlevel')
1417    self._deprecated = kwargs.get('deprecated', False)
1418    self._deprecation_description = kwargs.get('deprecation_description')
1419
1420    self._permission_needed = kwargs.get('permission_needed')
1421    self._optional = kwargs.get('optional')
1422    self._ndk_visible = kwargs.get('ndk_visible')
1423
1424    self._property_keys = kwargs
1425
1426  def merge(self):
1427    """
1428    Copy the attributes into a new entry, merging it with the target entry
1429    if it's a clone.
1430    """
1431    return MergedEntry(self)
1432
1433  # Helpers for accessing less than the fully qualified name
1434
1435  def get_name_as_list(self):
1436    """
1437    Returns the name as a list split by a period.
1438
1439    For example:
1440      entry.name is 'android.lens.info.shading'
1441      entry.get_name_as_list() == ['android', 'lens', 'info', 'shading']
1442    """
1443    return self.name.split(".")
1444
1445  def get_inner_namespace_list(self):
1446    """
1447    Returns the inner namespace part of the name as a list
1448
1449    For example:
1450      entry.name is 'android.lens.info.shading'
1451      entry.get_inner_namespace_list() == ['info']
1452    """
1453    return self.get_name_as_list()[2:-1]
1454
1455  def get_outer_namespace(self):
1456    """
1457    Returns the outer namespace as a string.
1458
1459    For example:
1460      entry.name is 'android.lens.info.shading'
1461      entry.get_outer_namespace() == 'android'
1462
1463    Remarks:
1464      Since outer namespaces are non-recursive,
1465      and each entry has one, this does not need to be a list.
1466    """
1467    return self.get_name_as_list()[0]
1468
1469  def get_section(self):
1470    """
1471    Returns the section as a string.
1472
1473    For example:
1474      entry.name is 'android.lens.info.shading'
1475      entry.get_section() == ''
1476
1477    Remarks:
1478      Since outer namespaces are non-recursive,
1479      and each entry has one, this does not need to be a list.
1480    """
1481    return self.get_name_as_list()[1]
1482
1483  def get_name_minimal(self):
1484    """
1485    Returns only the last component of the fully qualified name as a string.
1486
1487    For example:
1488      entry.name is 'android.lens.info.shading'
1489      entry.get_name_minimal() == 'shading'
1490
1491    Remarks:
1492      entry.name_short it an alias for this
1493    """
1494    return self.get_name_as_list()[-1]
1495
1496  def get_path_without_name(self):
1497    """
1498    Returns a string path to the entry, with the name component excluded.
1499
1500    For example:
1501      entry.name is 'android.lens.info.shading'
1502      entry.get_path_without_name() == 'android.lens.info'
1503    """
1504    return ".".join(self.get_name_as_list()[0:-1])
1505
1506
1507class Clone(Entry):
1508  """
1509  A Node corresponding to a <clone> element. It has all the attributes of an
1510  <entry> element (Entry) plus the additions specified below.
1511
1512  Attributes (Read-Only):
1513    entry: an edge to an Entry object that this targets
1514    target_kind: A string describing the kind of the target entry.
1515    name: a string of the name, same as entry.name
1516    kind: a string of the Kind ancestor, one of 'static', 'controls', 'dynamic'
1517          for the <clone> element.
1518    type: always None, since a clone cannot override the type.
1519  """
1520  def __init__(self, entry=None, **kwargs):
1521    """
1522    Instantiate a new Clone node.
1523
1524    Args:
1525      name: A string with the fully qualified name, e.g. 'android.shading.mode'
1526      type: A string describing the type, e.g. 'int32'
1527      kind: A string describing the kind, e.g. 'static'
1528      target_kind: A string for the kind of the target entry, e.g. 'dynamic'
1529      hal_version: A string for the initial HIDL HAL metadata version this entry
1530                   was added in
1531
1532    Args (if container):
1533      container: A string describing the container, e.g. 'array' or 'tuple'
1534      container_sizes: A list of string sizes if a container, or None otherwise
1535
1536    Args (if container is 'tuple'):
1537      tuple_values: A list of tuple values, e.g. ['width', 'height']
1538
1539    Args (if the 'enum' attribute is true):
1540      enum: A boolean, True if this is an enum, False otherwise
1541      enum_values: A list of value strings, e.g. ['ON', 'OFF']
1542      enum_optionals: A list of optional enum values, e.g. ['OFF']
1543      enum_notes: A dictionary of value->notes strings.
1544      enum_ids: A dictionary of value->id strings.
1545
1546    Args (optional):
1547      entry: An edge to the corresponding target Entry.
1548      description: A string with a description of the entry.
1549      range: A string with the range of the values of the entry, e.g. '>= 0'
1550      units: A string with the units of the values, e.g. 'inches'
1551      details: A string with the detailed documentation for the entry
1552      hal_details: A string with the HAL implementation details for the entry
1553      ndk_details: A string with the extra NDK documentation for the entry
1554      tag_ids: A list of tag ID strings, e.g. ['BC', 'V1']
1555      type_notes: A string with the notes for the type
1556
1557    Remarks:
1558      Note that type is not specified since it has to be the same as the
1559      entry.type.
1560    """
1561    self._entry = entry  # Entry object
1562    self._target_kind = kwargs['target_kind']
1563    self._name = kwargs['name']  # same as entry.name
1564    self._kind = kwargs['kind']
1565
1566    # illegal to override the type, it should be the same as the entry
1567    self._type = None
1568    # the rest of the kwargs are optional
1569    # can be used to override the regular entry data
1570    self._init_common(**kwargs)
1571
1572  @property
1573  def entry(self):
1574    return self._entry
1575
1576  @property
1577  def target_kind(self):
1578    return self._target_kind
1579
1580  def is_clone(self):
1581    """
1582    Whether or not this is a Clone instance.
1583
1584    Returns:
1585      True
1586    """
1587    return True
1588
1589class MergedEntry(Entry):
1590  """
1591  A MergedEntry has all the attributes of a Clone and its target Entry merged
1592  together.
1593
1594  Remarks:
1595    Useful when we want to 'unfold' a clone into a real entry by copying out
1596    the target entry data. In this case we don't care about distinguishing
1597    a clone vs an entry.
1598  """
1599  def __init__(self, entry):
1600    """
1601    Create a new instance of MergedEntry.
1602
1603    Args:
1604      entry: An Entry or Clone instance
1605    """
1606    props_distinct = ['description', 'units', 'range', 'details',
1607                      'hal_details', 'ndk_details', 'tags', 'kind',
1608                      'deprecation_description']
1609
1610    for p in props_distinct:
1611      p = '_' + p
1612      if entry.is_clone():
1613        setattr(self, p, getattr(entry, p) or getattr(entry.entry, p))
1614      else:
1615        setattr(self, p, getattr(entry, p))
1616
1617    props_common = ['parent', 'name', 'container',
1618                    'container_sizes', 'enum',
1619                    'tuple_values',
1620                    'type',
1621                    'type_notes',
1622                    'visibility',
1623                    'ndk_visible',
1624                    'synthetic',
1625                    'hwlevel',
1626                    'deprecated',
1627                    'optional',
1628                    'typedef',
1629                    'hal_major_version',
1630                    'hal_minor_version',
1631                    'permission_needed'
1632                   ]
1633
1634    for p in props_common:
1635      p = '_' + p
1636      if entry.is_clone():
1637        setattr(self, p, getattr(entry.entry, p))
1638      else:
1639        setattr(self, p, getattr(entry, p))
1640