blob: b403a47d6d424167387f9ad487f6826af609033d [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
Igor Murashkin375cfd32012-12-03 13:55:33 -0800723class EnumValue(Node):
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800724 """
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.
Igor Murashkin375cfd32012-12-03 13:55:33 -0800732 parent: An edge to the parent, always an Enum instance.
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800733 """
Igor Murashkin375cfd32012-12-03 13:55:33 -0800734 def __init__(self, name, parent, id=None, optional=False, notes=None):
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800735 self._name = name # str, e.g. 'ON' or 'OFF'
736 self._id = id # int, e.g. '0'
737 self._optional = optional # bool
738 self._notes = notes # None or str
Igor Murashkin375cfd32012-12-03 13:55:33 -0800739 self._parent = parent
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800740
741 @property
742 def id(self):
743 return self._id
744
745 @property
746 def optional(self):
747 return self._optional
748
749 @property
750 def notes(self):
751 return self._notes
752
Igor Murashkin375cfd32012-12-03 13:55:33 -0800753 def _get_children(self):
754 return None
755
756class Enum(Node):
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800757 """
758 A class corresponding to an <enum> element within an <entry>.
759
760 Attributes (Read-Only):
761 parent: An edge to the parent, always an Entry instance.
762 values: A sequence of EnumValue children.
763 """
764 def __init__(self, parent, values, ids={}, optionals=[], notes={}):
765 self._values = \
Igor Murashkin375cfd32012-12-03 13:55:33 -0800766 [ EnumValue(val, self, ids.get(val), val in optionals, notes.get(val)) \
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800767 for val in values ]
768
769 self._parent = parent
Igor Murashkin375cfd32012-12-03 13:55:33 -0800770 self._name = None
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800771
772 @property
773 def values(self):
774 return (i for i in self._values)
775
Igor Murashkin375cfd32012-12-03 13:55:33 -0800776 def _get_children(self):
777 return (i for i in self._values)
778
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800779class Entry(Node):
780 """
781 A node corresponding to an <entry> element.
782
783 Attributes (Read-Only):
784 parent: An edge to the parent node, which is an InnerNamespace or Kind.
785 name: The fully qualified name string, e.g. 'android.shading.mode'
786 name_short: The name attribute from <entry name="mode">, e.g. mode
787 type: The type attribute from <entry type="bar">
788 kind: A string ('static', 'dynamic', 'controls') corresponding to the
789 ancestor Kind#name
790 container: The container attribute from <entry container="array">, or None.
791 container_sizes: A sequence of size strings or None if container is None.
792 enum: An Enum instance if type is 'enum', None otherwise.
793 tuple_values: A sequence of strings describing the tuple values,
794 None if container is not 'tuple'.
795 description: A string description, or None.
796 range: A string range, or None.
797 units: A string units, or None.
798 tags: A sequence of Tag nodes associated with this Entry.
799 type_notes: A string describing notes for the type, or None.
800
801 Remarks:
802 Subclass Clone can be used interchangeable with an Entry,
803 for when we don't care about the underlying type.
804
805 parent and tags edges are invalid until after Metadata#construct_graph
806 has been invoked.
807 """
808 def __init__(self, **kwargs):
809 """
810 Instantiate a new Entry node.
811
812 Args:
813 name: A string with the fully qualified name, e.g. 'android.shading.mode'
814 type: A string describing the type, e.g. 'int32'
815 kind: A string describing the kind, e.g. 'static'
816
817 Args (if container):
818 container: A string describing the container, e.g. 'array' or 'tuple'
819 container_sizes: A list of string sizes if a container, or None otherwise
820
821 Args (if container is 'tuple'):
822 tuple_values: A list of tuple values, e.g. ['width', 'height']
823
824 Args (if type is 'enum'):
825 enum_values: A list of value strings, e.g. ['ON', 'OFF']
826 enum_optionals: A list of optional enum values, e.g. ['OFF']
827 enum_notes: A dictionary of value->notes strings.
828 enum_ids: A dictionary of value->id strings.
829
830 Args (optional):
831 description: A string with a description of the entry.
832 range: A string with the range of the values of the entry, e.g. '>= 0'
833 units: A string with the units of the values, e.g. 'inches'
834 notes: A string with the notes for the entry
835 tag_ids: A list of tag ID strings, e.g. ['BC', 'V1']
836 type_notes: A string with the notes for the type
837 """
838
839 if kwargs.get('type') is None:
840 print >> sys.stderr, "ERROR: Missing type for entry '%s' kind '%s'" \
841 %(kwargs.get('name'), kwargs.get('kind'))
842
843 # Attributes are Read-Only, but edges may be mutated by
844 # Metadata, particularly during construct_graph
845
846 self._name = kwargs['name']
847 self._type = kwargs['type']
848 self._kind = kwargs['kind'] # static, dynamic, or controls
849
850 self._init_common(**kwargs)
851
852 @property
853 def type(self):
854 return self._type
855
856 @property
857 def kind(self):
858 return self._kind
859
860 @property
861 def name_short(self):
862 return self.get_name_minimal()
863
864 @property
865 def container(self):
866 return self._container
867
868 @property
869 def container_sizes(self):
870 if self._container_sizes is None:
871 return None
872 else:
873 return (i for i in self._container_sizes)
874
875 @property
876 def tuple_values(self):
877 if self._tuple_values is None:
878 return None
879 else:
880 return (i for i in self._tuple_values)
881
882 @property
883 def description(self):
884 return self._description
885
886 @property
887 def range(self):
888 return self._range
889
890 @property
891 def units(self):
892 return self._units
893
894 @property
895 def notes(self):
896 return self._notes
897
898 @property
899 def tags(self):
900 if self._tags is None:
901 return None
902 else:
903 return (i for i in self._tags)
904
905 @property
906 def type_notes(self):
907 return self._type_notes
908
909 @property
910 def enum(self):
911 return self._enum
912
913 def _get_children(self):
Igor Murashkin375cfd32012-12-03 13:55:33 -0800914 if self.enum:
915 yield self.enum
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800916
917 def sort_children(self):
918 return None
919
920 def is_clone(self):
921 """
922 Whether or not this is a Clone instance.
923
924 Returns:
925 False
926 """
927 return False
928
929 def _init_common(self, **kwargs):
930
931 self._parent = None # filled in by MetadataSet::_construct_entries
932
933 self._container = kwargs.get('container')
934 self._container_sizes = kwargs.get('container_sizes')
935
936 # access these via the 'enum' prop
937 enum_values = kwargs.get('enum_values')
938 enum_optionals = kwargs.get('enum_optionals')
939 enum_notes = kwargs.get('enum_notes') # { value => notes }
940 enum_ids = kwargs.get('enum_ids') # { value => notes }
941 self._tuple_values = kwargs.get('tuple_values')
942
943 self._description = kwargs.get('description')
944 self._range = kwargs.get('range')
945 self._units = kwargs.get('units')
946 self._notes = kwargs.get('notes')
947
948 self._tag_ids = kwargs.get('tag_ids', [])
949 self._tags = None # Filled in by MetadataSet::_construct_tags
950
951 self._type_notes = kwargs.get('type_notes')
952
953 if self._type == 'enum':
954 self._enum = Enum(self, enum_values, enum_ids, enum_optionals, enum_notes)
Igor Murashkin617da162012-11-29 13:35:15 -0800955 else:
956 self._enum = None
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800957
958 self._property_keys = kwargs
959
Igor Murashkin617da162012-11-29 13:35:15 -0800960 def merge(self):
961 """
962 Copy the attributes into a new entry, merging it with the target entry
963 if it's a clone.
964 """
965 return MergedEntry(self)
966
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800967 # Helpers for accessing less than the fully qualified name
968
969 def get_name_as_list(self):
970 """
971 Returns the name as a list split by a period.
972
973 For example:
974 entry.name is 'android.lens.info.shading'
975 entry.get_name_as_list() == ['android', 'lens', 'info', 'shading']
976 """
977 return self.name.split(".")
978
979 def get_inner_namespace_list(self):
980 """
981 Returns the inner namespace part of the name as a list
982
983 For example:
984 entry.name is 'android.lens.info.shading'
985 entry.get_inner_namespace_list() == ['info']
986 """
987 return self.get_name_as_list()[2:-1]
988
989 def get_outer_namespace(self):
990 """
991 Returns the outer namespace as a string.
992
993 For example:
994 entry.name is 'android.lens.info.shading'
995 entry.get_outer_namespace() == 'android'
996
997 Remarks:
998 Since outer namespaces are non-recursive,
999 and each entry has one, this does not need to be a list.
1000 """
1001 return self.get_name_as_list()[0]
1002
1003 def get_section(self):
1004 """
1005 Returns the section as a string.
1006
1007 For example:
1008 entry.name is 'android.lens.info.shading'
1009 entry.get_section() == ''
1010
1011 Remarks:
1012 Since outer namespaces are non-recursive,
1013 and each entry has one, this does not need to be a list.
1014 """
1015 return self.get_name_as_list()[1]
1016
1017 def get_name_minimal(self):
1018 """
1019 Returns only the last component of the fully qualified name as a string.
1020
1021 For example:
1022 entry.name is 'android.lens.info.shading'
1023 entry.get_name_minimal() == 'shading'
1024
1025 Remarks:
1026 entry.name_short it an alias for this
1027 """
1028 return self.get_name_as_list()[-1]
1029
Igor Murashkin7b9a2dc2012-11-21 14:23:24 -08001030 def get_path_without_name(self):
1031 """
1032 Returns a string path to the entry, with the name component excluded.
1033
1034 For example:
1035 entry.name is 'android.lens.info.shading'
1036 entry.get_path_without_name() == 'android.lens.info'
1037 """
1038 return ".".join(self.get_name_as_list()[0:-1])
1039
1040
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001041class Clone(Entry):
1042 """
1043 A Node corresponding to a <clone> element. It has all the attributes of an
1044 <entry> element (Entry) plus the additions specified below.
1045
1046 Attributes (Read-Only):
1047 entry: an edge to an Entry object that this targets
1048 target_kind: A string describing the kind of the target entry.
1049 name: a string of the name, same as entry.name
1050 kind: a string of the Kind ancestor, one of 'static', 'controls', 'dynamic'
1051 for the <clone> element.
1052 type: always None, since a clone cannot override the type.
1053 """
1054 def __init__(self, entry=None, **kwargs):
1055 """
1056 Instantiate a new Clone node.
1057
1058 Args:
1059 name: A string with the fully qualified name, e.g. 'android.shading.mode'
1060 type: A string describing the type, e.g. 'int32'
1061 kind: A string describing the kind, e.g. 'static'
1062 target_kind: A string for the kind of the target entry, e.g. 'dynamic'
1063
1064 Args (if container):
1065 container: A string describing the container, e.g. 'array' or 'tuple'
1066 container_sizes: A list of string sizes if a container, or None otherwise
1067
1068 Args (if container is 'tuple'):
1069 tuple_values: A list of tuple values, e.g. ['width', 'height']
1070
1071 Args (if type is 'enum'):
1072 enum_values: A list of value strings, e.g. ['ON', 'OFF']
1073 enum_optionals: A list of optional enum values, e.g. ['OFF']
1074 enum_notes: A dictionary of value->notes strings.
1075 enum_ids: A dictionary of value->id strings.
1076
1077 Args (optional):
1078 entry: An edge to the corresponding target Entry.
1079 description: A string with a description of the entry.
1080 range: A string with the range of the values of the entry, e.g. '>= 0'
1081 units: A string with the units of the values, e.g. 'inches'
1082 notes: A string with the notes for the entry
1083 tag_ids: A list of tag ID strings, e.g. ['BC', 'V1']
1084 type_notes: A string with the notes for the type
1085
1086 Remarks:
1087 Note that type is not specified since it has to be the same as the
1088 entry.type.
1089 """
1090 self._entry = entry # Entry object
1091 self._target_kind = kwargs['target_kind']
1092 self._name = kwargs['name'] # same as entry.name
1093 self._kind = kwargs['kind']
1094
1095 # illegal to override the type, it should be the same as the entry
1096 self._type = None
1097 # the rest of the kwargs are optional
1098 # can be used to override the regular entry data
1099 self._init_common(**kwargs)
1100
1101 @property
1102 def entry(self):
1103 return self._entry
1104
1105 @property
1106 def target_kind(self):
1107 return self._target_kind
1108
1109 def is_clone(self):
1110 """
1111 Whether or not this is a Clone instance.
1112
1113 Returns:
1114 True
1115 """
1116 return True
1117
Igor Murashkin617da162012-11-29 13:35:15 -08001118class MergedEntry(Entry):
1119 """
1120 A MergedEntry has all the attributes of a Clone and its target Entry merged
1121 together.
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001122
Igor Murashkin617da162012-11-29 13:35:15 -08001123 Remarks:
1124 Useful when we want to 'unfold' a clone into a real entry by copying out
1125 the target entry data. In this case we don't care about distinguishing
1126 a clone vs an entry.
1127 """
1128 def __init__(self, entry):
1129 """
1130 Create a new instance of MergedEntry.
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001131
Igor Murashkin617da162012-11-29 13:35:15 -08001132 Args:
1133 entry: An Entry or Clone instance
1134 """
1135 props_distinct = ['description', 'units', 'range', 'notes', 'tags', 'kind']
1136
1137 for p in props_distinct:
1138 if entry.is_clone():
1139 setattr(self, '_' + p, getattr(entry, p) or getattr(entry.entry, p))
1140 else:
1141 setattr(self, '_' + p, getattr(entry, p))
1142
1143 props_common = ['parent', 'name', 'name_short', 'container',
1144 'container_sizes', 'enum',
1145 'tuple_values',
1146 'type',
1147 'type_notes',
1148 'enum'
1149 ]
1150
1151 for p in props_common:
1152 if entry.is_clone():
1153 setattr(self, '_' + p, getattr(entry.entry, p))
1154 else:
1155 setattr(self, '_' + p, getattr(entry, p))
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001156