blob: daf171c3bb776a3f272c9425cb288ddc3caf5ffb [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.
Igor Murashkinaa133d32013-06-28 17:27:49 -070026 MergedEntry: A node corresponding to either <entry> or <clone> elements.
Igor Murashkin77b63ca2012-11-09 16:15:02 -080027 Kind: A node corresponding to <dynamic>, <static>, <controls> elements.
28 InnerNamespace: A node corresponding to a <namespace> nested under a <kind>.
29 OuterNamespace: A node corresponding to a <namespace> with <kind> children.
30 Section: A node corresponding to a <section> element.
31 Enum: A class corresponding an <enum> element within an <entry>
Igor Murashkinaa133d32013-06-28 17:27:49 -070032 EnumValue: A class corresponding to a <value> element within an Enum
Igor Murashkin77b63ca2012-11-09 16:15:02 -080033 Metadata: Root node that also provides tree construction functionality.
34 Tag: A node corresponding to a top level <tag> element.
35"""
36
37import sys
Igor Murashkin5804a482012-12-05 13:06:59 -080038import itertools
Igor Murashkin586c8612012-11-29 17:08:36 -080039from collections import OrderedDict
Igor Murashkin77b63ca2012-11-09 16:15:02 -080040
41class Node(object):
42 """
43 Base class for most nodes that are part of the Metadata graph.
44
45 Attributes (Read-Only):
46 parent: An edge to a parent Node.
47 name: A string describing the name, usually but not always the 'name'
48 attribute of the corresponding XML node.
49 """
50
51 def __init__(self):
52 self._parent = None
53 self._name = None
54
55 @property
56 def parent(self):
57 return self._parent
58
59 @property
60 def name(self):
61 return self._name
62
63 def find_all(self, pred):
64 """
65 Find all descendants that match the predicate.
66
67 Args:
68 pred: a predicate function that acts as a filter for a Node
69
70 Yields:
71 A sequence of all descendants for which pred(node) is true,
72 in a pre-order visit order.
73 """
74 if pred(self):
75 yield self
76
77 if self._get_children() is None:
78 return
79
80 for i in self._get_children():
81 for j in i.find_all(pred):
82 yield j
83
Igor Murashkin77b63ca2012-11-09 16:15:02 -080084 def find_first(self, pred):
85 """
86 Find the first descendant that matches the predicate.
87
88 Args:
89 pred: a predicate function that acts as a filter for a Node
90
91 Returns:
92 The first Node from find_all(pred), or None if there were no results.
93 """
94 for i in self.find_all(pred):
95 return i
96
97 return None
98
99 def find_parent_first(self, pred):
100 """
101 Find the first ancestor that matches the predicate.
102
103 Args:
104 pred: A predicate function that acts as a filter for a Node
105
106 Returns:
107 The first ancestor closest to the node for which pred(node) is true.
108 """
109 for i in self.find_parents(pred):
110 return i
111
112 return None
113
114 def find_parents(self, pred):
115 """
116 Find all ancestors that match the predicate.
117
118 Args:
119 pred: A predicate function that acts as a filter for a Node
120
121 Yields:
122 A sequence of all ancestors (closest to furthest) from the node,
123 where pred(node) is true.
124 """
125 parent = self.parent
126
127 while parent is not None:
128 if pred(parent):
129 yield parent
130 parent = parent.parent
131
132 def sort_children(self):
133 """
134 Sorts the immediate children in-place.
135 """
136 self._sort_by_name(self._children)
137
138 def _sort_by_name(self, what):
139 what.sort(key=lambda x: x.name)
140
141 def _get_name(self):
142 return lambda x: x.name
143
144 # Iterate over all children nodes. None when node doesn't support children.
145 def _get_children(self):
146 return (i for i in self._children)
147
148 def _children_name_map_matching(self, match=lambda x: True):
149 d = {}
Igor Murashkinaa133d32013-06-28 17:27:49 -0700150 for i in self._get_children():
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800151 if match(i):
152 d[i.name] = i
153 return d
154
155 @staticmethod
156 def _dictionary_by_name(values):
Igor Murashkin586c8612012-11-29 17:08:36 -0800157 d = OrderedDict()
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800158 for i in values:
159 d[i.name] = i
160
161 return d
162
163 def validate_tree(self):
164 """
165 Sanity check the tree recursively, ensuring for a node n, all children's
166 parents are also n.
167
168 Returns:
169 True if validation succeeds, False otherwise.
170 """
171 succ = True
172 children = self._get_children()
173 if children is None:
174 return True
175
176 for child in self._get_children():
177 if child.parent != self:
178 print >> sys.stderr, ("ERROR: Node '%s' doesn't match the parent" + \
179 "(expected: %s, actual %s)") \
180 %(child, self, child.parent)
181 succ = False
182
183 succ = child.validate_tree() and succ
184
185 return succ
186
187 def __str__(self):
188 return "<%s name='%s'>" %(self.__class__, self.name)
189
190class Metadata(Node):
191 """
192 A node corresponding to a <metadata> entry.
193
194 Attributes (Read-Only):
195 parent: An edge to the parent Node. This is always None for Metadata.
196 outer_namespaces: A sequence of immediate OuterNamespace children.
197 tags: A sequence of all Tag instances available in the graph.
198 """
199
200 def __init__(self):
201 """
202 Initialize with no children. Use insert_* functions and then
203 construct_graph() to build up the Metadata from some source.
204 """
205
206# Private
207 self._entries = []
208 # kind => { name => entry }
209 self._entry_map = { 'static': {}, 'dynamic': {}, 'controls': {} }
Igor Murashkin586c8612012-11-29 17:08:36 -0800210 self._entries_ordered = [] # list of ordered Entry/Clone instances
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800211 self._clones = []
212
213# Public (Read Only)
214 self._parent = None
215 self._outer_namespaces = None
216 self._tags = []
217
218 @property
219 def outer_namespaces(self):
220 if self._outer_namespaces is None:
221 return None
222 else:
223 return (i for i in self._outer_namespaces)
224
225 @property
226 def tags(self):
227 return (i for i in self._tags)
228
229 def _get_properties(self):
230
231 for i in self._entries:
232 yield i
233
234 for i in self._clones:
235 yield i
236
237 def insert_tag(self, tag, description=""):
238 """
239 Insert a tag into the metadata.
240
241 Args:
242 tag: A string identifier for a tag.
243 description: A string description for a tag.
244
245 Example:
246 metadata.insert_tag("BC", "Backwards Compatibility for old API")
247
248 Remarks:
249 Subsequent calls to insert_tag with the same tag are safe (they will
250 be ignored).
251 """
252 tag_ids = [tg.name for tg in self.tags if tg.name == tag]
253 if not tag_ids:
Igor Murashkin6ad61d42012-11-21 09:44:05 -0800254 self._tags.append(Tag(tag, self, description))
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800255
256 def insert_entry(self, entry):
257 """
258 Insert an entry into the metadata.
259
260 Args:
261 entry: A key-value dictionary describing an entry. Refer to
262 Entry#__init__ for the keys required/optional.
263
264 Remarks:
265 Subsequent calls to insert_entry with the same entry+kind name are safe
266 (they will be ignored).
267 """
268 e = Entry(**entry)
269 self._entries.append(e)
270 self._entry_map[e.kind][e.name] = e
Igor Murashkin586c8612012-11-29 17:08:36 -0800271 self._entries_ordered.append(e)
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800272
273 def insert_clone(self, clone):
274 """
275 Insert a clone into the metadata.
276
277 Args:
278 clone: A key-value dictionary describing a clone. Refer to
279 Clone#__init__ for the keys required/optional.
280
281 Remarks:
282 Subsequent calls to insert_clone with the same clone+kind name are safe
283 (they will be ignored). Also the target entry need not be inserted
284 ahead of the clone entry.
285 """
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800286 # 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
Igor Murashkinaa133d32013-06-28 17:27:49 -0700312 for ancestor in p.find_parents(lambda x: not isinstance(x, Metadata)):
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800313 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):
Igor Murashkin5804a482012-12-05 13:06:59 -0800432 for kind in section.kinds:
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800433 kind._leafs = []
434 section.validate_tree()
435
Igor Murashkin5804a482012-12-05 13:06:59 -0800436 group_entry_by_kind = itertools.groupby(section._leafs, lambda x: x.kind)
437 leaf_it = ((k, g) for k, g in group_entry_by_kind)
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800438
Igor Murashkin5804a482012-12-05 13:06:59 -0800439 # allow multiple kinds with the same name. merge if adjacent
440 # e.g. dynamic,dynamic,static,static,dynamic -> dynamic,static,dynamic
441 # this helps maintain ABI compatibility when adding an entry in a new kind
442 for idx, (kind_name, entry_it) in enumerate(leaf_it):
443 if idx >= len(section._kinds):
444 kind = Kind(kind_name, section)
445 section._kinds.append(kind)
446 section.validate_tree()
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800447
Igor Murashkin5804a482012-12-05 13:06:59 -0800448 kind = section._kinds[idx]
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800449
Igor Murashkin5804a482012-12-05 13:06:59 -0800450 for p in entry_it:
451 if p not in kind._leafs:
452 kind._leafs.append(p)
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800453
Igor Murashkin5804a482012-12-05 13:06:59 -0800454 for kind in section._kinds:
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800455 kind.validate_tree()
456 self._construct_inner_namespaces(kind)
457 kind.validate_tree()
458 self._construct_entries(kind)
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800459 kind.validate_tree()
460
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800461 if not section.validate_tree():
462 print >> sys.stderr, ("ERROR: Failed to validate tree in " + \
463 "construct_kinds, with kind = '%s'") %(kind)
464
465 if not kind.validate_tree():
466 print >> sys.stderr, ("ERROR: Failed to validate tree in " + \
467 "construct_kinds, with kind = '%s'") %(kind)
468
469 def _construct_inner_namespaces(self, parent, depth=0):
470 #parent is InnerNamespace or Kind
471 ins_dict = self._dictionary_by_name(parent.namespaces)
472 for name, ins in ins_dict.iteritems():
473 ins._leafs = []
474
475 for p in parent._leafs:
476 ins_list = p.get_inner_namespace_list()
477
478 if len(ins_list) > depth:
479 ins_str = ins_list[depth]
480 ins = ins_dict.get(ins_str, InnerNamespace(ins_str, parent))
481 ins_dict[ins_str] = ins
482
483 if p not in ins._leafs:
484 ins._leafs.append(p)
485
486 for name, ins in ins_dict.iteritems():
487 ins.validate_tree()
488 # construct children INS
489 self._construct_inner_namespaces(ins, depth + 1)
490 ins.validate_tree()
491 # construct children entries
492 self._construct_entries(ins, depth + 1)
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800493
494 if ins not in parent.namespaces:
495 parent._namespaces.append(ins)
496
497 if not ins.validate_tree():
498 print >> sys.stderr, ("ERROR: Failed to validate tree in " + \
499 "construct_inner_namespaces, with ins = '%s'") \
500 %(ins)
501
502 # doesnt construct the entries, so much as links them
503 def _construct_entries(self, parent, depth=0):
504 #parent is InnerNamespace or Kind
505 entry_dict = self._dictionary_by_name(parent.entries)
506 for p in parent._leafs:
507 ins_list = p.get_inner_namespace_list()
508
509 if len(ins_list) == depth:
510 entry = entry_dict.get(p.name, p)
511 entry_dict[p.name] = entry
512
513 for name, entry in entry_dict.iteritems():
514
515 old_parent = entry.parent
516 entry._parent = parent
517
518 if entry not in parent.entries:
519 parent._entries.append(entry)
520
521 if old_parent is not None and old_parent != parent:
522 print >> sys.stderr, ("ERROR: Parent changed from '%s' to '%s' for " + \
523 "entry '%s'") \
524 %(old_parent.name, parent.name, entry.name)
525
526 def _get_children(self):
527 if self.outer_namespaces is not None:
528 for i in self.outer_namespaces:
529 yield i
530
531 if self.tags is not None:
532 for i in self.tags:
533 yield i
534
535class Tag(Node):
536 """
537 A tag Node corresponding to a top-level <tag> element.
538
539 Attributes (Read-Only):
540 name: alias for id
541 id: The name of the tag, e.g. for <tag id="BC"/> id = 'BC'
542 description: The description of the tag, the contents of the <tag> element.
543 parent: An edge to the parent, which is always the Metadata root node.
544 entries: A sequence of edges to entries/clones that are using this Tag.
545 """
546 def __init__(self, name, parent, description=""):
547 self._name = name # 'id' attribute in XML
548 self._id = name
549 self._description = description
550 self._parent = parent
551
552 # all entries that have this tag, including clones
Igor Murashkin617da162012-11-29 13:35:15 -0800553 self._entries = [] # filled in by Metadata#construct_tags
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800554
555 @property
556 def id(self):
557 return self._id
558
559 @property
560 def description(self):
561 return self._description
562
563 @property
564 def entries(self):
565 return (i for i in self._entries)
566
567 def _get_children(self):
568 return None
569
570class OuterNamespace(Node):
571 """
572 A node corresponding to a <namespace> element under <metadata>
573
574 Attributes (Read-Only):
575 name: The name attribute of the <namespace name="foo"> element.
576 parent: An edge to the parent, which is always the Metadata root node.
577 sections: A sequence of Section children.
578 """
579 def __init__(self, name, parent, sections=[]):
580 self._name = name
581 self._parent = parent # MetadataSet
582 self._sections = sections[:]
583 self._leafs = []
584
585 self._children = self._sections
586
587 @property
588 def sections(self):
589 return (i for i in self._sections)
590
591class Section(Node):
592 """
593 A node corresponding to a <section> element under <namespace>
594
595 Attributes (Read-Only):
596 name: The name attribute of the <section name="foo"> element.
597 parent: An edge to the parent, which is always an OuterNamespace instance.
598 description: A string description of the section, or None.
599 kinds: A sequence of Kind children.
Igor Murashkin5804a482012-12-05 13:06:59 -0800600 merged_kinds: A sequence of virtual Kind children,
601 with each Kind's children merged by the kind.name
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800602 """
603 def __init__(self, name, parent, description=None, kinds=[]):
604 self._name = name
605 self._parent = parent
606 self._description = description
607 self._kinds = kinds[:]
608
609 self._leafs = []
610
611
612 @property
613 def description(self):
614 return self._description
615
616 @property
617 def kinds(self):
618 return (i for i in self._kinds)
619
620 def sort_children(self):
621 self.validate_tree()
622 # order is always controls,static,dynamic
623 find_child = lambda x: [i for i in self._get_children() if i.name == x]
624 new_lst = find_child('controls') \
625 + find_child('static') \
626 + find_child('dynamic')
627 self._kinds = new_lst
628 self.validate_tree()
629
630 def _get_children(self):
631 return (i for i in self.kinds)
632
Igor Murashkin5804a482012-12-05 13:06:59 -0800633 @property
634 def merged_kinds(self):
635
636 def aggregate_by_name(acc, el):
637 existing = [i for i in acc if i.name == el.name]
638 if existing:
639 k = existing[0]
640 else:
641 k = Kind(el.name, el.parent)
642 acc.append(k)
643
644 k._namespaces.extend(el._namespaces)
645 k._entries.extend(el._entries)
646
647 return acc
648
649 new_kinds_lst = reduce(aggregate_by_name, self.kinds, [])
650
651 for k in new_kinds_lst:
652 yield k
653
Igor Murashkinaa133d32013-06-28 17:27:49 -0700654 def combine_kinds_into_single_node(self):
655 r"""
656 Combines the section's Kinds into a single node.
657
658 Combines all the children (kinds) of this section into a single
659 virtual Kind node.
660
661 Returns:
662 A new Kind node that collapses all Kind siblings into one, combining
663 all their children together.
664
665 For example, given self.kinds == [ x, y ]
666
667 x y z
668 / | | \ --> / | | \
669 a b c d a b c d
670
671 a new instance z is returned in this example.
672
673 Remarks:
674 The children of the kinds are the same references as before, that is
675 their parents will point to the old parents and not to the new parent.
676 """
677 combined = Kind(name="combined", parent=self)
678
679 for k in self._get_children():
680 combined._namespaces.extend(k.namespaces)
681 combined._entries.extend(k.entries)
682
683 return combined
684
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800685class Kind(Node):
686 """
687 A node corresponding to one of: <static>,<dynamic>,<controls> under a
688 <section> element.
689
690 Attributes (Read-Only):
691 name: A string which is one of 'static', 'dynamic, or 'controls'.
692 parent: An edge to the parent, which is always a Section instance.
693 namespaces: A sequence of InnerNamespace children.
694 entries: A sequence of Entry/Clone children.
Igor Murashkin617da162012-11-29 13:35:15 -0800695 merged_entries: A sequence of MergedEntry virtual nodes from entries
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800696 """
697 def __init__(self, name, parent):
698 self._name = name
699 self._parent = parent
700 self._namespaces = []
701 self._entries = []
702
703 self._leafs = []
704
705 @property
706 def namespaces(self):
707 return self._namespaces
708
709 @property
710 def entries(self):
711 return self._entries
712
Igor Murashkin617da162012-11-29 13:35:15 -0800713 @property
714 def merged_entries(self):
715 for i in self.entries:
716 yield i.merge()
717
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800718 def sort_children(self):
719 self._namespaces.sort(key=self._get_name())
720 self._entries.sort(key=self._get_name())
721
722 def _get_children(self):
723 for i in self.namespaces:
724 yield i
725 for i in self.entries:
726 yield i
727
Igor Murashkinaa133d32013-06-28 17:27:49 -0700728 def combine_children_by_name(self):
729 r"""
730 Combine multiple children with the same name into a single node.
731
732 Returns:
733 A new Kind where all of the children with the same name were combined.
734
735 For example:
736
737 Given a Kind k:
738
739 k
740 / | \
741 a b c
742 | | |
743 d e f
744
745 a.name == "foo"
746 b.name == "foo"
747 c.name == "bar"
748
749 The returned Kind will look like this:
750
751 k'
752 / \
753 a' c'
754 / | |
755 d e f
756
757 Remarks:
758 This operation is not recursive. To combine the grandchildren and other
759 ancestors, call this method on the ancestor nodes.
760 """
761 return Kind._combine_children_by_name(self, new_type=type(self))
762
763 # new_type is either Kind or InnerNamespace
764 @staticmethod
765 def _combine_children_by_name(self, new_type):
766 new_ins_dict = OrderedDict()
767 new_ent_dict = OrderedDict()
768
769 for ins in self.namespaces:
770 new_ins = new_ins_dict.setdefault(ins.name,
771 InnerNamespace(ins.name, parent=self))
772 new_ins._namespaces.extend(ins.namespaces)
773 new_ins._entries.extend(ins.entries)
774
775 for ent in self.entries:
776 new_ent = new_ent_dict.setdefault(ent.name,
777 ent.merge())
778
779 kind = new_type(self.name, self.parent)
780 kind._namespaces = new_ins_dict.values()
781 kind._entries = new_ent_dict.values()
782
783 return kind
784
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800785class InnerNamespace(Node):
786 """
787 A node corresponding to a <namespace> which is an ancestor of a Kind.
788 These namespaces may have other namespaces recursively, or entries as leafs.
789
790 Attributes (Read-Only):
791 name: Name attribute from the element, e.g. <namespace name="foo"> -> 'foo'
792 parent: An edge to the parent, which is an InnerNamespace or a Kind.
793 namespaces: A sequence of InnerNamespace children.
794 entries: A sequence of Entry/Clone children.
Igor Murashkin617da162012-11-29 13:35:15 -0800795 merged_entries: A sequence of MergedEntry virtual nodes from entries
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800796 """
797 def __init__(self, name, parent):
798 self._name = name
799 self._parent = parent
800 self._namespaces = []
801 self._entries = []
802 self._leafs = []
803
804 @property
805 def namespaces(self):
806 return self._namespaces
807
808 @property
809 def entries(self):
810 return self._entries
811
Igor Murashkin617da162012-11-29 13:35:15 -0800812 @property
813 def merged_entries(self):
814 for i in self.entries:
815 yield i.merge()
816
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800817 def sort_children(self):
818 self._namespaces.sort(key=self._get_name())
819 self._entries.sort(key=self._get_name())
820
821 def _get_children(self):
822 for i in self.namespaces:
823 yield i
824 for i in self.entries:
825 yield i
826
Igor Murashkinaa133d32013-06-28 17:27:49 -0700827 def combine_children_by_name(self):
828 r"""
829 Combine multiple children with the same name into a single node.
830
831 Returns:
832 A new InnerNamespace where all of the children with the same name were
833 combined.
834
835 For example:
836
837 Given an InnerNamespace i:
838
839 i
840 / | \
841 a b c
842 | | |
843 d e f
844
845 a.name == "foo"
846 b.name == "foo"
847 c.name == "bar"
848
849 The returned InnerNamespace will look like this:
850
851 i'
852 / \
853 a' c'
854 / | |
855 d e f
856
857 Remarks:
858 This operation is not recursive. To combine the grandchildren and other
859 ancestors, call this method on the ancestor nodes.
860 """
861 return Kind._combine_children_by_name(self, new_type=type(self))
862
Igor Murashkin375cfd32012-12-03 13:55:33 -0800863class EnumValue(Node):
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800864 """
865 A class corresponding to a <value> element within an <enum> within an <entry>.
866
867 Attributes (Read-Only):
868 name: A string, e.g. 'ON' or 'OFF'
869 id: An optional numeric string, e.g. '0' or '0xFF'
870 optional: A boolean
871 notes: A string describing the notes, or None.
Igor Murashkin375cfd32012-12-03 13:55:33 -0800872 parent: An edge to the parent, always an Enum instance.
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800873 """
Igor Murashkin375cfd32012-12-03 13:55:33 -0800874 def __init__(self, name, parent, id=None, optional=False, notes=None):
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800875 self._name = name # str, e.g. 'ON' or 'OFF'
876 self._id = id # int, e.g. '0'
877 self._optional = optional # bool
878 self._notes = notes # None or str
Igor Murashkin375cfd32012-12-03 13:55:33 -0800879 self._parent = parent
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800880
881 @property
882 def id(self):
883 return self._id
884
885 @property
886 def optional(self):
887 return self._optional
888
889 @property
890 def notes(self):
891 return self._notes
892
Igor Murashkin375cfd32012-12-03 13:55:33 -0800893 def _get_children(self):
894 return None
895
896class Enum(Node):
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800897 """
898 A class corresponding to an <enum> element within an <entry>.
899
900 Attributes (Read-Only):
901 parent: An edge to the parent, always an Entry instance.
902 values: A sequence of EnumValue children.
Igor Murashkinaa133d32013-06-28 17:27:49 -0700903 has_values_with_id: A boolean representing if any of the children have a
904 non-empty id property.
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800905 """
906 def __init__(self, parent, values, ids={}, optionals=[], notes={}):
907 self._values = \
Igor Murashkin375cfd32012-12-03 13:55:33 -0800908 [ EnumValue(val, self, ids.get(val), val in optionals, notes.get(val)) \
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800909 for val in values ]
910
911 self._parent = parent
Igor Murashkin375cfd32012-12-03 13:55:33 -0800912 self._name = None
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800913
914 @property
915 def values(self):
916 return (i for i in self._values)
917
Igor Murashkinaa133d32013-06-28 17:27:49 -0700918 @property
919 def has_values_with_id(self):
920 return bool(any(i for i in self.values if i.id))
921
Igor Murashkin375cfd32012-12-03 13:55:33 -0800922 def _get_children(self):
923 return (i for i in self._values)
924
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800925class Entry(Node):
926 """
927 A node corresponding to an <entry> element.
928
929 Attributes (Read-Only):
930 parent: An edge to the parent node, which is an InnerNamespace or Kind.
931 name: The fully qualified name string, e.g. 'android.shading.mode'
932 name_short: The name attribute from <entry name="mode">, e.g. mode
933 type: The type attribute from <entry type="bar">
934 kind: A string ('static', 'dynamic', 'controls') corresponding to the
935 ancestor Kind#name
936 container: The container attribute from <entry container="array">, or None.
937 container_sizes: A sequence of size strings or None if container is None.
Igor Murashkinb556bc42012-12-04 16:07:21 -0800938 enum: An Enum instance if the enum attribute is true, None otherwise.
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800939 tuple_values: A sequence of strings describing the tuple values,
940 None if container is not 'tuple'.
941 description: A string description, or None.
942 range: A string range, or None.
943 units: A string units, or None.
944 tags: A sequence of Tag nodes associated with this Entry.
945 type_notes: A string describing notes for the type, or None.
946
947 Remarks:
948 Subclass Clone can be used interchangeable with an Entry,
949 for when we don't care about the underlying type.
950
951 parent and tags edges are invalid until after Metadata#construct_graph
952 has been invoked.
953 """
954 def __init__(self, **kwargs):
955 """
956 Instantiate a new Entry node.
957
958 Args:
959 name: A string with the fully qualified name, e.g. 'android.shading.mode'
960 type: A string describing the type, e.g. 'int32'
961 kind: A string describing the kind, e.g. 'static'
962
963 Args (if container):
964 container: A string describing the container, e.g. 'array' or 'tuple'
965 container_sizes: A list of string sizes if a container, or None otherwise
966
967 Args (if container is 'tuple'):
968 tuple_values: A list of tuple values, e.g. ['width', 'height']
969
Igor Murashkinb556bc42012-12-04 16:07:21 -0800970 Args (if the 'enum' attribute is true):
971 enum: A boolean, True if this is an enum, False otherwise
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800972 enum_values: A list of value strings, e.g. ['ON', 'OFF']
973 enum_optionals: A list of optional enum values, e.g. ['OFF']
974 enum_notes: A dictionary of value->notes strings.
975 enum_ids: A dictionary of value->id strings.
976
977 Args (optional):
978 description: A string with a description of the entry.
979 range: A string with the range of the values of the entry, e.g. '>= 0'
980 units: A string with the units of the values, e.g. 'inches'
981 notes: A string with the notes for the entry
982 tag_ids: A list of tag ID strings, e.g. ['BC', 'V1']
983 type_notes: A string with the notes for the type
984 """
985
986 if kwargs.get('type') is None:
987 print >> sys.stderr, "ERROR: Missing type for entry '%s' kind '%s'" \
988 %(kwargs.get('name'), kwargs.get('kind'))
989
990 # Attributes are Read-Only, but edges may be mutated by
991 # Metadata, particularly during construct_graph
992
993 self._name = kwargs['name']
994 self._type = kwargs['type']
995 self._kind = kwargs['kind'] # static, dynamic, or controls
996
997 self._init_common(**kwargs)
998
999 @property
1000 def type(self):
1001 return self._type
1002
1003 @property
1004 def kind(self):
1005 return self._kind
1006
1007 @property
1008 def name_short(self):
1009 return self.get_name_minimal()
1010
1011 @property
1012 def container(self):
1013 return self._container
1014
1015 @property
1016 def container_sizes(self):
1017 if self._container_sizes is None:
1018 return None
1019 else:
1020 return (i for i in self._container_sizes)
1021
1022 @property
1023 def tuple_values(self):
1024 if self._tuple_values is None:
1025 return None
1026 else:
1027 return (i for i in self._tuple_values)
1028
1029 @property
1030 def description(self):
1031 return self._description
1032
1033 @property
1034 def range(self):
1035 return self._range
1036
1037 @property
1038 def units(self):
1039 return self._units
1040
1041 @property
1042 def notes(self):
1043 return self._notes
1044
1045 @property
1046 def tags(self):
1047 if self._tags is None:
1048 return None
1049 else:
1050 return (i for i in self._tags)
1051
1052 @property
1053 def type_notes(self):
1054 return self._type_notes
1055
1056 @property
1057 def enum(self):
1058 return self._enum
1059
1060 def _get_children(self):
Igor Murashkin375cfd32012-12-03 13:55:33 -08001061 if self.enum:
1062 yield self.enum
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001063
1064 def sort_children(self):
1065 return None
1066
1067 def is_clone(self):
1068 """
1069 Whether or not this is a Clone instance.
1070
1071 Returns:
1072 False
1073 """
1074 return False
1075
1076 def _init_common(self, **kwargs):
1077
1078 self._parent = None # filled in by MetadataSet::_construct_entries
1079
1080 self._container = kwargs.get('container')
1081 self._container_sizes = kwargs.get('container_sizes')
1082
1083 # access these via the 'enum' prop
1084 enum_values = kwargs.get('enum_values')
1085 enum_optionals = kwargs.get('enum_optionals')
Igor Murashkinaa133d32013-06-28 17:27:49 -07001086 enum_notes = kwargs.get('enum_notes') # { value => notes }
1087 enum_ids = kwargs.get('enum_ids') # { value => notes }
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001088 self._tuple_values = kwargs.get('tuple_values')
1089
1090 self._description = kwargs.get('description')
1091 self._range = kwargs.get('range')
1092 self._units = kwargs.get('units')
1093 self._notes = kwargs.get('notes')
1094
1095 self._tag_ids = kwargs.get('tag_ids', [])
Igor Murashkinaa133d32013-06-28 17:27:49 -07001096 self._tags = None # Filled in by MetadataSet::_construct_tags
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001097
1098 self._type_notes = kwargs.get('type_notes')
1099
Igor Murashkinb556bc42012-12-04 16:07:21 -08001100 if kwargs.get('enum', False):
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001101 self._enum = Enum(self, enum_values, enum_ids, enum_optionals, enum_notes)
Igor Murashkin617da162012-11-29 13:35:15 -08001102 else:
1103 self._enum = None
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001104
1105 self._property_keys = kwargs
1106
Igor Murashkin617da162012-11-29 13:35:15 -08001107 def merge(self):
1108 """
1109 Copy the attributes into a new entry, merging it with the target entry
1110 if it's a clone.
1111 """
1112 return MergedEntry(self)
1113
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001114 # Helpers for accessing less than the fully qualified name
1115
1116 def get_name_as_list(self):
1117 """
1118 Returns the name as a list split by a period.
1119
1120 For example:
1121 entry.name is 'android.lens.info.shading'
1122 entry.get_name_as_list() == ['android', 'lens', 'info', 'shading']
1123 """
1124 return self.name.split(".")
1125
1126 def get_inner_namespace_list(self):
1127 """
1128 Returns the inner namespace part of the name as a list
1129
1130 For example:
1131 entry.name is 'android.lens.info.shading'
1132 entry.get_inner_namespace_list() == ['info']
1133 """
1134 return self.get_name_as_list()[2:-1]
1135
1136 def get_outer_namespace(self):
1137 """
1138 Returns the outer namespace as a string.
1139
1140 For example:
1141 entry.name is 'android.lens.info.shading'
1142 entry.get_outer_namespace() == 'android'
1143
1144 Remarks:
1145 Since outer namespaces are non-recursive,
1146 and each entry has one, this does not need to be a list.
1147 """
1148 return self.get_name_as_list()[0]
1149
1150 def get_section(self):
1151 """
1152 Returns the section as a string.
1153
1154 For example:
1155 entry.name is 'android.lens.info.shading'
1156 entry.get_section() == ''
1157
1158 Remarks:
1159 Since outer namespaces are non-recursive,
1160 and each entry has one, this does not need to be a list.
1161 """
1162 return self.get_name_as_list()[1]
1163
1164 def get_name_minimal(self):
1165 """
1166 Returns only the last component of the fully qualified name as a string.
1167
1168 For example:
1169 entry.name is 'android.lens.info.shading'
1170 entry.get_name_minimal() == 'shading'
1171
1172 Remarks:
1173 entry.name_short it an alias for this
1174 """
1175 return self.get_name_as_list()[-1]
1176
Igor Murashkin7b9a2dc2012-11-21 14:23:24 -08001177 def get_path_without_name(self):
1178 """
1179 Returns a string path to the entry, with the name component excluded.
1180
1181 For example:
1182 entry.name is 'android.lens.info.shading'
1183 entry.get_path_without_name() == 'android.lens.info'
1184 """
1185 return ".".join(self.get_name_as_list()[0:-1])
1186
1187
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001188class Clone(Entry):
1189 """
1190 A Node corresponding to a <clone> element. It has all the attributes of an
1191 <entry> element (Entry) plus the additions specified below.
1192
1193 Attributes (Read-Only):
1194 entry: an edge to an Entry object that this targets
1195 target_kind: A string describing the kind of the target entry.
1196 name: a string of the name, same as entry.name
1197 kind: a string of the Kind ancestor, one of 'static', 'controls', 'dynamic'
1198 for the <clone> element.
1199 type: always None, since a clone cannot override the type.
1200 """
1201 def __init__(self, entry=None, **kwargs):
1202 """
1203 Instantiate a new Clone node.
1204
1205 Args:
1206 name: A string with the fully qualified name, e.g. 'android.shading.mode'
1207 type: A string describing the type, e.g. 'int32'
1208 kind: A string describing the kind, e.g. 'static'
1209 target_kind: A string for the kind of the target entry, e.g. 'dynamic'
1210
1211 Args (if container):
1212 container: A string describing the container, e.g. 'array' or 'tuple'
1213 container_sizes: A list of string sizes if a container, or None otherwise
1214
1215 Args (if container is 'tuple'):
1216 tuple_values: A list of tuple values, e.g. ['width', 'height']
1217
Igor Murashkinb556bc42012-12-04 16:07:21 -08001218 Args (if the 'enum' attribute is true):
1219 enum: A boolean, True if this is an enum, False otherwise
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001220 enum_values: A list of value strings, e.g. ['ON', 'OFF']
1221 enum_optionals: A list of optional enum values, e.g. ['OFF']
1222 enum_notes: A dictionary of value->notes strings.
1223 enum_ids: A dictionary of value->id strings.
1224
1225 Args (optional):
1226 entry: An edge to the corresponding target Entry.
1227 description: A string with a description of the entry.
1228 range: A string with the range of the values of the entry, e.g. '>= 0'
1229 units: A string with the units of the values, e.g. 'inches'
1230 notes: A string with the notes for the entry
1231 tag_ids: A list of tag ID strings, e.g. ['BC', 'V1']
1232 type_notes: A string with the notes for the type
1233
1234 Remarks:
1235 Note that type is not specified since it has to be the same as the
1236 entry.type.
1237 """
Igor Murashkinaa133d32013-06-28 17:27:49 -07001238 self._entry = entry # Entry object
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001239 self._target_kind = kwargs['target_kind']
Igor Murashkinaa133d32013-06-28 17:27:49 -07001240 self._name = kwargs['name'] # same as entry.name
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001241 self._kind = kwargs['kind']
1242
1243 # illegal to override the type, it should be the same as the entry
1244 self._type = None
1245 # the rest of the kwargs are optional
1246 # can be used to override the regular entry data
1247 self._init_common(**kwargs)
1248
1249 @property
1250 def entry(self):
1251 return self._entry
1252
1253 @property
1254 def target_kind(self):
1255 return self._target_kind
1256
1257 def is_clone(self):
1258 """
1259 Whether or not this is a Clone instance.
1260
1261 Returns:
1262 True
1263 """
1264 return True
1265
Igor Murashkin617da162012-11-29 13:35:15 -08001266class MergedEntry(Entry):
1267 """
1268 A MergedEntry has all the attributes of a Clone and its target Entry merged
1269 together.
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001270
Igor Murashkin617da162012-11-29 13:35:15 -08001271 Remarks:
1272 Useful when we want to 'unfold' a clone into a real entry by copying out
1273 the target entry data. In this case we don't care about distinguishing
1274 a clone vs an entry.
1275 """
1276 def __init__(self, entry):
1277 """
1278 Create a new instance of MergedEntry.
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001279
Igor Murashkin617da162012-11-29 13:35:15 -08001280 Args:
1281 entry: An Entry or Clone instance
1282 """
1283 props_distinct = ['description', 'units', 'range', 'notes', 'tags', 'kind']
1284
1285 for p in props_distinct:
Igor Murashkinbaacf9a2012-12-05 14:49:11 -08001286 p = '_' + p
Igor Murashkin617da162012-11-29 13:35:15 -08001287 if entry.is_clone():
Igor Murashkinbaacf9a2012-12-05 14:49:11 -08001288 setattr(self, p, getattr(entry, p) or getattr(entry.entry, p))
Igor Murashkin617da162012-11-29 13:35:15 -08001289 else:
Igor Murashkinbaacf9a2012-12-05 14:49:11 -08001290 setattr(self, p, getattr(entry, p))
Igor Murashkin617da162012-11-29 13:35:15 -08001291
Igor Murashkinbaacf9a2012-12-05 14:49:11 -08001292 props_common = ['parent', 'name', 'container',
Igor Murashkin617da162012-11-29 13:35:15 -08001293 'container_sizes', 'enum',
1294 'tuple_values',
1295 'type',
1296 'type_notes',
Igor Murashkin617da162012-11-29 13:35:15 -08001297 ]
1298
1299 for p in props_common:
Igor Murashkinbaacf9a2012-12-05 14:49:11 -08001300 p = '_' + p
Igor Murashkin617da162012-11-29 13:35:15 -08001301 if entry.is_clone():
Igor Murashkinbaacf9a2012-12-05 14:49:11 -08001302 setattr(self, p, getattr(entry.entry, p))
Igor Murashkin617da162012-11-29 13:35:15 -08001303 else:
Igor Murashkinbaacf9a2012-12-05 14:49:11 -08001304 setattr(self, p, getattr(entry, p))