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