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