blob: 6b70288dfb03c2a503a53e0789bd67ab09b62e08 [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.
Eino-Ville Talvalaf384f0a2013-07-12 17:02:27 -0700939 visibility: The visibility of this entry ('system', 'hidden', 'public')
940 across the system. System entries are only visible in native code
941 headers. Hidden entries are marked @hide in managed code, while
942 public entries are visible in the Android SDK.
943 applied_visibility: As visibility, but always valid, defaulting to 'system'
944 if no visibility is given for an entry.
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800945 tuple_values: A sequence of strings describing the tuple values,
946 None if container is not 'tuple'.
947 description: A string description, or None.
948 range: A string range, or None.
949 units: A string units, or None.
950 tags: A sequence of Tag nodes associated with this Entry.
951 type_notes: A string describing notes for the type, or None.
952
953 Remarks:
954 Subclass Clone can be used interchangeable with an Entry,
955 for when we don't care about the underlying type.
956
957 parent and tags edges are invalid until after Metadata#construct_graph
958 has been invoked.
959 """
960 def __init__(self, **kwargs):
961 """
962 Instantiate a new Entry node.
963
964 Args:
965 name: A string with the fully qualified name, e.g. 'android.shading.mode'
966 type: A string describing the type, e.g. 'int32'
967 kind: A string describing the kind, e.g. 'static'
968
969 Args (if container):
970 container: A string describing the container, e.g. 'array' or 'tuple'
971 container_sizes: A list of string sizes if a container, or None otherwise
972
973 Args (if container is 'tuple'):
974 tuple_values: A list of tuple values, e.g. ['width', 'height']
975
Igor Murashkinb556bc42012-12-04 16:07:21 -0800976 Args (if the 'enum' attribute is true):
977 enum: A boolean, True if this is an enum, False otherwise
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800978 enum_values: A list of value strings, e.g. ['ON', 'OFF']
979 enum_optionals: A list of optional enum values, e.g. ['OFF']
980 enum_notes: A dictionary of value->notes strings.
981 enum_ids: A dictionary of value->id strings.
982
983 Args (optional):
984 description: A string with a description of the entry.
985 range: A string with the range of the values of the entry, e.g. '>= 0'
986 units: A string with the units of the values, e.g. 'inches'
987 notes: A string with the notes for the entry
988 tag_ids: A list of tag ID strings, e.g. ['BC', 'V1']
989 type_notes: A string with the notes for the type
Eino-Ville Talvalaf384f0a2013-07-12 17:02:27 -0700990 visibility: A string describing the visibility, eg 'system', 'hidden',
991 'public'
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800992 """
993
994 if kwargs.get('type') is None:
995 print >> sys.stderr, "ERROR: Missing type for entry '%s' kind '%s'" \
996 %(kwargs.get('name'), kwargs.get('kind'))
997
998 # Attributes are Read-Only, but edges may be mutated by
999 # Metadata, particularly during construct_graph
1000
1001 self._name = kwargs['name']
1002 self._type = kwargs['type']
1003 self._kind = kwargs['kind'] # static, dynamic, or controls
1004
1005 self._init_common(**kwargs)
1006
1007 @property
1008 def type(self):
1009 return self._type
1010
1011 @property
1012 def kind(self):
1013 return self._kind
1014
1015 @property
Eino-Ville Talvalaf384f0a2013-07-12 17:02:27 -07001016 def visibility(self):
1017 return self._visibility
1018
1019 @property
1020 def applied_visibility(self):
1021 return self._visibility or 'system'
1022
1023 @property
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001024 def name_short(self):
1025 return self.get_name_minimal()
1026
1027 @property
1028 def container(self):
1029 return self._container
1030
1031 @property
1032 def container_sizes(self):
1033 if self._container_sizes is None:
1034 return None
1035 else:
1036 return (i for i in self._container_sizes)
1037
1038 @property
1039 def tuple_values(self):
1040 if self._tuple_values is None:
1041 return None
1042 else:
1043 return (i for i in self._tuple_values)
1044
1045 @property
1046 def description(self):
1047 return self._description
1048
1049 @property
1050 def range(self):
1051 return self._range
1052
1053 @property
1054 def units(self):
1055 return self._units
1056
1057 @property
1058 def notes(self):
1059 return self._notes
1060
1061 @property
1062 def tags(self):
1063 if self._tags is None:
1064 return None
1065 else:
1066 return (i for i in self._tags)
1067
1068 @property
1069 def type_notes(self):
1070 return self._type_notes
1071
1072 @property
1073 def enum(self):
1074 return self._enum
1075
1076 def _get_children(self):
Igor Murashkin375cfd32012-12-03 13:55:33 -08001077 if self.enum:
1078 yield self.enum
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001079
1080 def sort_children(self):
1081 return None
1082
1083 def is_clone(self):
1084 """
1085 Whether or not this is a Clone instance.
1086
1087 Returns:
1088 False
1089 """
1090 return False
1091
1092 def _init_common(self, **kwargs):
1093
1094 self._parent = None # filled in by MetadataSet::_construct_entries
1095
1096 self._container = kwargs.get('container')
1097 self._container_sizes = kwargs.get('container_sizes')
1098
1099 # access these via the 'enum' prop
1100 enum_values = kwargs.get('enum_values')
1101 enum_optionals = kwargs.get('enum_optionals')
Igor Murashkinaa133d32013-06-28 17:27:49 -07001102 enum_notes = kwargs.get('enum_notes') # { value => notes }
1103 enum_ids = kwargs.get('enum_ids') # { value => notes }
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001104 self._tuple_values = kwargs.get('tuple_values')
1105
1106 self._description = kwargs.get('description')
1107 self._range = kwargs.get('range')
1108 self._units = kwargs.get('units')
1109 self._notes = kwargs.get('notes')
1110
1111 self._tag_ids = kwargs.get('tag_ids', [])
Igor Murashkinaa133d32013-06-28 17:27:49 -07001112 self._tags = None # Filled in by MetadataSet::_construct_tags
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001113
1114 self._type_notes = kwargs.get('type_notes')
1115
Igor Murashkinb556bc42012-12-04 16:07:21 -08001116 if kwargs.get('enum', False):
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001117 self._enum = Enum(self, enum_values, enum_ids, enum_optionals, enum_notes)
Igor Murashkin617da162012-11-29 13:35:15 -08001118 else:
1119 self._enum = None
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001120
Eino-Ville Talvalaf384f0a2013-07-12 17:02:27 -07001121 self._visibility = kwargs.get('visibility')
1122
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001123 self._property_keys = kwargs
1124
Igor Murashkin617da162012-11-29 13:35:15 -08001125 def merge(self):
1126 """
1127 Copy the attributes into a new entry, merging it with the target entry
1128 if it's a clone.
1129 """
1130 return MergedEntry(self)
1131
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001132 # Helpers for accessing less than the fully qualified name
1133
1134 def get_name_as_list(self):
1135 """
1136 Returns the name as a list split by a period.
1137
1138 For example:
1139 entry.name is 'android.lens.info.shading'
1140 entry.get_name_as_list() == ['android', 'lens', 'info', 'shading']
1141 """
1142 return self.name.split(".")
1143
1144 def get_inner_namespace_list(self):
1145 """
1146 Returns the inner namespace part of the name as a list
1147
1148 For example:
1149 entry.name is 'android.lens.info.shading'
1150 entry.get_inner_namespace_list() == ['info']
1151 """
1152 return self.get_name_as_list()[2:-1]
1153
1154 def get_outer_namespace(self):
1155 """
1156 Returns the outer namespace as a string.
1157
1158 For example:
1159 entry.name is 'android.lens.info.shading'
1160 entry.get_outer_namespace() == 'android'
1161
1162 Remarks:
1163 Since outer namespaces are non-recursive,
1164 and each entry has one, this does not need to be a list.
1165 """
1166 return self.get_name_as_list()[0]
1167
1168 def get_section(self):
1169 """
1170 Returns the section as a string.
1171
1172 For example:
1173 entry.name is 'android.lens.info.shading'
1174 entry.get_section() == ''
1175
1176 Remarks:
1177 Since outer namespaces are non-recursive,
1178 and each entry has one, this does not need to be a list.
1179 """
1180 return self.get_name_as_list()[1]
1181
1182 def get_name_minimal(self):
1183 """
1184 Returns only the last component of the fully qualified name as a string.
1185
1186 For example:
1187 entry.name is 'android.lens.info.shading'
1188 entry.get_name_minimal() == 'shading'
1189
1190 Remarks:
1191 entry.name_short it an alias for this
1192 """
1193 return self.get_name_as_list()[-1]
1194
Igor Murashkin7b9a2dc2012-11-21 14:23:24 -08001195 def get_path_without_name(self):
1196 """
1197 Returns a string path to the entry, with the name component excluded.
1198
1199 For example:
1200 entry.name is 'android.lens.info.shading'
1201 entry.get_path_without_name() == 'android.lens.info'
1202 """
1203 return ".".join(self.get_name_as_list()[0:-1])
1204
1205
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001206class Clone(Entry):
1207 """
1208 A Node corresponding to a <clone> element. It has all the attributes of an
1209 <entry> element (Entry) plus the additions specified below.
1210
1211 Attributes (Read-Only):
1212 entry: an edge to an Entry object that this targets
1213 target_kind: A string describing the kind of the target entry.
1214 name: a string of the name, same as entry.name
1215 kind: a string of the Kind ancestor, one of 'static', 'controls', 'dynamic'
1216 for the <clone> element.
1217 type: always None, since a clone cannot override the type.
1218 """
1219 def __init__(self, entry=None, **kwargs):
1220 """
1221 Instantiate a new Clone node.
1222
1223 Args:
1224 name: A string with the fully qualified name, e.g. 'android.shading.mode'
1225 type: A string describing the type, e.g. 'int32'
1226 kind: A string describing the kind, e.g. 'static'
1227 target_kind: A string for the kind of the target entry, e.g. 'dynamic'
1228
1229 Args (if container):
1230 container: A string describing the container, e.g. 'array' or 'tuple'
1231 container_sizes: A list of string sizes if a container, or None otherwise
1232
1233 Args (if container is 'tuple'):
1234 tuple_values: A list of tuple values, e.g. ['width', 'height']
1235
Igor Murashkinb556bc42012-12-04 16:07:21 -08001236 Args (if the 'enum' attribute is true):
1237 enum: A boolean, True if this is an enum, False otherwise
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001238 enum_values: A list of value strings, e.g. ['ON', 'OFF']
1239 enum_optionals: A list of optional enum values, e.g. ['OFF']
1240 enum_notes: A dictionary of value->notes strings.
1241 enum_ids: A dictionary of value->id strings.
1242
1243 Args (optional):
1244 entry: An edge to the corresponding target Entry.
1245 description: A string with a description of the entry.
1246 range: A string with the range of the values of the entry, e.g. '>= 0'
1247 units: A string with the units of the values, e.g. 'inches'
1248 notes: A string with the notes for the entry
1249 tag_ids: A list of tag ID strings, e.g. ['BC', 'V1']
1250 type_notes: A string with the notes for the type
1251
1252 Remarks:
1253 Note that type is not specified since it has to be the same as the
1254 entry.type.
1255 """
Igor Murashkinaa133d32013-06-28 17:27:49 -07001256 self._entry = entry # Entry object
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001257 self._target_kind = kwargs['target_kind']
Igor Murashkinaa133d32013-06-28 17:27:49 -07001258 self._name = kwargs['name'] # same as entry.name
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001259 self._kind = kwargs['kind']
1260
1261 # illegal to override the type, it should be the same as the entry
1262 self._type = None
1263 # the rest of the kwargs are optional
1264 # can be used to override the regular entry data
1265 self._init_common(**kwargs)
1266
1267 @property
1268 def entry(self):
1269 return self._entry
1270
1271 @property
1272 def target_kind(self):
1273 return self._target_kind
1274
1275 def is_clone(self):
1276 """
1277 Whether or not this is a Clone instance.
1278
1279 Returns:
1280 True
1281 """
1282 return True
1283
Igor Murashkin617da162012-11-29 13:35:15 -08001284class MergedEntry(Entry):
1285 """
1286 A MergedEntry has all the attributes of a Clone and its target Entry merged
1287 together.
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001288
Igor Murashkin617da162012-11-29 13:35:15 -08001289 Remarks:
1290 Useful when we want to 'unfold' a clone into a real entry by copying out
1291 the target entry data. In this case we don't care about distinguishing
1292 a clone vs an entry.
1293 """
1294 def __init__(self, entry):
1295 """
1296 Create a new instance of MergedEntry.
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001297
Igor Murashkin617da162012-11-29 13:35:15 -08001298 Args:
1299 entry: An Entry or Clone instance
1300 """
1301 props_distinct = ['description', 'units', 'range', 'notes', 'tags', 'kind']
1302
1303 for p in props_distinct:
Igor Murashkinbaacf9a2012-12-05 14:49:11 -08001304 p = '_' + p
Igor Murashkin617da162012-11-29 13:35:15 -08001305 if entry.is_clone():
Igor Murashkinbaacf9a2012-12-05 14:49:11 -08001306 setattr(self, p, getattr(entry, p) or getattr(entry.entry, p))
Igor Murashkin617da162012-11-29 13:35:15 -08001307 else:
Igor Murashkinbaacf9a2012-12-05 14:49:11 -08001308 setattr(self, p, getattr(entry, p))
Igor Murashkin617da162012-11-29 13:35:15 -08001309
Igor Murashkinbaacf9a2012-12-05 14:49:11 -08001310 props_common = ['parent', 'name', 'container',
Igor Murashkin617da162012-11-29 13:35:15 -08001311 'container_sizes', 'enum',
1312 'tuple_values',
1313 'type',
1314 'type_notes',
Eino-Ville Talvalaf384f0a2013-07-12 17:02:27 -07001315 'visibility'
Igor Murashkin617da162012-11-29 13:35:15 -08001316 ]
1317
1318 for p in props_common:
Igor Murashkinbaacf9a2012-12-05 14:49:11 -08001319 p = '_' + p
Igor Murashkin617da162012-11-29 13:35:15 -08001320 if entry.is_clone():
Igor Murashkinbaacf9a2012-12-05 14:49:11 -08001321 setattr(self, p, getattr(entry.entry, p))
Igor Murashkin617da162012-11-29 13:35:15 -08001322 else:
Igor Murashkinbaacf9a2012-12-05 14:49:11 -08001323 setattr(self, p, getattr(entry, p))