blob: c1f4e0959ff959068559aea177b1acf560e0f006 [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 Murashkin5804a482012-12-05 13:06:59 -080037import itertools
Igor Murashkin586c8612012-11-29 17:08:36 -080038from collections import OrderedDict
Igor Murashkin77b63ca2012-11-09 16:15:02 -080039
40class Node(object):
41 """
42 Base class for most nodes that are part of the Metadata graph.
43
44 Attributes (Read-Only):
45 parent: An edge to a parent Node.
46 name: A string describing the name, usually but not always the 'name'
47 attribute of the corresponding XML node.
48 """
49
50 def __init__(self):
51 self._parent = None
52 self._name = None
53
54 @property
55 def parent(self):
56 return self._parent
57
58 @property
59 def name(self):
60 return self._name
61
62 def find_all(self, pred):
63 """
64 Find all descendants that match the predicate.
65
66 Args:
67 pred: a predicate function that acts as a filter for a Node
68
69 Yields:
70 A sequence of all descendants for which pred(node) is true,
71 in a pre-order visit order.
72 """
73 if pred(self):
74 yield self
75
76 if self._get_children() is None:
77 return
78
79 for i in self._get_children():
80 for j in i.find_all(pred):
81 yield j
82
83
84 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 = {}
150 for i in _get_children():
151 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 """
286 entry_name = clone['name']
287 # figure out corresponding entry later. allow clone insert, entry insert
288 entry = None
289 c = Clone(entry, **clone)
290 self._entry_map[c.kind][c.name] = c
291 self._clones.append(c)
Igor Murashkin586c8612012-11-29 17:08:36 -0800292 self._entries_ordered.append(c)
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800293
294 def prune_clones(self):
295 """
296 Remove all clones that don't point to an existing entry.
297
298 Remarks:
299 This should be called after all insert_entry/insert_clone calls have
300 finished.
301 """
302 remove_list = []
303 for p in self._clones:
304 if p.entry is None:
305 remove_list.append(p)
306
307 for p in remove_list:
308
309 # remove from parent's entries list
310 if p.parent is not None:
311 p.parent._entries.remove(p)
312 # remove from parents' _leafs list
313 for ancestor in p.find_parents(lambda x: not isinstance(x, MetadataSet)):
314 ancestor._leafs.remove(p)
315
316 # remove from global list
317 self._clones.remove(p)
318 self._entry_map[p.kind].pop(p.name)
Igor Murashkin586c8612012-11-29 17:08:36 -0800319 self._entries_ordered.remove(p)
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800320
321
322 # After all entries/clones are inserted,
323 # invoke this to generate the parent/child node graph all these objects
324 def construct_graph(self):
325 """
326 Generate the graph recursively, after which all Entry nodes will be
327 accessible recursively by crawling through the outer_namespaces sequence.
328
329 Remarks:
330 This is safe to be called multiple times at any time. It should be done at
331 least once or there will be no graph.
332 """
333 self.validate_tree()
334 self._construct_tags()
335 self.validate_tree()
336 self._construct_clones()
337 self.validate_tree()
338 self._construct_outer_namespaces()
339 self.validate_tree()
340
341 def _construct_tags(self):
342 tag_dict = self._dictionary_by_name(self.tags)
343 for p in self._get_properties():
344 p._tags = []
345 for tag_id in p._tag_ids:
346 tag = tag_dict.get(tag_id)
347
348 if tag not in p._tags:
349 p._tags.append(tag)
350
Igor Murashkin617da162012-11-29 13:35:15 -0800351 if p not in tag.entries:
352 tag._entries.append(p)
353
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800354 def _construct_clones(self):
355 for p in self._clones:
356 target_kind = p.target_kind
357 target_entry = self._entry_map[target_kind].get(p.name)
358 p._entry = target_entry
359
360 # should not throw if we pass validation
361 # but can happen when importing obsolete CSV entries
362 if target_entry is None:
363 print >> sys.stderr, ("WARNING: Clone entry '%s' target kind '%s'" + \
364 " has no corresponding entry") \
365 %(p.name, p.target_kind)
366
367 def _construct_outer_namespaces(self):
368
369 if self._outer_namespaces is None: #the first time this runs
370 self._outer_namespaces = []
371
372 root = self._dictionary_by_name(self._outer_namespaces)
373 for ons_name, ons in root.iteritems():
374 ons._leafs = []
375
Igor Murashkin586c8612012-11-29 17:08:36 -0800376 for p in self._entries_ordered:
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800377 ons_name = p.get_outer_namespace()
378 ons = root.get(ons_name, OuterNamespace(ons_name, self))
379 root[ons_name] = ons
380
381 if p not in ons._leafs:
382 ons._leafs.append(p)
383
384 for ons_name, ons in root.iteritems():
385
386 ons.validate_tree()
387
388 self._construct_sections(ons)
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800389
390 if ons not in self._outer_namespaces:
391 self._outer_namespaces.append(ons)
392
393 ons.validate_tree()
394
395 def _construct_sections(self, outer_namespace):
396
397 sections_dict = self._dictionary_by_name(outer_namespace.sections)
398 for sec_name, sec in sections_dict.iteritems():
399 sec._leafs = []
400 sec.validate_tree()
401
402 for p in outer_namespace._leafs:
403 does_exist = sections_dict.get(p.get_section())
404
405 sec = sections_dict.get(p.get_section(), \
406 Section(p.get_section(), outer_namespace))
407 sections_dict[p.get_section()] = sec
408
409 sec.validate_tree()
410
411 if p not in sec._leafs:
412 sec._leafs.append(p)
413
414 for sec_name, sec in sections_dict.iteritems():
415
416 if not sec.validate_tree():
417 print >> sys.stderr, ("ERROR: Failed to validate tree in " + \
418 "construct_sections (start), with section = '%s'")\
419 %(sec)
420
421 self._construct_kinds(sec)
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800422
423 if sec not in outer_namespace.sections:
424 outer_namespace._sections.append(sec)
425
426 if not sec.validate_tree():
427 print >> sys.stderr, ("ERROR: Failed to validate tree in " + \
428 "construct_sections (end), with section = '%s'") \
429 %(sec)
430
431 # 'controls', 'static' 'dynamic'. etc
432 def _construct_kinds(self, section):
Igor Murashkin5804a482012-12-05 13:06:59 -0800433 for kind in section.kinds:
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800434 kind._leafs = []
435 section.validate_tree()
436
Igor Murashkin5804a482012-12-05 13:06:59 -0800437 group_entry_by_kind = itertools.groupby(section._leafs, lambda x: x.kind)
438 leaf_it = ((k, g) for k, g in group_entry_by_kind)
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800439
Igor Murashkin5804a482012-12-05 13:06:59 -0800440 # allow multiple kinds with the same name. merge if adjacent
441 # e.g. dynamic,dynamic,static,static,dynamic -> dynamic,static,dynamic
442 # this helps maintain ABI compatibility when adding an entry in a new kind
443 for idx, (kind_name, entry_it) in enumerate(leaf_it):
444 if idx >= len(section._kinds):
445 kind = Kind(kind_name, section)
446 section._kinds.append(kind)
447 section.validate_tree()
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800448
Igor Murashkin5804a482012-12-05 13:06:59 -0800449 kind = section._kinds[idx]
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800450
Igor Murashkin5804a482012-12-05 13:06:59 -0800451 for p in entry_it:
452 if p not in kind._leafs:
453 kind._leafs.append(p)
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800454
Igor Murashkin5804a482012-12-05 13:06:59 -0800455 for kind in section._kinds:
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800456 kind.validate_tree()
457 self._construct_inner_namespaces(kind)
458 kind.validate_tree()
459 self._construct_entries(kind)
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800460 kind.validate_tree()
461
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800462 if not section.validate_tree():
463 print >> sys.stderr, ("ERROR: Failed to validate tree in " + \
464 "construct_kinds, with kind = '%s'") %(kind)
465
466 if not kind.validate_tree():
467 print >> sys.stderr, ("ERROR: Failed to validate tree in " + \
468 "construct_kinds, with kind = '%s'") %(kind)
469
470 def _construct_inner_namespaces(self, parent, depth=0):
471 #parent is InnerNamespace or Kind
472 ins_dict = self._dictionary_by_name(parent.namespaces)
473 for name, ins in ins_dict.iteritems():
474 ins._leafs = []
475
476 for p in parent._leafs:
477 ins_list = p.get_inner_namespace_list()
478
479 if len(ins_list) > depth:
480 ins_str = ins_list[depth]
481 ins = ins_dict.get(ins_str, InnerNamespace(ins_str, parent))
482 ins_dict[ins_str] = ins
483
484 if p not in ins._leafs:
485 ins._leafs.append(p)
486
487 for name, ins in ins_dict.iteritems():
488 ins.validate_tree()
489 # construct children INS
490 self._construct_inner_namespaces(ins, depth + 1)
491 ins.validate_tree()
492 # construct children entries
493 self._construct_entries(ins, depth + 1)
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800494
495 if ins not in parent.namespaces:
496 parent._namespaces.append(ins)
497
498 if not ins.validate_tree():
499 print >> sys.stderr, ("ERROR: Failed to validate tree in " + \
500 "construct_inner_namespaces, with ins = '%s'") \
501 %(ins)
502
503 # doesnt construct the entries, so much as links them
504 def _construct_entries(self, parent, depth=0):
505 #parent is InnerNamespace or Kind
506 entry_dict = self._dictionary_by_name(parent.entries)
507 for p in parent._leafs:
508 ins_list = p.get_inner_namespace_list()
509
510 if len(ins_list) == depth:
511 entry = entry_dict.get(p.name, p)
512 entry_dict[p.name] = entry
513
514 for name, entry in entry_dict.iteritems():
515
516 old_parent = entry.parent
517 entry._parent = parent
518
519 if entry not in parent.entries:
520 parent._entries.append(entry)
521
522 if old_parent is not None and old_parent != parent:
523 print >> sys.stderr, ("ERROR: Parent changed from '%s' to '%s' for " + \
524 "entry '%s'") \
525 %(old_parent.name, parent.name, entry.name)
526
527 def _get_children(self):
528 if self.outer_namespaces is not None:
529 for i in self.outer_namespaces:
530 yield i
531
532 if self.tags is not None:
533 for i in self.tags:
534 yield i
535
536class Tag(Node):
537 """
538 A tag Node corresponding to a top-level <tag> element.
539
540 Attributes (Read-Only):
541 name: alias for id
542 id: The name of the tag, e.g. for <tag id="BC"/> id = 'BC'
543 description: The description of the tag, the contents of the <tag> element.
544 parent: An edge to the parent, which is always the Metadata root node.
545 entries: A sequence of edges to entries/clones that are using this Tag.
546 """
547 def __init__(self, name, parent, description=""):
548 self._name = name # 'id' attribute in XML
549 self._id = name
550 self._description = description
551 self._parent = parent
552
553 # all entries that have this tag, including clones
Igor Murashkin617da162012-11-29 13:35:15 -0800554 self._entries = [] # filled in by Metadata#construct_tags
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800555
556 @property
557 def id(self):
558 return self._id
559
560 @property
561 def description(self):
562 return self._description
563
564 @property
565 def entries(self):
566 return (i for i in self._entries)
567
568 def _get_children(self):
569 return None
570
571class OuterNamespace(Node):
572 """
573 A node corresponding to a <namespace> element under <metadata>
574
575 Attributes (Read-Only):
576 name: The name attribute of the <namespace name="foo"> element.
577 parent: An edge to the parent, which is always the Metadata root node.
578 sections: A sequence of Section children.
579 """
580 def __init__(self, name, parent, sections=[]):
581 self._name = name
582 self._parent = parent # MetadataSet
583 self._sections = sections[:]
584 self._leafs = []
585
586 self._children = self._sections
587
588 @property
589 def sections(self):
590 return (i for i in self._sections)
591
592class Section(Node):
593 """
594 A node corresponding to a <section> element under <namespace>
595
596 Attributes (Read-Only):
597 name: The name attribute of the <section name="foo"> element.
598 parent: An edge to the parent, which is always an OuterNamespace instance.
599 description: A string description of the section, or None.
600 kinds: A sequence of Kind children.
Igor Murashkin5804a482012-12-05 13:06:59 -0800601 merged_kinds: A sequence of virtual Kind children,
602 with each Kind's children merged by the kind.name
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800603 """
604 def __init__(self, name, parent, description=None, kinds=[]):
605 self._name = name
606 self._parent = parent
607 self._description = description
608 self._kinds = kinds[:]
609
610 self._leafs = []
611
612
613 @property
614 def description(self):
615 return self._description
616
617 @property
618 def kinds(self):
619 return (i for i in self._kinds)
620
621 def sort_children(self):
622 self.validate_tree()
623 # order is always controls,static,dynamic
624 find_child = lambda x: [i for i in self._get_children() if i.name == x]
625 new_lst = find_child('controls') \
626 + find_child('static') \
627 + find_child('dynamic')
628 self._kinds = new_lst
629 self.validate_tree()
630
631 def _get_children(self):
632 return (i for i in self.kinds)
633
Igor Murashkin5804a482012-12-05 13:06:59 -0800634 @property
635 def merged_kinds(self):
636
637 def aggregate_by_name(acc, el):
638 existing = [i for i in acc if i.name == el.name]
639 if existing:
640 k = existing[0]
641 else:
642 k = Kind(el.name, el.parent)
643 acc.append(k)
644
645 k._namespaces.extend(el._namespaces)
646 k._entries.extend(el._entries)
647
648 return acc
649
650 new_kinds_lst = reduce(aggregate_by_name, self.kinds, [])
651
652 for k in new_kinds_lst:
653 yield k
654
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800655class Kind(Node):
656 """
657 A node corresponding to one of: <static>,<dynamic>,<controls> under a
658 <section> element.
659
660 Attributes (Read-Only):
661 name: A string which is one of 'static', 'dynamic, or 'controls'.
662 parent: An edge to the parent, which is always a Section instance.
663 namespaces: A sequence of InnerNamespace children.
664 entries: A sequence of Entry/Clone children.
Igor Murashkin617da162012-11-29 13:35:15 -0800665 merged_entries: A sequence of MergedEntry virtual nodes from entries
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800666 """
667 def __init__(self, name, parent):
668 self._name = name
669 self._parent = parent
670 self._namespaces = []
671 self._entries = []
672
673 self._leafs = []
674
675 @property
676 def namespaces(self):
677 return self._namespaces
678
679 @property
680 def entries(self):
681 return self._entries
682
Igor Murashkin617da162012-11-29 13:35:15 -0800683 @property
684 def merged_entries(self):
685 for i in self.entries:
686 yield i.merge()
687
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800688 def sort_children(self):
689 self._namespaces.sort(key=self._get_name())
690 self._entries.sort(key=self._get_name())
691
692 def _get_children(self):
693 for i in self.namespaces:
694 yield i
695 for i in self.entries:
696 yield i
697
698class InnerNamespace(Node):
699 """
700 A node corresponding to a <namespace> which is an ancestor of a Kind.
701 These namespaces may have other namespaces recursively, or entries as leafs.
702
703 Attributes (Read-Only):
704 name: Name attribute from the element, e.g. <namespace name="foo"> -> 'foo'
705 parent: An edge to the parent, which is an InnerNamespace or a Kind.
706 namespaces: A sequence of InnerNamespace children.
707 entries: A sequence of Entry/Clone children.
Igor Murashkin617da162012-11-29 13:35:15 -0800708 merged_entries: A sequence of MergedEntry virtual nodes from entries
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800709 """
710 def __init__(self, name, parent):
711 self._name = name
712 self._parent = parent
713 self._namespaces = []
714 self._entries = []
715 self._leafs = []
716
717 @property
718 def namespaces(self):
719 return self._namespaces
720
721 @property
722 def entries(self):
723 return self._entries
724
Igor Murashkin617da162012-11-29 13:35:15 -0800725 @property
726 def merged_entries(self):
727 for i in self.entries:
728 yield i.merge()
729
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800730 def sort_children(self):
731 self._namespaces.sort(key=self._get_name())
732 self._entries.sort(key=self._get_name())
733
734 def _get_children(self):
735 for i in self.namespaces:
736 yield i
737 for i in self.entries:
738 yield i
739
Igor Murashkin375cfd32012-12-03 13:55:33 -0800740class EnumValue(Node):
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800741 """
742 A class corresponding to a <value> element within an <enum> within an <entry>.
743
744 Attributes (Read-Only):
745 name: A string, e.g. 'ON' or 'OFF'
746 id: An optional numeric string, e.g. '0' or '0xFF'
747 optional: A boolean
748 notes: A string describing the notes, or None.
Igor Murashkin375cfd32012-12-03 13:55:33 -0800749 parent: An edge to the parent, always an Enum instance.
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800750 """
Igor Murashkin375cfd32012-12-03 13:55:33 -0800751 def __init__(self, name, parent, id=None, optional=False, notes=None):
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800752 self._name = name # str, e.g. 'ON' or 'OFF'
753 self._id = id # int, e.g. '0'
754 self._optional = optional # bool
755 self._notes = notes # None or str
Igor Murashkin375cfd32012-12-03 13:55:33 -0800756 self._parent = parent
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800757
758 @property
759 def id(self):
760 return self._id
761
762 @property
763 def optional(self):
764 return self._optional
765
766 @property
767 def notes(self):
768 return self._notes
769
Igor Murashkin375cfd32012-12-03 13:55:33 -0800770 def _get_children(self):
771 return None
772
773class Enum(Node):
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800774 """
775 A class corresponding to an <enum> element within an <entry>.
776
777 Attributes (Read-Only):
778 parent: An edge to the parent, always an Entry instance.
779 values: A sequence of EnumValue children.
780 """
781 def __init__(self, parent, values, ids={}, optionals=[], notes={}):
782 self._values = \
Igor Murashkin375cfd32012-12-03 13:55:33 -0800783 [ EnumValue(val, self, ids.get(val), val in optionals, notes.get(val)) \
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800784 for val in values ]
785
786 self._parent = parent
Igor Murashkin375cfd32012-12-03 13:55:33 -0800787 self._name = None
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800788
789 @property
790 def values(self):
791 return (i for i in self._values)
792
Igor Murashkin375cfd32012-12-03 13:55:33 -0800793 def _get_children(self):
794 return (i for i in self._values)
795
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800796class Entry(Node):
797 """
798 A node corresponding to an <entry> element.
799
800 Attributes (Read-Only):
801 parent: An edge to the parent node, which is an InnerNamespace or Kind.
802 name: The fully qualified name string, e.g. 'android.shading.mode'
803 name_short: The name attribute from <entry name="mode">, e.g. mode
804 type: The type attribute from <entry type="bar">
805 kind: A string ('static', 'dynamic', 'controls') corresponding to the
806 ancestor Kind#name
807 container: The container attribute from <entry container="array">, or None.
808 container_sizes: A sequence of size strings or None if container is None.
Igor Murashkinb556bc42012-12-04 16:07:21 -0800809 enum: An Enum instance if the enum attribute is true, None otherwise.
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800810 tuple_values: A sequence of strings describing the tuple values,
811 None if container is not 'tuple'.
812 description: A string description, or None.
813 range: A string range, or None.
814 units: A string units, or None.
815 tags: A sequence of Tag nodes associated with this Entry.
816 type_notes: A string describing notes for the type, or None.
817
818 Remarks:
819 Subclass Clone can be used interchangeable with an Entry,
820 for when we don't care about the underlying type.
821
822 parent and tags edges are invalid until after Metadata#construct_graph
823 has been invoked.
824 """
825 def __init__(self, **kwargs):
826 """
827 Instantiate a new Entry node.
828
829 Args:
830 name: A string with the fully qualified name, e.g. 'android.shading.mode'
831 type: A string describing the type, e.g. 'int32'
832 kind: A string describing the kind, e.g. 'static'
833
834 Args (if container):
835 container: A string describing the container, e.g. 'array' or 'tuple'
836 container_sizes: A list of string sizes if a container, or None otherwise
837
838 Args (if container is 'tuple'):
839 tuple_values: A list of tuple values, e.g. ['width', 'height']
840
Igor Murashkinb556bc42012-12-04 16:07:21 -0800841 Args (if the 'enum' attribute is true):
842 enum: A boolean, True if this is an enum, False otherwise
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800843 enum_values: A list of value strings, e.g. ['ON', 'OFF']
844 enum_optionals: A list of optional enum values, e.g. ['OFF']
845 enum_notes: A dictionary of value->notes strings.
846 enum_ids: A dictionary of value->id strings.
847
848 Args (optional):
849 description: A string with a description of the entry.
850 range: A string with the range of the values of the entry, e.g. '>= 0'
851 units: A string with the units of the values, e.g. 'inches'
852 notes: A string with the notes for the entry
853 tag_ids: A list of tag ID strings, e.g. ['BC', 'V1']
854 type_notes: A string with the notes for the type
855 """
856
857 if kwargs.get('type') is None:
858 print >> sys.stderr, "ERROR: Missing type for entry '%s' kind '%s'" \
859 %(kwargs.get('name'), kwargs.get('kind'))
860
861 # Attributes are Read-Only, but edges may be mutated by
862 # Metadata, particularly during construct_graph
863
864 self._name = kwargs['name']
865 self._type = kwargs['type']
866 self._kind = kwargs['kind'] # static, dynamic, or controls
867
868 self._init_common(**kwargs)
869
870 @property
871 def type(self):
872 return self._type
873
874 @property
875 def kind(self):
876 return self._kind
877
878 @property
879 def name_short(self):
880 return self.get_name_minimal()
881
882 @property
883 def container(self):
884 return self._container
885
886 @property
887 def container_sizes(self):
888 if self._container_sizes is None:
889 return None
890 else:
891 return (i for i in self._container_sizes)
892
893 @property
894 def tuple_values(self):
895 if self._tuple_values is None:
896 return None
897 else:
898 return (i for i in self._tuple_values)
899
900 @property
901 def description(self):
902 return self._description
903
904 @property
905 def range(self):
906 return self._range
907
908 @property
909 def units(self):
910 return self._units
911
912 @property
913 def notes(self):
914 return self._notes
915
916 @property
917 def tags(self):
918 if self._tags is None:
919 return None
920 else:
921 return (i for i in self._tags)
922
923 @property
924 def type_notes(self):
925 return self._type_notes
926
927 @property
928 def enum(self):
929 return self._enum
930
931 def _get_children(self):
Igor Murashkin375cfd32012-12-03 13:55:33 -0800932 if self.enum:
933 yield self.enum
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800934
935 def sort_children(self):
936 return None
937
938 def is_clone(self):
939 """
940 Whether or not this is a Clone instance.
941
942 Returns:
943 False
944 """
945 return False
946
947 def _init_common(self, **kwargs):
948
949 self._parent = None # filled in by MetadataSet::_construct_entries
950
951 self._container = kwargs.get('container')
952 self._container_sizes = kwargs.get('container_sizes')
953
954 # access these via the 'enum' prop
955 enum_values = kwargs.get('enum_values')
956 enum_optionals = kwargs.get('enum_optionals')
957 enum_notes = kwargs.get('enum_notes') # { value => notes }
958 enum_ids = kwargs.get('enum_ids') # { value => notes }
959 self._tuple_values = kwargs.get('tuple_values')
960
961 self._description = kwargs.get('description')
962 self._range = kwargs.get('range')
963 self._units = kwargs.get('units')
964 self._notes = kwargs.get('notes')
965
966 self._tag_ids = kwargs.get('tag_ids', [])
967 self._tags = None # Filled in by MetadataSet::_construct_tags
968
969 self._type_notes = kwargs.get('type_notes')
970
Igor Murashkinb556bc42012-12-04 16:07:21 -0800971 if kwargs.get('enum', False):
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800972 self._enum = Enum(self, enum_values, enum_ids, enum_optionals, enum_notes)
Igor Murashkin617da162012-11-29 13:35:15 -0800973 else:
974 self._enum = None
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800975
976 self._property_keys = kwargs
977
Igor Murashkin617da162012-11-29 13:35:15 -0800978 def merge(self):
979 """
980 Copy the attributes into a new entry, merging it with the target entry
981 if it's a clone.
982 """
983 return MergedEntry(self)
984
Igor Murashkin77b63ca2012-11-09 16:15:02 -0800985 # Helpers for accessing less than the fully qualified name
986
987 def get_name_as_list(self):
988 """
989 Returns the name as a list split by a period.
990
991 For example:
992 entry.name is 'android.lens.info.shading'
993 entry.get_name_as_list() == ['android', 'lens', 'info', 'shading']
994 """
995 return self.name.split(".")
996
997 def get_inner_namespace_list(self):
998 """
999 Returns the inner namespace part of the name as a list
1000
1001 For example:
1002 entry.name is 'android.lens.info.shading'
1003 entry.get_inner_namespace_list() == ['info']
1004 """
1005 return self.get_name_as_list()[2:-1]
1006
1007 def get_outer_namespace(self):
1008 """
1009 Returns the outer namespace as a string.
1010
1011 For example:
1012 entry.name is 'android.lens.info.shading'
1013 entry.get_outer_namespace() == 'android'
1014
1015 Remarks:
1016 Since outer namespaces are non-recursive,
1017 and each entry has one, this does not need to be a list.
1018 """
1019 return self.get_name_as_list()[0]
1020
1021 def get_section(self):
1022 """
1023 Returns the section as a string.
1024
1025 For example:
1026 entry.name is 'android.lens.info.shading'
1027 entry.get_section() == ''
1028
1029 Remarks:
1030 Since outer namespaces are non-recursive,
1031 and each entry has one, this does not need to be a list.
1032 """
1033 return self.get_name_as_list()[1]
1034
1035 def get_name_minimal(self):
1036 """
1037 Returns only the last component of the fully qualified name as a string.
1038
1039 For example:
1040 entry.name is 'android.lens.info.shading'
1041 entry.get_name_minimal() == 'shading'
1042
1043 Remarks:
1044 entry.name_short it an alias for this
1045 """
1046 return self.get_name_as_list()[-1]
1047
Igor Murashkin7b9a2dc2012-11-21 14:23:24 -08001048 def get_path_without_name(self):
1049 """
1050 Returns a string path to the entry, with the name component excluded.
1051
1052 For example:
1053 entry.name is 'android.lens.info.shading'
1054 entry.get_path_without_name() == 'android.lens.info'
1055 """
1056 return ".".join(self.get_name_as_list()[0:-1])
1057
1058
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001059class Clone(Entry):
1060 """
1061 A Node corresponding to a <clone> element. It has all the attributes of an
1062 <entry> element (Entry) plus the additions specified below.
1063
1064 Attributes (Read-Only):
1065 entry: an edge to an Entry object that this targets
1066 target_kind: A string describing the kind of the target entry.
1067 name: a string of the name, same as entry.name
1068 kind: a string of the Kind ancestor, one of 'static', 'controls', 'dynamic'
1069 for the <clone> element.
1070 type: always None, since a clone cannot override the type.
1071 """
1072 def __init__(self, entry=None, **kwargs):
1073 """
1074 Instantiate a new Clone node.
1075
1076 Args:
1077 name: A string with the fully qualified name, e.g. 'android.shading.mode'
1078 type: A string describing the type, e.g. 'int32'
1079 kind: A string describing the kind, e.g. 'static'
1080 target_kind: A string for the kind of the target entry, e.g. 'dynamic'
1081
1082 Args (if container):
1083 container: A string describing the container, e.g. 'array' or 'tuple'
1084 container_sizes: A list of string sizes if a container, or None otherwise
1085
1086 Args (if container is 'tuple'):
1087 tuple_values: A list of tuple values, e.g. ['width', 'height']
1088
Igor Murashkinb556bc42012-12-04 16:07:21 -08001089 Args (if the 'enum' attribute is true):
1090 enum: A boolean, True if this is an enum, False otherwise
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001091 enum_values: A list of value strings, e.g. ['ON', 'OFF']
1092 enum_optionals: A list of optional enum values, e.g. ['OFF']
1093 enum_notes: A dictionary of value->notes strings.
1094 enum_ids: A dictionary of value->id strings.
1095
1096 Args (optional):
1097 entry: An edge to the corresponding target Entry.
1098 description: A string with a description of the entry.
1099 range: A string with the range of the values of the entry, e.g. '>= 0'
1100 units: A string with the units of the values, e.g. 'inches'
1101 notes: A string with the notes for the entry
1102 tag_ids: A list of tag ID strings, e.g. ['BC', 'V1']
1103 type_notes: A string with the notes for the type
1104
1105 Remarks:
1106 Note that type is not specified since it has to be the same as the
1107 entry.type.
1108 """
1109 self._entry = entry # Entry object
1110 self._target_kind = kwargs['target_kind']
1111 self._name = kwargs['name'] # same as entry.name
1112 self._kind = kwargs['kind']
1113
1114 # illegal to override the type, it should be the same as the entry
1115 self._type = None
1116 # the rest of the kwargs are optional
1117 # can be used to override the regular entry data
1118 self._init_common(**kwargs)
1119
1120 @property
1121 def entry(self):
1122 return self._entry
1123
1124 @property
1125 def target_kind(self):
1126 return self._target_kind
1127
1128 def is_clone(self):
1129 """
1130 Whether or not this is a Clone instance.
1131
1132 Returns:
1133 True
1134 """
1135 return True
1136
Igor Murashkin617da162012-11-29 13:35:15 -08001137class MergedEntry(Entry):
1138 """
1139 A MergedEntry has all the attributes of a Clone and its target Entry merged
1140 together.
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001141
Igor Murashkin617da162012-11-29 13:35:15 -08001142 Remarks:
1143 Useful when we want to 'unfold' a clone into a real entry by copying out
1144 the target entry data. In this case we don't care about distinguishing
1145 a clone vs an entry.
1146 """
1147 def __init__(self, entry):
1148 """
1149 Create a new instance of MergedEntry.
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001150
Igor Murashkin617da162012-11-29 13:35:15 -08001151 Args:
1152 entry: An Entry or Clone instance
1153 """
1154 props_distinct = ['description', 'units', 'range', 'notes', 'tags', 'kind']
1155
1156 for p in props_distinct:
Igor Murashkinbaacf9a2012-12-05 14:49:11 -08001157 p = '_' + p
Igor Murashkin617da162012-11-29 13:35:15 -08001158 if entry.is_clone():
Igor Murashkinbaacf9a2012-12-05 14:49:11 -08001159 setattr(self, p, getattr(entry, p) or getattr(entry.entry, p))
Igor Murashkin617da162012-11-29 13:35:15 -08001160 else:
Igor Murashkinbaacf9a2012-12-05 14:49:11 -08001161 setattr(self, p, getattr(entry, p))
Igor Murashkin617da162012-11-29 13:35:15 -08001162
Igor Murashkinbaacf9a2012-12-05 14:49:11 -08001163 props_common = ['parent', 'name', 'container',
Igor Murashkin617da162012-11-29 13:35:15 -08001164 'container_sizes', 'enum',
1165 'tuple_values',
1166 'type',
1167 'type_notes',
Igor Murashkin617da162012-11-29 13:35:15 -08001168 ]
1169
1170 for p in props_common:
Igor Murashkinbaacf9a2012-12-05 14:49:11 -08001171 p = '_' + p
Igor Murashkin617da162012-11-29 13:35:15 -08001172 if entry.is_clone():
Igor Murashkinbaacf9a2012-12-05 14:49:11 -08001173 setattr(self, p, getattr(entry.entry, p))
Igor Murashkin617da162012-11-29 13:35:15 -08001174 else:
Igor Murashkinbaacf9a2012-12-05 14:49:11 -08001175 setattr(self, p, getattr(entry, p))
Igor Murashkin77b63ca2012-11-09 16:15:02 -08001176