blob: e2a2f0ee3f82213ad0bd599c37e21f71454ebbc8 [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.
Igor Murashkinb556bc42012-12-04 16:07:21 -0800792 enum: An Enum instance if the enum attribute is true, None otherwise.
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800793 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
Igor Murashkinb556bc42012-12-04 16:07:21 -0800824 Args (if the 'enum' attribute is true):
825 enum: A boolean, True if this is an enum, False otherwise
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800826 enum_values: A list of value strings, e.g. ['ON', 'OFF']
827 enum_optionals: A list of optional enum values, e.g. ['OFF']
828 enum_notes: A dictionary of value->notes strings.
829 enum_ids: A dictionary of value->id strings.
830
831 Args (optional):
832 description: A string with a description of the entry.
833 range: A string with the range of the values of the entry, e.g. '>= 0'
834 units: A string with the units of the values, e.g. 'inches'
835 notes: A string with the notes for the entry
836 tag_ids: A list of tag ID strings, e.g. ['BC', 'V1']
837 type_notes: A string with the notes for the type
838 """
839
840 if kwargs.get('type') is None:
841 print >> sys.stderr, "ERROR: Missing type for entry '%s' kind '%s'" \
842 %(kwargs.get('name'), kwargs.get('kind'))
843
844 # Attributes are Read-Only, but edges may be mutated by
845 # Metadata, particularly during construct_graph
846
847 self._name = kwargs['name']
848 self._type = kwargs['type']
849 self._kind = kwargs['kind'] # static, dynamic, or controls
850
851 self._init_common(**kwargs)
852
853 @property
854 def type(self):
855 return self._type
856
857 @property
858 def kind(self):
859 return self._kind
860
861 @property
862 def name_short(self):
863 return self.get_name_minimal()
864
865 @property
866 def container(self):
867 return self._container
868
869 @property
870 def container_sizes(self):
871 if self._container_sizes is None:
872 return None
873 else:
874 return (i for i in self._container_sizes)
875
876 @property
877 def tuple_values(self):
878 if self._tuple_values is None:
879 return None
880 else:
881 return (i for i in self._tuple_values)
882
883 @property
884 def description(self):
885 return self._description
886
887 @property
888 def range(self):
889 return self._range
890
891 @property
892 def units(self):
893 return self._units
894
895 @property
896 def notes(self):
897 return self._notes
898
899 @property
900 def tags(self):
901 if self._tags is None:
902 return None
903 else:
904 return (i for i in self._tags)
905
906 @property
907 def type_notes(self):
908 return self._type_notes
909
910 @property
911 def enum(self):
912 return self._enum
913
914 def _get_children(self):
Igor Murashkin375cfd32012-12-03 13:55:33 -0800915 if self.enum:
916 yield self.enum
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800917
918 def sort_children(self):
919 return None
920
921 def is_clone(self):
922 """
923 Whether or not this is a Clone instance.
924
925 Returns:
926 False
927 """
928 return False
929
930 def _init_common(self, **kwargs):
931
932 self._parent = None # filled in by MetadataSet::_construct_entries
933
934 self._container = kwargs.get('container')
935 self._container_sizes = kwargs.get('container_sizes')
936
937 # access these via the 'enum' prop
938 enum_values = kwargs.get('enum_values')
939 enum_optionals = kwargs.get('enum_optionals')
940 enum_notes = kwargs.get('enum_notes') # { value => notes }
941 enum_ids = kwargs.get('enum_ids') # { value => notes }
942 self._tuple_values = kwargs.get('tuple_values')
943
944 self._description = kwargs.get('description')
945 self._range = kwargs.get('range')
946 self._units = kwargs.get('units')
947 self._notes = kwargs.get('notes')
948
949 self._tag_ids = kwargs.get('tag_ids', [])
950 self._tags = None # Filled in by MetadataSet::_construct_tags
951
952 self._type_notes = kwargs.get('type_notes')
953
Igor Murashkinb556bc42012-12-04 16:07:21 -0800954 if kwargs.get('enum', False):
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800955 self._enum = Enum(self, enum_values, enum_ids, enum_optionals, enum_notes)
Igor Murashkin617da162012-11-29 13:35:15 -0800956 else:
957 self._enum = None
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800958
959 self._property_keys = kwargs
960
Igor Murashkin617da162012-11-29 13:35:15 -0800961 def merge(self):
962 """
963 Copy the attributes into a new entry, merging it with the target entry
964 if it's a clone.
965 """
966 return MergedEntry(self)
967
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800968 # Helpers for accessing less than the fully qualified name
969
970 def get_name_as_list(self):
971 """
972 Returns the name as a list split by a period.
973
974 For example:
975 entry.name is 'android.lens.info.shading'
976 entry.get_name_as_list() == ['android', 'lens', 'info', 'shading']
977 """
978 return self.name.split(".")
979
980 def get_inner_namespace_list(self):
981 """
982 Returns the inner namespace part of the name as a list
983
984 For example:
985 entry.name is 'android.lens.info.shading'
986 entry.get_inner_namespace_list() == ['info']
987 """
988 return self.get_name_as_list()[2:-1]
989
990 def get_outer_namespace(self):
991 """
992 Returns the outer namespace as a string.
993
994 For example:
995 entry.name is 'android.lens.info.shading'
996 entry.get_outer_namespace() == 'android'
997
998 Remarks:
999 Since outer namespaces are non-recursive,
1000 and each entry has one, this does not need to be a list.
1001 """
1002 return self.get_name_as_list()[0]
1003
1004 def get_section(self):
1005 """
1006 Returns the section as a string.
1007
1008 For example:
1009 entry.name is 'android.lens.info.shading'
1010 entry.get_section() == ''
1011
1012 Remarks:
1013 Since outer namespaces are non-recursive,
1014 and each entry has one, this does not need to be a list.
1015 """
1016 return self.get_name_as_list()[1]
1017
1018 def get_name_minimal(self):
1019 """
1020 Returns only the last component of the fully qualified name as a string.
1021
1022 For example:
1023 entry.name is 'android.lens.info.shading'
1024 entry.get_name_minimal() == 'shading'
1025
1026 Remarks:
1027 entry.name_short it an alias for this
1028 """
1029 return self.get_name_as_list()[-1]
1030
Igor Murashkin7b9a2dc2012-11-21 14:23:24 -08001031 def get_path_without_name(self):
1032 """
1033 Returns a string path to the entry, with the name component excluded.
1034
1035 For example:
1036 entry.name is 'android.lens.info.shading'
1037 entry.get_path_without_name() == 'android.lens.info'
1038 """
1039 return ".".join(self.get_name_as_list()[0:-1])
1040
1041
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001042class Clone(Entry):
1043 """
1044 A Node corresponding to a <clone> element. It has all the attributes of an
1045 <entry> element (Entry) plus the additions specified below.
1046
1047 Attributes (Read-Only):
1048 entry: an edge to an Entry object that this targets
1049 target_kind: A string describing the kind of the target entry.
1050 name: a string of the name, same as entry.name
1051 kind: a string of the Kind ancestor, one of 'static', 'controls', 'dynamic'
1052 for the <clone> element.
1053 type: always None, since a clone cannot override the type.
1054 """
1055 def __init__(self, entry=None, **kwargs):
1056 """
1057 Instantiate a new Clone node.
1058
1059 Args:
1060 name: A string with the fully qualified name, e.g. 'android.shading.mode'
1061 type: A string describing the type, e.g. 'int32'
1062 kind: A string describing the kind, e.g. 'static'
1063 target_kind: A string for the kind of the target entry, e.g. 'dynamic'
1064
1065 Args (if container):
1066 container: A string describing the container, e.g. 'array' or 'tuple'
1067 container_sizes: A list of string sizes if a container, or None otherwise
1068
1069 Args (if container is 'tuple'):
1070 tuple_values: A list of tuple values, e.g. ['width', 'height']
1071
Igor Murashkinb556bc42012-12-04 16:07:21 -08001072 Args (if the 'enum' attribute is true):
1073 enum: A boolean, True if this is an enum, False otherwise
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001074 enum_values: A list of value strings, e.g. ['ON', 'OFF']
1075 enum_optionals: A list of optional enum values, e.g. ['OFF']
1076 enum_notes: A dictionary of value->notes strings.
1077 enum_ids: A dictionary of value->id strings.
1078
1079 Args (optional):
1080 entry: An edge to the corresponding target Entry.
1081 description: A string with a description of the entry.
1082 range: A string with the range of the values of the entry, e.g. '>= 0'
1083 units: A string with the units of the values, e.g. 'inches'
1084 notes: A string with the notes for the entry
1085 tag_ids: A list of tag ID strings, e.g. ['BC', 'V1']
1086 type_notes: A string with the notes for the type
1087
1088 Remarks:
1089 Note that type is not specified since it has to be the same as the
1090 entry.type.
1091 """
1092 self._entry = entry # Entry object
1093 self._target_kind = kwargs['target_kind']
1094 self._name = kwargs['name'] # same as entry.name
1095 self._kind = kwargs['kind']
1096
1097 # illegal to override the type, it should be the same as the entry
1098 self._type = None
1099 # the rest of the kwargs are optional
1100 # can be used to override the regular entry data
1101 self._init_common(**kwargs)
1102
1103 @property
1104 def entry(self):
1105 return self._entry
1106
1107 @property
1108 def target_kind(self):
1109 return self._target_kind
1110
1111 def is_clone(self):
1112 """
1113 Whether or not this is a Clone instance.
1114
1115 Returns:
1116 True
1117 """
1118 return True
1119
Igor Murashkin617da162012-11-29 13:35:15 -08001120class MergedEntry(Entry):
1121 """
1122 A MergedEntry has all the attributes of a Clone and its target Entry merged
1123 together.
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001124
Igor Murashkin617da162012-11-29 13:35:15 -08001125 Remarks:
1126 Useful when we want to 'unfold' a clone into a real entry by copying out
1127 the target entry data. In this case we don't care about distinguishing
1128 a clone vs an entry.
1129 """
1130 def __init__(self, entry):
1131 """
1132 Create a new instance of MergedEntry.
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001133
Igor Murashkin617da162012-11-29 13:35:15 -08001134 Args:
1135 entry: An Entry or Clone instance
1136 """
1137 props_distinct = ['description', 'units', 'range', 'notes', 'tags', 'kind']
1138
1139 for p in props_distinct:
1140 if entry.is_clone():
1141 setattr(self, '_' + p, getattr(entry, p) or getattr(entry.entry, p))
1142 else:
1143 setattr(self, '_' + p, getattr(entry, p))
1144
1145 props_common = ['parent', 'name', 'name_short', 'container',
1146 'container_sizes', 'enum',
1147 'tuple_values',
1148 'type',
1149 'type_notes',
1150 'enum'
1151 ]
1152
1153 for p in props_common:
1154 if entry.is_clone():
1155 setattr(self, '_' + p, getattr(entry.entry, p))
1156 else:
1157 setattr(self, '_' + p, getattr(entry, p))
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001158